diff --git a/apps/browser-extension-wallet/src/components/Layout/MainLayout.tsx b/apps/browser-extension-wallet/src/components/Layout/MainLayout.tsx
index d81ce835bb..8ba985a6b4 100644
--- a/apps/browser-extension-wallet/src/components/Layout/MainLayout.tsx
+++ b/apps/browser-extension-wallet/src/components/Layout/MainLayout.tsx
@@ -52,7 +52,7 @@ export const MainLayout = ({
getAboutExtensionData();
}, [getAboutExtensionData]);
- const onUpdateAknowledge = useCallback(async () => {
+ const onUpdateAcknowledge = useCallback(async () => {
const data = { version, acknowledged: true, reason };
await storage.local.set({
[ABOUT_EXTENSION_KEY]: data
@@ -72,7 +72,7 @@ export const MainLayout = ({
diff --git a/apps/browser-extension-wallet/src/features/dapp/components/ConfirmData.tsx b/apps/browser-extension-wallet/src/features/dapp/components/ConfirmData.tsx
index 6ccd1dd225..1dbc00a1ea 100644
--- a/apps/browser-extension-wallet/src/features/dapp/components/ConfirmData.tsx
+++ b/apps/browser-extension-wallet/src/features/dapp/components/ConfirmData.tsx
@@ -65,8 +65,14 @@ export const DappConfirmData = (): React.ReactElement => {
useEffect(() => {
const subscription = signingCoordinator.signDataRequest$.pipe(take(1)).subscribe(async (r) => {
- setDappInfo(await senderToDappInfo(r.signContext.sender));
setSignDataRequest(r);
+ try {
+ setDappInfo(await senderToDappInfo(r.signContext.sender));
+ } catch (error) {
+ logger.error(error);
+ void r.reject('Could not get DApp info');
+ redirectToSignFailure();
+ }
});
const api = exposeApi>(
@@ -86,7 +92,7 @@ export const DappConfirmData = (): React.ReactElement => {
subscription.unsubscribe();
api.shutdown();
};
- }, [setSignDataRequest]);
+ }, [redirectToSignFailure, setSignDataRequest]);
useEffect(() => {
if (!req) return;
diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransaction.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransaction.tsx
index b82adf8e16..0f8125879c 100644
--- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransaction.tsx
+++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransaction.tsx
@@ -19,6 +19,8 @@ import { runtime } from 'webextension-polyfill';
import { Skeleton } from 'antd';
import { DappTransactionContainer } from './DappTransactionContainer';
import { useTxWitnessRequest } from '@providers/TxWitnessRequestProvider';
+import { useRedirection } from '@hooks';
+import { dAppRoutePaths } from '@routes';
export const ConfirmTransaction = (): React.ReactElement => {
const { t } = useTranslation();
@@ -28,6 +30,7 @@ export const ConfirmTransaction = (): React.ReactElement => {
signTxRequest: { request: req, set: setSignTxRequest }
} = useViewsFlowContext();
const { walletType, isHardwareWallet, walletInfo, inMemoryWallet } = useWalletStore();
+ const redirectToDappTxSignFailure = useRedirection(dAppRoutePaths.dappTxSignFailure);
const analytics = useAnalyticsContext();
const [confirmTransactionError] = useState(false);
const disallowSignTx = useDisallowSignTx(req);
@@ -49,11 +52,26 @@ export const ConfirmTransaction = (): React.ReactElement => {
const txWitnessRequest = useTxWitnessRequest();
+ const cancelTransaction = useCallback(() => {
+ disallowSignTx(true);
+ }, [disallowSignTx]);
+
+ useOnUnload(cancelTransaction);
+
useEffect(() => {
(async () => {
- if (!txWitnessRequest) return (): (() => void) => void 0;
+ const emptyFn = (): void => void 0;
+ if (!txWitnessRequest) return emptyFn;
+
+ try {
+ setDappInfo(await senderToDappInfo(txWitnessRequest.signContext.sender));
+ } catch (error) {
+ logger.error(error);
+ void disallowSignTx(true, 'Could not get DApp info');
+ redirectToDappTxSignFailure();
+ return emptyFn;
+ }
- setDappInfo(await senderToDappInfo(txWitnessRequest.signContext.sender));
setSignTxRequest(txWitnessRequest);
const api = exposeApi>(
@@ -73,7 +91,7 @@ export const ConfirmTransaction = (): React.ReactElement => {
api.shutdown();
};
})();
- }, [setSignTxRequest, setDappInfo, txWitnessRequest]);
+ }, [setSignTxRequest, setDappInfo, txWitnessRequest, redirectToDappTxSignFailure, disallowSignTx]);
const onCancelTransaction = () => {
analytics.sendEventToPostHog(PostHogAction.SendTransactionSummaryCancelClick, {
@@ -82,12 +100,6 @@ export const ConfirmTransaction = (): React.ReactElement => {
disallowSignTx(true);
};
- const cancelTransaction = useCallback(() => {
- disallowSignTx(true);
- }, [disallowSignTx]);
-
- useOnUnload(cancelTransaction);
-
return (
{req && walletInfo && inMemoryWallet ? : }
diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/hooks.ts b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/hooks.ts
index 694be40fb8..0ad2b6e2e2 100644
--- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/hooks.ts
+++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/hooks.ts
@@ -151,7 +151,8 @@ export const useCreateMintedAssetList = ({
export const useDisallowSignTx = (
req: TransactionWitnessRequest
-): ((close?: boolean) => Promise) => useCallback(async (close) => await disallowSignTx(req, close), [req]);
+): ((close?: boolean, reason?: string) => Promise) =>
+ useCallback(async (close, reason) => await disallowSignTx(req, close, reason), [req]);
export const useAllowSignTx = (
req: TransactionWitnessRequest
diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/utils.ts b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/utils.ts
index 519b4613d9..c08a384b2f 100644
--- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/utils.ts
+++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/utils.ts
@@ -27,10 +27,11 @@ export const readyToSign = (): void => {
export const disallowSignTx = async (
req: TransactionWitnessRequest,
- close = false
+ close = false,
+ reason = 'User declined to sign'
): Promise => {
try {
- await req?.reject('User declined to sign');
+ await req?.reject(reason);
} finally {
close && window.close();
}
diff --git a/apps/browser-extension-wallet/src/features/nami-migration/migration-tool/cross-extension-messaging/lace-migration-client.extension.ts b/apps/browser-extension-wallet/src/features/nami-migration/migration-tool/cross-extension-messaging/lace-migration-client.extension.ts
index 3bc1769303..fc07da39f3 100644
--- a/apps/browser-extension-wallet/src/features/nami-migration/migration-tool/cross-extension-messaging/lace-migration-client.extension.ts
+++ b/apps/browser-extension-wallet/src/features/nami-migration/migration-tool/cross-extension-messaging/lace-migration-client.extension.ts
@@ -7,6 +7,7 @@ import { NAMI_EXTENSION_ID } from './lace/environment';
import { createLaceMigrationOpenListener } from './lace/create-lace-migration-open-listener';
import { LACE_EXTENSION_ID } from './nami/environment';
import { logger } from '@lace/common';
+import { catchAndBrandExtensionApiError } from '@utils/catch-and-brand-extension-api-error';
type CheckMigrationStatus = () => Promise;
@@ -42,6 +43,14 @@ export const handleNamiRequests = (): void => {
runtime.onMessageExternal.addListener(createLaceMigrationPingListener(NAMI_EXTENSION_ID));
logger.debug('[NAMI MIGRATION] createLaceMigrationOpenListener');
runtime.onMessageExternal.addListener(
- createLaceMigrationOpenListener(NAMI_EXTENSION_ID, LACE_EXTENSION_ID, tabs.create)
+ createLaceMigrationOpenListener(NAMI_EXTENSION_ID, LACE_EXTENSION_ID, ({ url }) =>
+ catchAndBrandExtensionApiError(
+ tabs.create({ url }),
+ `[NAMI MIGRATION] laceMigrationOpenListener failed to create tab with url ${url}`,
+ {
+ reThrow: false
+ }
+ )
+ )
);
};
diff --git a/apps/browser-extension-wallet/src/features/nami-migration/migration-tool/cross-extension-messaging/lace/create-lace-migration-open-listener.ts b/apps/browser-extension-wallet/src/features/nami-migration/migration-tool/cross-extension-messaging/lace/create-lace-migration-open-listener.ts
index 967e3adc73..f4bcd6a7a6 100644
--- a/apps/browser-extension-wallet/src/features/nami-migration/migration-tool/cross-extension-messaging/lace/create-lace-migration-open-listener.ts
+++ b/apps/browser-extension-wallet/src/features/nami-migration/migration-tool/cross-extension-messaging/lace/create-lace-migration-open-listener.ts
@@ -8,7 +8,11 @@ export const createLaceMigrationOpenListener =
logger.debug('[NAMI MIGRATION] createLaceMigrationOpenListener', message, sender);
if (message === NamiMessages.open && sender.id === namiExtensionId) {
// First close all open lace tabs
- await closeAllLaceOrNamiTabs();
+ try {
+ await closeAllLaceOrNamiTabs();
+ } catch (error) {
+ logger.error('[NAMI MIGRATION] createLaceMigrationOpenListener: failed to close all windows', error);
+ }
createTab({ url: `chrome-extension://${laceExtensionId}/app.html` });
}
};
diff --git a/apps/browser-extension-wallet/src/lib/scripts/background/cip30.ts b/apps/browser-extension-wallet/src/lib/scripts/background/cip30.ts
index 395b72624b..94d87b34e3 100644
--- a/apps/browser-extension-wallet/src/lib/scripts/background/cip30.ts
+++ b/apps/browser-extension-wallet/src/lib/scripts/background/cip30.ts
@@ -47,7 +47,7 @@ export const confirmationCallback: walletCip30.CallbackConfirmation = {
return cancelOnTabClose(tab);
} catch (error) {
logger.error(error);
- return Promise.reject(new ApiError(APIErrorCode.InternalError, 'Unable to sign transaction'));
+ throw new ApiError(APIErrorCode.InternalError, 'Unable to sign transaction');
}
},
DEBOUNCE_THROTTLE,
@@ -62,8 +62,7 @@ export const confirmationCallback: walletCip30.CallbackConfirmation = {
return cancelOnTabClose(tab);
} catch (error) {
logger.error(error);
- // eslint-disable-next-line unicorn/no-useless-undefined
- return Promise.reject(new ApiError(APIErrorCode.InternalError, 'Unable to sign data'));
+ throw new ApiError(APIErrorCode.InternalError, 'Unable to sign data');
}
},
DEBOUNCE_THROTTLE,
diff --git a/apps/browser-extension-wallet/src/lib/scripts/background/requestAccess.ts b/apps/browser-extension-wallet/src/lib/scripts/background/requestAccess.ts
index bd10398783..090d377a82 100644
--- a/apps/browser-extension-wallet/src/lib/scripts/background/requestAccess.ts
+++ b/apps/browser-extension-wallet/src/lib/scripts/background/requestAccess.ts
@@ -10,15 +10,31 @@ import { AUTHORIZED_DAPPS_KEY } from '../types';
import { Wallet } from '@lace/cardano';
import { BehaviorSubject } from 'rxjs';
import { senderToDappInfo } from '@src/utils/senderToDappInfo';
+import { logger } from '@lace/common';
const DEBOUNCE_THROTTLE = 500;
export const dappInfo$ = new BehaviorSubject(undefined);
export const requestAccess: RequestAccess = async (sender: Runtime.MessageSender) => {
- const { logo, name, url } = await senderToDappInfo(sender);
+ let dappInfo: Wallet.DappInfo;
+ try {
+ dappInfo = await senderToDappInfo(sender);
+ } catch (error) {
+ logger.error('Failed to get info of a DApp requesting access', error);
+ return false;
+ }
+
+ const { logo, name, url } = dappInfo;
dappInfo$.next({ logo, name, url });
- await ensureUiIsOpenAndLoaded('#/dapp/connect');
+
+ try {
+ await ensureUiIsOpenAndLoaded('#/dapp/connect');
+ } catch (error) {
+ logger.error('Failed to ensure DApp connection UI is loaded', error);
+ return false;
+ }
+
const isAllowed = await userPromptService.allowOrigin(url);
if (isAllowed === 'deny') return Promise.reject();
if (isAllowed === 'allow') {
@@ -31,14 +47,16 @@ export const requestAccess: RequestAccess = async (sender: Runtime.MessageSender
authorizedDappsList.next([{ logo, name, url }]);
}
} else {
- tabs.onRemoved.addListener((t) => {
- if (t === sender.tab.id) {
+ const onRemovedHandler = (tabId: number) => {
+ if (tabId === sender.tab.id) {
authenticator.revokeAccess(sender);
- tabs.onRemoved.removeListener(this);
+ tabs.onRemoved.removeListener(onRemovedHandler);
}
- });
+ };
+ tabs.onRemoved.addListener(onRemovedHandler);
}
- return Promise.resolve(true);
+
+ return true;
};
export const requestAccessDebounced = pDebounce(requestAccess, DEBOUNCE_THROTTLE, { before: true });
diff --git a/apps/browser-extension-wallet/src/lib/scripts/background/services/utilityServices.ts b/apps/browser-extension-wallet/src/lib/scripts/background/services/utilityServices.ts
index 2f48a8358f..9d608fbf41 100644
--- a/apps/browser-extension-wallet/src/lib/scripts/background/services/utilityServices.ts
+++ b/apps/browser-extension-wallet/src/lib/scripts/background/services/utilityServices.ts
@@ -29,6 +29,7 @@ import { laceFeaturesApiProperties, LACE_FEATURES_CHANNEL } from '../injectUtil'
import { getErrorMessage } from '@src/utils/get-error-message';
import { logger } from '@lace/common';
import { POPUP_WINDOW_NAMI_TITLE } from '@utils/constants';
+import { catchAndBrandExtensionApiError } from '@utils/catch-and-brand-extension-api-error';
export const requestMessage$ = new Subject();
export const backendFailures$ = new BehaviorSubject(0);
@@ -103,17 +104,26 @@ const handleOpenBrowser = async (data: OpenBrowserData) => {
break;
}
const params = data.urlSearchParams ? `?${data.urlSearchParams}` : '';
- await tabs.create({ url: `app.html#${path}${params}` }).catch((error) => logger.error(error));
+ const url = `app.html#${path}${params}`;
+ await catchAndBrandExtensionApiError(tabs.create({ url }), `Failed to open expanded view with url: ${url}`).catch(
+ (error) => logger.error(error)
+ );
};
const handleOpenNamiBrowser = async (data: OpenNamiBrowserData) => {
- await tabs.create({ url: `popup.html#${data.path}` }).catch((error) => logger.error(error));
+ const url = `popup.html#${data.path}`;
+ await catchAndBrandExtensionApiError(
+ tabs.create({ url }),
+ `Failed to open nami mode extended with url: ${url}`
+ ).catch((error) => logger.error(error));
};
const enrichWithTabsDataIfMissing = (browserWindows: Windows.Window[]) => {
const promises = browserWindows.map(async (w) => ({
...w,
- tabs: w.tabs || (await tabs.query({ windowId: w.id }))
+ tabs:
+ w.tabs ||
+ (await catchAndBrandExtensionApiError(tabs.query({ windowId: w.id }), 'Failed to query tabs of a window'))
}));
return Promise.all(promises);
};
@@ -129,7 +139,8 @@ const doesWindowHaveOtherTabs = (browserWindow: WindowWithTabsNotOptional) =>
const closeAllTabsAndOpenPopup = async () => {
try {
- const allWindows = await enrichWithTabsDataIfMissing(await windows.getAll());
+ const allWindowsRaw = await catchAndBrandExtensionApiError(windows.getAll(), 'Failed to query all browser windows');
+ const allWindows = await enrichWithTabsDataIfMissing(allWindowsRaw);
if (allWindows.length === 0) return;
const windowsWith3rdPartyTabs = allWindows.filter((w) => doesWindowHaveOtherTabs(w));
@@ -141,14 +152,19 @@ const closeAllTabsAndOpenPopup = async () => {
const noSingleWindowWith3rdPartyTabsOpen = !nextFocusedWindow;
if (noSingleWindowWith3rdPartyTabsOpen) {
nextFocusedWindow = allWindows[0];
- await tabs.create({ active: true, windowId: nextFocusedWindow.id });
+ await catchAndBrandExtensionApiError(
+ tabs.create({ active: true, windowId: nextFocusedWindow.id }),
+ 'Failed to open empty tab to prevent window from closing'
+ );
}
- await windows.update(nextFocusedWindow.id, { focused: true });
+ await catchAndBrandExtensionApiError(
+ windows.update(nextFocusedWindow.id, { focused: true }),
+ 'Failed to focus window'
+ );
await closeAllLaceOrNamiTabs();
- await action.openPopup();
+ await catchAndBrandExtensionApiError(action.openPopup(), 'Failed to open popup');
} catch (error) {
- // unable to programatically open the popup again
logger.error(error);
}
};
diff --git a/apps/browser-extension-wallet/src/lib/scripts/background/session/is-lace-tab-active.ts b/apps/browser-extension-wallet/src/lib/scripts/background/session/is-lace-tab-active.ts
index cacdfe5413..bca0397d01 100644
--- a/apps/browser-extension-wallet/src/lib/scripts/background/session/is-lace-tab-active.ts
+++ b/apps/browser-extension-wallet/src/lib/scripts/background/session/is-lace-tab-active.ts
@@ -1,6 +1,7 @@
import { LACE_EXTENSION_ID } from '@src/features/nami-migration/migration-tool/cross-extension-messaging/nami/environment';
import { distinctUntilChanged, from, fromEventPattern, map, merge, share, startWith, switchMap } from 'rxjs';
import { Tabs, tabs, windows } from 'webextension-polyfill';
+import { catchAndBrandExtensionApiError } from '@utils/catch-and-brand-extension-api-error';
type WindowId = number;
@@ -21,10 +22,13 @@ const tabActivated$ = fromEventPattern(
export const isLaceTabActive$ = merge(windowRemoved$, tabUpdated$, tabActivated$).pipe(
switchMap(() =>
from(
- tabs.query({
- active: true,
- url: `chrome-extension://${LACE_EXTENSION_ID}/*`
- })
+ catchAndBrandExtensionApiError(
+ tabs.query({
+ active: true,
+ url: `chrome-extension://${LACE_EXTENSION_ID}/*`
+ }),
+ 'Failed to query for currently active lace tab'
+ )
)
),
map((activeLaceTabs) => activeLaceTabs.length > 0),
diff --git a/apps/browser-extension-wallet/src/lib/scripts/background/util.ts b/apps/browser-extension-wallet/src/lib/scripts/background/util.ts
index 3f7c2b78f4..ba8bf7d441 100644
--- a/apps/browser-extension-wallet/src/lib/scripts/background/util.ts
+++ b/apps/browser-extension-wallet/src/lib/scripts/background/util.ts
@@ -12,6 +12,7 @@ import {
WalletType
} from '@cardano-sdk/web-extension';
import { getBackgroundStorage } from './storage';
+import { catchAndBrandExtensionApiError } from '@utils/catch-and-brand-extension-api-error';
const { blake2b } = Wallet.Crypto;
const DAPP_CONNECTOR_REGEX = new RegExp(/dappconnector/i);
@@ -47,13 +48,6 @@ const calculatePopupWindowPositionAndSize = (
...popup
});
-const createTab = async (url: string, active = false) =>
- tabs.create({
- url: runtime.getURL(url),
- active,
- pinned: true
- });
-
const createWindow = (
tabId: number,
windowSize: WindowSizeAndPositionProps,
@@ -74,7 +68,14 @@ const createWindow = (
*/
export const launchCip30Popup = async (url: string): Promise => {
const currentWindow = await windows.getCurrent();
- const tab = await createTab(`../dappConnector.html${url}`, false);
+ const tab = await catchAndBrandExtensionApiError(
+ tabs.create({
+ url: runtime.getURL(`../dappConnector.html${url}`),
+ active: false,
+ pinned: true
+ }),
+ 'Failed to launch cip30 popup'
+ );
const { namiMigration } = await getBackgroundStorage();
const windowSize = namiMigration?.mode === 'nami' ? POPUP_WINDOW_NAMI : POPUP_WINDOW;
@@ -125,12 +126,18 @@ export const getActiveWallet = async ({
};
export const closeAllLaceOrNamiTabs = async (shouldRemoveTab?: (url: string) => boolean): Promise => {
- const openTabs = await tabs.query({ title: 'Lace' });
- const namiTabs = await tabs.query({ title: POPUP_WINDOW_NAMI_TITLE });
- openTabs.push(...namiTabs);
+ const openTabs = [
+ ...(await catchAndBrandExtensionApiError(tabs.query({ title: 'Lace' }), 'Failed to query lace tabs for closing')),
+ ...(await catchAndBrandExtensionApiError(
+ tabs.query({ title: POPUP_WINDOW_NAMI_TITLE }),
+ 'Failed to query nami mode tabs for closing'
+ ))
+ ];
// Close all previously opened lace dapp connector windows
for (const tab of openTabs) {
- if (!shouldRemoveTab || shouldRemoveTab(tab.url)) await tabs.remove(tab.id);
+ if (!shouldRemoveTab || shouldRemoveTab(tab.url)) {
+ await catchAndBrandExtensionApiError(tabs.remove(tab.id), `Failed to close tab with url ${tab.url}`);
+ }
}
};
diff --git a/apps/browser-extension-wallet/src/lib/scripts/trezor/trezor-usb-permissions.ts b/apps/browser-extension-wallet/src/lib/scripts/trezor/trezor-usb-permissions.ts
index c4aa97fb19..3e3d1bdf0b 100644
--- a/apps/browser-extension-wallet/src/lib/scripts/trezor/trezor-usb-permissions.ts
+++ b/apps/browser-extension-wallet/src/lib/scripts/trezor/trezor-usb-permissions.ts
@@ -1,27 +1,47 @@
import { runtime, tabs } from 'webextension-polyfill';
import { AllowedOrigins } from './types';
+import { catchAndBrandExtensionApiError } from '@utils/catch-and-brand-extension-api-error';
+import { logger } from '@lace/common';
+
+const contextualMessage = (msg: string) => `[trezor-usb-permissions] ${msg}`;
/* Handling messages from usb permissions iframe */
const switchToPopupTab = async (event?: BeforeUnloadEvent) => {
window.removeEventListener('beforeunload', switchToPopupTab);
- if (!event) {
- // triggered from 'usb-permissions-close' message
- // close current tab
- const currentTabs = await tabs.query({
- currentWindow: true,
- active: true
- });
+ try {
+ if (!event) {
+ // triggered from 'usb-permissions-close' message
+ // close current tab
+ const currentTabs = await catchAndBrandExtensionApiError(
+ tabs.query({
+ currentWindow: true,
+ active: true
+ }),
+ contextualMessage('Failed to query for current tab when switching to popup')
+ );
+ if (currentTabs.length < 0) return;
+ await catchAndBrandExtensionApiError(
+ tabs.remove(currentTabs[0].id),
+ contextualMessage('Failed to remove current tab when switching to popup')
+ );
+ }
+
+ // find tab by popup pattern and switch to it
+ const currentTabs = await catchAndBrandExtensionApiError(
+ tabs.query({
+ url: `${AllowedOrigins.TREZOR_CONNECT_POPUP_BASE_URL}/popup.html`
+ }),
+ contextualMessage('Failed to query TREZOR_CONNECT_POPUP tab')
+ );
if (currentTabs.length < 0) return;
- await tabs.remove(currentTabs[0].id);
+ void catchAndBrandExtensionApiError(
+ tabs.update(currentTabs[0].id, { active: true }),
+ contextualMessage('Failed to switch to the TREZOR_CONNECT_POPUP tab')
+ );
+ } catch (error) {
+ logger.error(error);
}
-
- // find tab by popup pattern and switch to it
- const currentTabs = await tabs.query({
- url: `${AllowedOrigins.TREZOR_CONNECT_POPUP_BASE_URL}/popup.html`
- });
- if (currentTabs.length < 0) return;
- tabs.update(currentTabs[0].id, { active: true });
};
window.addEventListener('message', async (event) => {
diff --git a/apps/browser-extension-wallet/src/utils/catch-and-brand-extension-api-error.ts b/apps/browser-extension-wallet/src/utils/catch-and-brand-extension-api-error.ts
new file mode 100644
index 0000000000..6367709e6a
--- /dev/null
+++ b/apps/browser-extension-wallet/src/utils/catch-and-brand-extension-api-error.ts
@@ -0,0 +1,24 @@
+import { logger } from '@lace/common';
+
+class ExtensionApiError extends Error {}
+
+type CatchAndBrandExtensionApiErrorOptions = {
+ reThrow?: boolean;
+};
+
+export const catchAndBrandExtensionApiError = async (
+ promise: Promise,
+ errorMessage: string,
+ { reThrow = true }: CatchAndBrandExtensionApiErrorOptions = {}
+ // eslint-disable-next-line consistent-return
+): Promise => {
+ try {
+ return await promise;
+ } catch (error) {
+ const message = `${errorMessage} due to: ${error}`;
+ logger.error(`[WebExtension API error] ${message}`);
+ if (reThrow) {
+ throw new ExtensionApiError(errorMessage);
+ }
+ }
+};
diff --git a/apps/browser-extension-wallet/src/utils/senderToDappInfo.ts b/apps/browser-extension-wallet/src/utils/senderToDappInfo.ts
index 2ac5c51f56..ece34ddcff 100644
--- a/apps/browser-extension-wallet/src/utils/senderToDappInfo.ts
+++ b/apps/browser-extension-wallet/src/utils/senderToDappInfo.ts
@@ -3,12 +3,16 @@ import { Wallet } from '@lace/cardano';
import { getRandomIcon } from '@lace/common';
import uniqueId from 'lodash/uniqueId';
import { tabs, Runtime } from 'webextension-polyfill';
+import { catchAndBrandExtensionApiError } from '@utils/catch-and-brand-extension-api-error';
export const senderToDappInfo = async (sender: Runtime.MessageSender): Promise => {
if (!sender.tab?.id) throw new Error('Unknown sender tab id');
// Tab info might've changed. It used to fail e2e tests when using data from 'sender.tab'.
// It would be better if SDK waited for tab to load before emitting events with sender.
- const tab = await tabs.get(sender.tab?.id);
+ const tab = await catchAndBrandExtensionApiError(
+ tabs.get(sender.tab?.id),
+ `Failed to get tab data of a DApp with url ${sender.url}`
+ );
return {
url: senderOrigin(sender),
logo: tab.favIconUrl || getRandomIcon({ id: uniqueId(), size: 40 }),
diff --git a/apps/browser-extension-wallet/src/views/browser-view/components/TopUpWallet/TopUpWalletButton.tsx b/apps/browser-extension-wallet/src/views/browser-view/components/TopUpWallet/TopUpWalletButton.tsx
index 3d3c9090e6..39a0358b12 100644
--- a/apps/browser-extension-wallet/src/views/browser-view/components/TopUpWallet/TopUpWalletButton.tsx
+++ b/apps/browser-extension-wallet/src/views/browser-view/components/TopUpWallet/TopUpWalletButton.tsx
@@ -1,10 +1,9 @@
-import { tabs } from 'webextension-polyfill';
import { AdaComponentTransparent, Button } from '@input-output-hk/lace-ui-toolkit';
import React, { useRef, useState } from 'react';
import { TopUpWalletDialog } from './TopUpWalletDialog';
import { useTranslation } from 'react-i18next';
import { BANXA_LACE_URL } from './config';
-import { useAnalyticsContext } from '@providers';
+import { useAnalyticsContext, useExternalLinkOpener } from '@providers';
import { PostHogAction } from '@lace/common';
export const TopUpWalletButton = (): React.ReactElement => {
@@ -12,6 +11,7 @@ export const TopUpWalletButton = (): React.ReactElement => {
const [open, setOpen] = useState(false);
const { t } = useTranslation();
const analytics = useAnalyticsContext();
+ const openExternalLink = useExternalLinkOpener();
return (
<>
@@ -35,7 +35,7 @@ export const TopUpWalletButton = (): React.ReactElement => {
triggerRef={dialogTriggerReference}
onConfirm={() => {
analytics.sendEventToPostHog(PostHogAction.TokenBuyAdaDisclaimerContinueClick);
- tabs.create({ url: BANXA_LACE_URL });
+ openExternalLink(BANXA_LACE_URL);
setOpen(false);
}}
/>
diff --git a/apps/browser-extension-wallet/src/views/browser-view/components/TopUpWallet/TopUpWalletDialog.tsx b/apps/browser-extension-wallet/src/views/browser-view/components/TopUpWallet/TopUpWalletDialog.tsx
index 4a65ce7698..269f31eefc 100644
--- a/apps/browser-extension-wallet/src/views/browser-view/components/TopUpWallet/TopUpWalletDialog.tsx
+++ b/apps/browser-extension-wallet/src/views/browser-view/components/TopUpWallet/TopUpWalletDialog.tsx
@@ -1,9 +1,9 @@
-import React, { useCallback } from 'react';
+import React from 'react';
import { Box, Dialog, Text, TextLink } from '@input-output-hk/lace-ui-toolkit';
import styles from './TopUpWallet.module.scss';
import { useTranslation } from 'react-i18next';
-import { tabs } from 'webextension-polyfill';
import { BANXA_HOMEPAGE_URL } from './config';
+import { useExternalLinkOpener } from '@providers';
interface TopUpWalletDialogProps {
open: boolean;
@@ -18,9 +18,10 @@ export const TopUpWalletDialog = ({
triggerRef
}: TopUpWalletDialogProps): React.ReactElement => {
const { t } = useTranslation();
- const handleOpenTabBanxaHomepage = useCallback(() => {
- tabs.create({ url: BANXA_HOMEPAGE_URL });
- }, []);
+ const openExternalLink = useExternalLinkOpener();
+ const handleOpenTabBanxaHomepage = () => {
+ openExternalLink(BANXA_HOMEPAGE_URL);
+ };
return (
diff --git a/apps/browser-extension-wallet/src/views/browser-view/routes/index.tsx b/apps/browser-extension-wallet/src/views/browser-view/routes/index.tsx
index a684c9c434..c2c615c3e8 100644
--- a/apps/browser-extension-wallet/src/views/browser-view/routes/index.tsx
+++ b/apps/browser-extension-wallet/src/views/browser-view/routes/index.tsx
@@ -42,6 +42,7 @@ import { Crash } from '@components/Crash';
import { useIsPosthogClientInitialized } from '@providers/PostHogClientProvider/useIsPosthogClientInitialized';
import { logger } from '@lace/common';
import { VotingLayout } from '../features/voting-beta';
+import { catchAndBrandExtensionApiError } from '@utils/catch-and-brand-extension-api-error';
export const defaultRoutes: RouteMap = [
{
@@ -96,13 +97,18 @@ const { CHAIN, GOV_TOOLS_URLS } = config();
* @param {number} currentTabId - Tab not to discard (freeze)
*/
const discardStaleTabs = async (currentTabId: number) => {
- const allTabs = await tabs.query({ title: 'Lace' });
- const namiTabs = await tabs.query({ title: POPUP_WINDOW_NAMI_TITLE });
- allTabs.push(...namiTabs);
+ const allTabs = [
+ ...(await catchAndBrandExtensionApiError(tabs.query({ title: 'Lace' }), 'Failed to query for stale lace tabs')),
+ ...(await catchAndBrandExtensionApiError(
+ tabs.query({ title: POPUP_WINDOW_NAMI_TITLE }),
+ 'Failed to query for stale nami mode tabs'
+ ))
+ ];
const isLaceOrigin = allTabs.find((tab) => tab.id === currentTabId);
if (!isLaceOrigin) return;
- allTabs.forEach(async (tab) => {
- if (currentTabId !== tab.id) await tabs.discard(tab.id);
+ allTabs.forEach((tab) => {
+ if (currentTabId === tab.id) return;
+ void catchAndBrandExtensionApiError(tabs.discard(tab.id), 'Failed to discard stale tab');
});
};
diff --git a/packages/nami/src/ui/app/pages/dapp-connector/signData.tsx b/packages/nami/src/ui/app/pages/dapp-connector/signData.tsx
index f75bf2b317..da0b4832da 100644
--- a/packages/nami/src/ui/app/pages/dapp-connector/signData.tsx
+++ b/packages/nami/src/ui/app/pages/dapp-connector/signData.tsx
@@ -24,6 +24,7 @@ import {
DappConnector,
useDappOutsideHandles,
} from '../../../../features/dapp-outside-handles-provider';
+import { logger } from '@lace/common';
interface Props {
dappConnector: DappConnector;
@@ -87,24 +88,29 @@ export const SignData = ({ dappConnector, account }: Readonly) => {
}
};
+ const cancelTransaction = useCallback(async () => {
+ await request?.reject(() => void 0);
+ window.close();
+ }, [request]);
+
const loadData = async () => {
- const { dappInfo, request } = await dappConnector.getSignDataRequest();
- getPayload(request.data.payload);
- getAddress(request.data.address);
- setDappInfo(dappInfo);
- setRequest(request);
- setIsLoading(false);
+ try {
+ const { dappInfo, request } = await dappConnector.getSignDataRequest();
+ getPayload(request.data.payload);
+ getAddress(request.data.address);
+ setDappInfo(dappInfo);
+ setRequest(request);
+ setIsLoading(false);
+ } catch (error) {
+ logger.error('Failed to get SignData request data', error);
+ void cancelTransaction();
+ }
};
React.useEffect(() => {
loadData();
}, []);
- const cancelTransaction = useCallback(async () => {
- await request?.reject(() => void 0);
- window.close();
- }, [request]);
-
useOnUnload(cancelTransaction);
return (
diff --git a/packages/nami/src/ui/app/pages/dapp-connector/signTx.tsx b/packages/nami/src/ui/app/pages/dapp-connector/signTx.tsx
index a711ec2afe..5bafef0b80 100644
--- a/packages/nami/src/ui/app/pages/dapp-connector/signTx.tsx
+++ b/packages/nami/src/ui/app/pages/dapp-connector/signTx.tsx
@@ -51,6 +51,7 @@ import type { DappConnector } from '../../../../features/dapp-outside-handles-pr
import type { Asset as NamiAsset } from '../../../../types/assets';
import type { AssetsModalRef } from '../../components/assetsModal';
import type { Cardano } from '@cardano-sdk/core';
+import { logger } from '@lace/common';
interface Props {
dappConnector: DappConnector;
@@ -231,10 +232,29 @@ export const SignTx = ({
}
};
+ const cancelTransaction = useCallback(async () => {
+ await request?.reject(() => void 0);
+ window.close();
+ }, [request]);
+
+ useOnUnload(cancelTransaction);
+
const getInfo = async () => {
if (!txWitnessRequest) return;
- const { dappInfo, request } =
- await dappConnector.getSignTxRequest(txWitnessRequest);
+
+ let signTxRequestData: Awaited<
+ ReturnType
+ >;
+ try {
+ signTxRequestData =
+ await dappConnector.getSignTxRequest(txWitnessRequest);
+ } catch (error) {
+ logger.error('Failed to get SignTx request data', error);
+ void cancelTransaction();
+ return;
+ }
+
+ const { dappInfo, request } = signTxRequestData;
setRequest(request);
setDappInfo(dappInfo);
@@ -276,13 +296,6 @@ export const SignTx = ({
getInfo();
}, [txWitnessRequest]);
- const cancelTransaction = useCallback(async () => {
- await request?.reject(() => void 0);
- window.close();
- }, [request]);
-
- useOnUnload(cancelTransaction);
-
return (
<>
{isLoading.loading ? (