Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions @shared/api/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1611,11 +1611,13 @@ export const saveSettings = async ({
isDataSharingAllowed,
isMemoValidationEnabled,
isHideDustEnabled,
isOpenSidebarByDefault,
}: {
activePublicKey: string;
isDataSharingAllowed: boolean;
isMemoValidationEnabled: boolean;
isHideDustEnabled: boolean;
isOpenSidebarByDefault: boolean;
}): Promise<Settings & IndexerSettings> => {
let response = {
allowList: DEFAULT_ALLOW_LIST,
Expand All @@ -1629,6 +1631,7 @@ export const saveSettings = async ({
isSorobanPublicEnabled: false,
isNonSSLEnabled: false,
isHideDustEnabled: true,
isOpenSidebarByDefault: false,
error: "",
hiddenAssets: {},
};
Expand All @@ -1639,6 +1642,7 @@ export const saveSettings = async ({
isDataSharingAllowed,
isMemoValidationEnabled,
isHideDustEnabled,
isOpenSidebarByDefault,
type: SERVICE_TYPES.SAVE_SETTINGS,
});
} catch (e) {
Expand Down
20 changes: 19 additions & 1 deletion @shared/api/types/message-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ export interface SaveSettingsMessage extends BaseMessage {
isHideDustEnabled: boolean;
isMemoValidationEnabled: boolean;
isDataSharingAllowed: boolean;
isOpenSidebarByDefault: boolean;
}

export interface SaveExperimentalFeaturesMessage extends BaseMessage {
Expand Down Expand Up @@ -444,6 +445,20 @@ export interface MarkQueueActiveMessage extends BaseMessage {
isActive: boolean;
}

export interface SidebarRegisterMessage extends BaseMessage {
type: SERVICE_TYPES.SIDEBAR_REGISTER;
windowId: number;
}

export interface SidebarUnregisterMessage extends BaseMessage {
type: SERVICE_TYPES.SIDEBAR_UNREGISTER;
}

export interface OpenSidebarMessage extends BaseMessage {
type: SERVICE_TYPES.OPEN_SIDEBAR;
windowId: number;
}

export type ServiceMessageRequest =
| FundAccountMessage
| CreateAccountMessage
Expand Down Expand Up @@ -508,4 +523,7 @@ export type ServiceMessageRequest =
| GetCollectiblesMessage
| ChangeCollectibleVisibilityMessage
| GetHiddenCollectiblesMessage
| MarkQueueActiveMessage;
| MarkQueueActiveMessage
| SidebarRegisterMessage
| SidebarUnregisterMessage
| OpenSidebarMessage;
2 changes: 2 additions & 0 deletions @shared/api/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export interface Response {
recommendedFee: string;
isNonSSLEnabled: boolean;
isHideDustEnabled: boolean;
isOpenSidebarByDefault: boolean;
activePublicKey: string;
isAccountMismatch: boolean;
assetVisibility: {
Expand Down Expand Up @@ -185,6 +186,7 @@ export interface Preferences {
isMemoValidationEnabled: boolean;
networksList: NetworkDetails[];
isHideDustEnabled: boolean;
isOpenSidebarByDefault: boolean;
error: string;
}

Expand Down
3 changes: 3 additions & 0 deletions @shared/constants/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ export enum SERVICE_TYPES {
CHANGE_COLLECTIBLE_VISIBILITY = "CHANGE_COLLECTIBLE_VISIBILITY",
GET_HIDDEN_COLLECTIBLES = "GET_HIDDEN_COLLECTIBLES",
MARK_QUEUE_ACTIVE = "MARK_QUEUE_ACTIVE",
SIDEBAR_REGISTER = "SIDEBAR_REGISTER",
SIDEBAR_UNREGISTER = "SIDEBAR_UNREGISTER",
OPEN_SIDEBAR = "OPEN_SIDEBAR",
}

export enum EXTERNAL_SERVICE_TYPES {
Expand Down
94 changes: 62 additions & 32 deletions extension/e2e-tests/accountHistory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,38 +23,68 @@ test("View Account History", async ({ page, extensionId, context }) => {
});

test("View failed transaction", async ({ page, extensionId, context }) => {
test.slow();
const mockAccountHistoryData = [
{
amount: "0.0010000",
asset_code: "USDC",
asset_issuer:
"GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
asset_type: "credit_alphanum4",
created_at: "2025-03-21T22:28:46Z",
from: "GDF32CQINROD3E2LMCGZUDVMWTXCJFR5SBYVRJ7WAAIAS3P7DCVWZEFY",
id: "164007621169153",
paging_token: "164007621169153",
source_account:
"GDF32CQINROD3E2LMCGZUDVMWTXCJFR5SBYVRJ7WAAIAS3P7DCVWZEFY",
to: "GCKUVXILBNYS4FDNWCGCYSJBY2PBQ4KAW2M5CODRVJPUFM62IJFH67J2",
transaction_attr: {},
transaction_hash:
"686601028de9ddf40a1c24461a6a9c0415d60a39255c35eccad0b52ac1e700a5",
transaction_successful: false,
type: "payment",
type_i: 1,
},
];

const stubOverrides = async () => {
await page.route("*/**/account-history/*", async (route) => {
const json = [
{
amount: "0.0010000",
asset_code: "USDC",
asset_issuer:
"GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
asset_type: "credit_alphanum4",
created_at: "2025-03-21T22:28:46Z",
from: "GDF32CQINROD3E2LMCGZUDVMWTXCJFR5SBYVRJ7WAAIAS3P7DCVWZEFY",
id: "164007621169153",
paging_token: "164007621169153",
source_account:
"GDF32CQINROD3E2LMCGZUDVMWTXCJFR5SBYVRJ7WAAIAS3P7DCVWZEFY",
to: "GCKUVXILBNYS4FDNWCGCYSJBY2PBQ4KAW2M5CODRVJPUFM62IJFH67J2",
transaction_attr: {},
transaction_hash:
"686601028de9ddf40a1c24461a6a9c0415d60a39255c35eccad0b52ac1e700a5",
transaction_successful: false,
type: "payment",
type_i: 1,
},
];
await route.fulfill({ json });
// Use addInitScript to patch window.fetch directly in the extension page.
// Playwright's route interception (context.route/page.route) does not reliably
// intercept fetch requests made from Chrome extension popup pages in CI headless mode.
// addInitScript injects code before page scripts run and is guaranteed to work.
await page.addInitScript((data: object[]) => {
const origFetch = window.fetch.bind(window);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).fetch = function (input: any, init: any) {
const urlStr: string =
typeof input === "string"
? input
: input instanceof URL
? input.href
: input.url ?? "";
if (urlStr.includes("/account-history/")) {
return Promise.resolve(
new Response(JSON.stringify(data), {
status: 200,
headers: { "Content-Type": "application/json" },
}),
);
}
return origFetch(input, init);
};
}, mockAccountHistoryData);

// Also register context.route as a network-level fallback
await context.route("*/**/account-history/*", async (route) => {
await route.fulfill({ json: mockAccountHistoryData });
});
};

await loginToTestAccount({ page, extensionId, context, stubOverrides });
await page.getByTestId("nav-link-account-history").click();
await expect(page.getByTestId("history-item-amount-component")).toHaveText(
"Mar 21",
{ timeout: 30000 },
);
await expect(page.getByTestId("history-item-label")).toHaveText(
"Transaction Failed",
Expand All @@ -76,7 +106,7 @@ test("Hide create claimable balance spam", async ({
context,
}) => {
const stubOverrides = async () => {
await page.route("*/**/account-history/*", async (route) => {
await context.route("*/**/account-history/*", async (route) => {
const json = [
{
amount: "0.0010000",
Expand Down Expand Up @@ -205,7 +235,7 @@ test("History row displays muxed address extracted from XDR for payment", async
const envelopeXdr = tx.toXDR();
// Stub account history BEFORE login to ensure it catches all requests
// (returns both base G address and muxed M address)
await page.route("**/account-history/**", async (route) => {
await context.route("**/account-history/**", async (route) => {
const json = [
{
amount: "1.0000000",
Expand Down Expand Up @@ -307,7 +337,7 @@ test.skip("History row displays address extracted from XDR for createAccount", a
await stubAccountBalances(page);
await loginToTestAccount({ page, extensionId, context });

await page.route("**/account-history/**", async (route) => {
await context.route("**/account-history/**", async (route) => {
const json = [
{
amount: "1.0000000",
Expand Down Expand Up @@ -388,7 +418,7 @@ test("History row displays regular G address when no muxed address in XDR", asyn

const stubOverrides = async () => {
// Stub account history
await page.route("**/account-history/**", async (route) => {
await context.route("**/account-history/**", async (route) => {
const json = [
{
amount: "1.0000000",
Expand Down Expand Up @@ -463,7 +493,7 @@ test.describe("Asset Diffs in Transaction History", () => {
await stubTokenDetails(page);

const stubOverrides = async () => {
await page.route("*/**/account-history/*", async (route) => {
await context.route("*/**/account-history/*", async (route) => {
const json = [
{
amount: "100.0000000",
Expand Down Expand Up @@ -528,7 +558,7 @@ test.describe("Asset Diffs in Transaction History", () => {
await stubTokenDetails(page);

const stubOverrides = async () => {
await page.route("*/**/account-history/*", async (route) => {
await context.route("*/**/account-history/*", async (route) => {
const json = [
{
asset_type: "native",
Expand Down Expand Up @@ -609,7 +639,7 @@ test.describe("Asset Diffs in Transaction History", () => {
await stubTokenDetails(page);

const stubOverrides = async () => {
await page.route("*/**/account-history/*", async (route) => {
await context.route("*/**/account-history/*", async (route) => {
const json = [
{
asset_type: "native",
Expand Down Expand Up @@ -713,7 +743,7 @@ test.describe("Asset Diffs in Transaction History", () => {
}
});

await page.route("*/**/account-history/*", async (route) => {
await context.route("*/**/account-history/*", async (route) => {
const json = [
{
amount: "1000000000000000000",
Expand Down
6 changes: 3 additions & 3 deletions extension/e2e-tests/helpers/stubs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -839,8 +839,8 @@ export const stubAccountBalancesWithUSDC = async (page: Page) => {
});
};

export const stubAccountHistory = async (page: Page) => {
await page.route("**/account-history/**", async (route) => {
export const stubAccountHistory = async (context: BrowserContext) => {
await context.route("**/account-history/**", async (route) => {
const json = [
Comment on lines +842 to 844
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stubAccountHistory() now accepts a BrowserContext, but there are still many call sites in e2e tests passing a Page (e.g., addAsset.test.ts, hideCollectible.test.ts, freighterApiIntegration.test.ts). This will cause TypeScript compile/runtime errors. Update all callers to pass the test BrowserContext instead of the Page.

Copilot uses AI. Check for mistakes.
{
_links: {
Expand Down Expand Up @@ -2708,7 +2708,7 @@ export const stubAllExternalApis = async (
// Mercury/History endpoints
// Note: Tests that need account history should call stubAccountHistory() instead
// to provide their own test data
await stubAccountHistory(page);
await stubAccountHistory(context);
await stubMercuryTransactions(page);

// RPC and Soroban
Expand Down
4 changes: 4 additions & 0 deletions extension/public/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import {
initExtensionMessageListener,
initInstalledListener,
initAlarmListener,
initSidebarBehavior,
initSidebarConnectionListener,
} from "background";

function main() {
initContentScriptMessageListener();
initExtensionMessageListener();
initInstalledListener();
initAlarmListener();
initSidebarBehavior();
initSidebarConnectionListener();
}

main();
7 changes: 7 additions & 0 deletions extension/public/static/manifest/v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,12 @@
"128": "images/icon128.png"
},
"permissions": ["storage", "alarms"],
"sidebar_action": {
"default_panel": "index.html?mode=sidebar",
"default_icon": {
"16": "images/icon16.png",
"32": "images/icon32.png"
}
},
"manifest_version": 2
}
5 changes: 4 additions & 1 deletion extension/public/static/manifest/v3.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
"48": "images/icon48.png",
"128": "images/icon128.png"
},
"permissions": ["storage", "alarms"],
"permissions": ["storage", "alarms", "sidePanel"],
"side_panel": {
"default_path": "index.html?mode=sidebar"
},
"manifest_version": 3
}
38 changes: 37 additions & 1 deletion extension/src/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ import {
import { ExternalRequest, Response } from "@shared/api/types";
import { buildStore } from "background/store";

import { popupMessageListener } from "./messageListener/popupMessageListener";
import {
popupMessageListener,
clearSidebarWindowId,
setSidebarWindowId,
} from "./messageListener/popupMessageListener";
import { freighterApiMessageListener } from "./messageListener/freighterApiMessageListener";
import { SIDEBAR_PORT_NAME } from "popup/components/SidebarSigningListener";
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

background/index.ts imports SIDEBAR_PORT_NAME from a popup React component module. This can pull react/react-router-dom (and potentially DOM-dependent code) into the background bundle, risking runtime issues and bloating the service worker. Move SIDEBAR_PORT_NAME to a shared constants module (e.g., @shared/constants/sidebar) and import it from both background and popup.

Suggested change
import { SIDEBAR_PORT_NAME } from "popup/components/SidebarSigningListener";
import { SIDEBAR_PORT_NAME } from "@shared/constants/sidebar";

Copilot uses AI. Check for mistakes.
import {
SESSION_ALARM_NAME,
SessionTimer,
Expand All @@ -24,6 +29,7 @@ import {
dataStorageAccess,
browserLocalStorage,
} from "./helpers/dataStorageAccess";
import { IS_OPEN_SIDEBAR_BY_DEFAULT_ID } from "constants/localStorageTypes";
import { ServiceMessageRequest } from "@shared/api/types/message-request";
import {
BrowserStorageKeyStore,
Expand All @@ -45,6 +51,24 @@ export const initContentScriptMessageListener = () => {
});
};

export const initSidebarConnectionListener = () => {
chrome.runtime.onConnect.addListener((port) => {
if (port.name !== SIDEBAR_PORT_NAME) return;

// Sidebar sends its window ID as first message
port.onMessage.addListener((msg: { windowId: number }) => {
if (msg.windowId !== undefined) {
setSidebarWindowId(msg.windowId);
}
});

// When sidebar closes (for any reason), clear the window ID
port.onDisconnect.addListener(() => {
clearSidebarWindowId();
});
});
};

export const initExtensionMessageListener = () => {
browser?.runtime?.onMessage?.addListener(async (request, sender) => {
const sessionStore = await buildStore();
Expand Down Expand Up @@ -109,6 +133,18 @@ export const initInstalledListener = () => {
browser?.runtime?.onInstalled.addListener(versionedMigration);
};

export const initSidebarBehavior = async () => {
const localStore = dataStorageAccess(browserLocalStorage);
const val =
((await localStore.getItem(IS_OPEN_SIDEBAR_BY_DEFAULT_ID)) as boolean) ??
false;
if (chrome.sidePanel?.setPanelBehavior) {
chrome.sidePanel
.setPanelBehavior({ openPanelOnActionClick: !!val })
.catch((e) => console.error("Failed to set panel behavior:", e));
}
};

export const initAlarmListener = () => {
browser?.alarms?.onAlarm.addListener(async ({ name }: { name: string }) => {
const sessionStore = await buildStore();
Expand Down
Loading
Loading