Skip to content

Commit b4a5351

Browse files
refactor: improve error handling of extension tab api interactions
1 parent 958032a commit b4a5351

File tree

20 files changed

+255
-105
lines changed

20 files changed

+255
-105
lines changed

apps/browser-extension-wallet/src/components/Layout/MainLayout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export const MainLayout = ({
5252
getAboutExtensionData();
5353
}, [getAboutExtensionData]);
5454

55-
const onUpdateAknowledge = useCallback(async () => {
55+
const onUpdateAcknowledge = useCallback(async () => {
5656
const data = { version, acknowledged: true, reason };
5757
await storage.local.set({
5858
[ABOUT_EXTENSION_KEY]: data
@@ -72,7 +72,7 @@ export const MainLayout = ({
7272
</div>
7373
<Announcement
7474
visible={showAnnouncement && version && !acknowledged}
75-
onConfirm={onUpdateAknowledge}
75+
onConfirm={onUpdateAcknowledge}
7676
version={version}
7777
reason={reason}
7878
/>

apps/browser-extension-wallet/src/features/dapp/components/ConfirmData.tsx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,27 @@ export const DappConfirmData = (): React.ReactElement => {
5656
dataToSign: string;
5757
}>();
5858

59-
const cancelTransaction = useCallback(async () => {
60-
await req.reject('User rejected to sign');
61-
window.close();
62-
}, [req]);
59+
const cancelTransaction = useCallback(
60+
async (reason = 'User rejected to sign') => {
61+
await req.reject(reason);
62+
window.close();
63+
},
64+
[req]
65+
);
6366

6467
useOnUnload(cancelTransaction);
6568

6669
useEffect(() => {
6770
const subscription = signingCoordinator.signDataRequest$.pipe(take(1)).subscribe(async (r) => {
68-
setDappInfo(await senderToDappInfo(r.signContext.sender));
71+
try {
72+
setDappInfo(await senderToDappInfo(r.signContext.sender));
73+
} catch (error) {
74+
logger.error(error);
75+
void cancelTransaction('Could not get DApp info');
76+
redirectToSignFailure();
77+
return;
78+
}
79+
6980
setSignDataRequest(r);
7081
});
7182

@@ -86,7 +97,7 @@ export const DappConfirmData = (): React.ReactElement => {
8697
subscription.unsubscribe();
8798
api.shutdown();
8899
};
89-
}, [setSignDataRequest]);
100+
}, [cancelTransaction, redirectToSignFailure, setSignDataRequest]);
90101

91102
useEffect(() => {
92103
if (!req) return;

apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/ConfirmTransaction.tsx

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import { runtime } from 'webextension-polyfill';
1919
import { Skeleton } from 'antd';
2020
import { DappTransactionContainer } from './DappTransactionContainer';
2121
import { useTxWitnessRequest } from '@providers/TxWitnessRequestProvider';
22+
import { useRedirection } from '@hooks';
23+
import { dAppRoutePaths } from '@routes';
2224

2325
export const ConfirmTransaction = (): React.ReactElement => {
2426
const { t } = useTranslation();
@@ -28,6 +30,7 @@ export const ConfirmTransaction = (): React.ReactElement => {
2830
signTxRequest: { request: req, set: setSignTxRequest }
2931
} = useViewsFlowContext();
3032
const { walletType, isHardwareWallet, walletInfo, inMemoryWallet } = useWalletStore();
33+
const redirectToDappTxSignFailure = useRedirection(dAppRoutePaths.dappTxSignFailure);
3134
const analytics = useAnalyticsContext();
3235
const [confirmTransactionError] = useState(false);
3336
const disallowSignTx = useDisallowSignTx(req);
@@ -49,11 +52,26 @@ export const ConfirmTransaction = (): React.ReactElement => {
4952

5053
const txWitnessRequest = useTxWitnessRequest();
5154

55+
const cancelTransaction = useCallback(() => {
56+
disallowSignTx(true);
57+
}, [disallowSignTx]);
58+
59+
useOnUnload(cancelTransaction);
60+
5261
useEffect(() => {
5362
(async () => {
54-
if (!txWitnessRequest) return (): (() => void) => void 0;
63+
const emptyFn = (): void => void 0;
64+
if (!txWitnessRequest) return emptyFn;
65+
66+
try {
67+
setDappInfo(await senderToDappInfo(txWitnessRequest.signContext.sender));
68+
} catch (error) {
69+
logger.error(error);
70+
void disallowSignTx(true, 'Could not get DApp info');
71+
redirectToDappTxSignFailure();
72+
return emptyFn;
73+
}
5574

56-
setDappInfo(await senderToDappInfo(txWitnessRequest.signContext.sender));
5775
setSignTxRequest(txWitnessRequest);
5876

5977
const api = exposeApi<Pick<UserPromptService, 'readyToSignTx'>>(
@@ -73,7 +91,7 @@ export const ConfirmTransaction = (): React.ReactElement => {
7391
api.shutdown();
7492
};
7593
})();
76-
}, [setSignTxRequest, setDappInfo, txWitnessRequest]);
94+
}, [setSignTxRequest, setDappInfo, txWitnessRequest, redirectToDappTxSignFailure, disallowSignTx]);
7795

7896
const onCancelTransaction = () => {
7997
analytics.sendEventToPostHog(PostHogAction.SendTransactionSummaryCancelClick, {
@@ -82,12 +100,6 @@ export const ConfirmTransaction = (): React.ReactElement => {
82100
disallowSignTx(true);
83101
};
84102

85-
const cancelTransaction = useCallback(() => {
86-
disallowSignTx(true);
87-
}, [disallowSignTx]);
88-
89-
useOnUnload(cancelTransaction);
90-
91103
return (
92104
<Layout layoutClassname={cn(confirmTransactionError && styles.layoutError)} pageClassname={styles.spaceBetween}>
93105
{req && walletInfo && inMemoryWallet ? <DappTransactionContainer /> : <Skeleton loading />}

apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/hooks.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@ export const useCreateMintedAssetList = ({
151151

152152
export const useDisallowSignTx = (
153153
req: TransactionWitnessRequest<Wallet.WalletMetadata, Wallet.AccountMetadata>
154-
): ((close?: boolean) => Promise<void>) => useCallback(async (close) => await disallowSignTx(req, close), [req]);
154+
): ((close?: boolean, reason?: string) => Promise<void>) =>
155+
useCallback(async (close, reason) => await disallowSignTx(req, close, reason), [req]);
155156

156157
export const useAllowSignTx = (
157158
req: TransactionWitnessRequest<Wallet.WalletMetadata, Wallet.AccountMetadata>

apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/utils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ export const readyToSign = (): void => {
2727

2828
export const disallowSignTx = async (
2929
req: TransactionWitnessRequest<Wallet.WalletMetadata, Wallet.AccountMetadata>,
30-
close = false
30+
close = false,
31+
reason = 'User declined to sign'
3132
): Promise<void> => {
3233
try {
33-
await req?.reject('User declined to sign');
34+
await req?.reject(reason);
3435
} finally {
3536
close && window.close();
3637
}

apps/browser-extension-wallet/src/features/nami-migration/migration-tool/cross-extension-messaging/lace-migration-client.extension.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { NAMI_EXTENSION_ID } from './lace/environment';
77
import { createLaceMigrationOpenListener } from './lace/create-lace-migration-open-listener';
88
import { LACE_EXTENSION_ID } from './nami/environment';
99
import { logger } from '@lace/common';
10+
import { brandPotentialExtApiError } from '@utils/brand-potential-ext-api-error';
1011

1112
type CheckMigrationStatus = () => Promise<MigrationState>;
1213

@@ -42,6 +43,14 @@ export const handleNamiRequests = (): void => {
4243
runtime.onMessageExternal.addListener(createLaceMigrationPingListener(NAMI_EXTENSION_ID));
4344
logger.debug('[NAMI MIGRATION] createLaceMigrationOpenListener');
4445
runtime.onMessageExternal.addListener(
45-
createLaceMigrationOpenListener(NAMI_EXTENSION_ID, LACE_EXTENSION_ID, tabs.create)
46+
createLaceMigrationOpenListener(NAMI_EXTENSION_ID, LACE_EXTENSION_ID, ({ url }) =>
47+
brandPotentialExtApiError(
48+
tabs.create({ url }),
49+
`[NAMI MIGRATION] laceMigrationOpenListener failed to create tab with url ${url}`,
50+
{
51+
reThrow: false
52+
}
53+
)
54+
)
4655
);
4756
};

apps/browser-extension-wallet/src/features/nami-migration/migration-tool/cross-extension-messaging/lace/create-lace-migration-open-listener.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ export const createLaceMigrationOpenListener =
88
logger.debug('[NAMI MIGRATION] createLaceMigrationOpenListener', message, sender);
99
if (message === NamiMessages.open && sender.id === namiExtensionId) {
1010
// First close all open lace tabs
11-
await closeAllLaceOrNamiTabs();
11+
try {
12+
await closeAllLaceOrNamiTabs();
13+
} catch (error) {
14+
logger.error('[NAMI MIGRATION] createLaceMigrationOpenListener: failed to close all windows', error);
15+
}
1216
createTab({ url: `chrome-extension://${laceExtensionId}/app.html` });
1317
}
1418
};

apps/browser-extension-wallet/src/lib/scripts/background/cip30.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export const confirmationCallback: walletCip30.CallbackConfirmation = {
4747
return cancelOnTabClose(tab);
4848
} catch (error) {
4949
logger.error(error);
50-
return Promise.reject(new ApiError(APIErrorCode.InternalError, 'Unable to sign transaction'));
50+
throw new ApiError(APIErrorCode.InternalError, 'Unable to sign transaction');
5151
}
5252
},
5353
DEBOUNCE_THROTTLE,
@@ -62,8 +62,7 @@ export const confirmationCallback: walletCip30.CallbackConfirmation = {
6262
return cancelOnTabClose(tab);
6363
} catch (error) {
6464
logger.error(error);
65-
// eslint-disable-next-line unicorn/no-useless-undefined
66-
return Promise.reject(new ApiError(APIErrorCode.InternalError, 'Unable to sign data'));
65+
throw new ApiError(APIErrorCode.InternalError, 'Unable to sign data');
6766
}
6867
},
6968
DEBOUNCE_THROTTLE,

apps/browser-extension-wallet/src/lib/scripts/background/requestAccess.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,31 @@ import { AUTHORIZED_DAPPS_KEY } from '../types';
1010
import { Wallet } from '@lace/cardano';
1111
import { BehaviorSubject } from 'rxjs';
1212
import { senderToDappInfo } from '@src/utils/senderToDappInfo';
13+
import { logger } from '@lace/common';
1314

1415
const DEBOUNCE_THROTTLE = 500;
1516

1617
export const dappInfo$ = new BehaviorSubject<Wallet.DappInfo>(undefined);
1718

1819
export const requestAccess: RequestAccess = async (sender: Runtime.MessageSender) => {
19-
const { logo, name, url } = await senderToDappInfo(sender);
20+
let dappInfo: Wallet.DappInfo;
21+
try {
22+
dappInfo = await senderToDappInfo(sender);
23+
} catch (error) {
24+
logger.error('Failed to get info of a DApp requesting access', error);
25+
return false;
26+
}
27+
28+
const { logo, name, url } = dappInfo;
2029
dappInfo$.next({ logo, name, url });
21-
await ensureUiIsOpenAndLoaded('#/dapp/connect');
30+
31+
try {
32+
await ensureUiIsOpenAndLoaded('#/dapp/connect');
33+
} catch (error) {
34+
logger.error('Failed to ensure DApp connection UI is loaded', error);
35+
return false;
36+
}
37+
2238
const isAllowed = await userPromptService.allowOrigin(url);
2339
if (isAllowed === 'deny') return Promise.reject();
2440
if (isAllowed === 'allow') {
@@ -31,14 +47,16 @@ export const requestAccess: RequestAccess = async (sender: Runtime.MessageSender
3147
authorizedDappsList.next([{ logo, name, url }]);
3248
}
3349
} else {
34-
tabs.onRemoved.addListener((t) => {
35-
if (t === sender.tab.id) {
50+
const onRemovedHandler = (tabId: number) => {
51+
if (tabId === sender.tab.id) {
3652
authenticator.revokeAccess(sender);
37-
tabs.onRemoved.removeListener(this);
53+
tabs.onRemoved.removeListener(onRemovedHandler);
3854
}
39-
});
55+
};
56+
tabs.onRemoved.addListener(onRemovedHandler);
4057
}
41-
return Promise.resolve(true);
58+
59+
return true;
4260
};
4361

4462
export const requestAccessDebounced = pDebounce(requestAccess, DEBOUNCE_THROTTLE, { before: true });

apps/browser-extension-wallet/src/lib/scripts/background/services/utilityServices.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { laceFeaturesApiProperties, LACE_FEATURES_CHANNEL } from '../injectUtil'
2929
import { getErrorMessage } from '@src/utils/get-error-message';
3030
import { logger } from '@lace/common';
3131
import { POPUP_WINDOW_NAMI_TITLE } from '@utils/constants';
32+
import { brandPotentialExtApiError } from '@utils/brand-potential-ext-api-error';
3233

3334
export const requestMessage$ = new Subject<Message>();
3435
export const backendFailures$ = new BehaviorSubject(0);
@@ -103,17 +104,24 @@ const handleOpenBrowser = async (data: OpenBrowserData) => {
103104
break;
104105
}
105106
const params = data.urlSearchParams ? `?${data.urlSearchParams}` : '';
106-
await tabs.create({ url: `app.html#${path}${params}` }).catch((error) => logger.error(error));
107+
const url = `app.html#${path}${params}`;
108+
await brandPotentialExtApiError(tabs.create({ url }), `Failed to open expanded view with url: ${url}`).catch(
109+
(error) => logger.error(error)
110+
);
107111
};
108112

109113
const handleOpenNamiBrowser = async (data: OpenNamiBrowserData) => {
110-
await tabs.create({ url: `popup.html#${data.path}` }).catch((error) => logger.error(error));
114+
const url = `popup.html#${data.path}`;
115+
await brandPotentialExtApiError(tabs.create({ url }), `Failed to open nami mode extended with url: ${url}`).catch(
116+
(error) => logger.error(error)
117+
);
111118
};
112119

113120
const enrichWithTabsDataIfMissing = (browserWindows: Windows.Window[]) => {
114121
const promises = browserWindows.map(async (w) => ({
115122
...w,
116-
tabs: w.tabs || (await tabs.query({ windowId: w.id }))
123+
tabs:
124+
w.tabs || (await brandPotentialExtApiError(tabs.query({ windowId: w.id }), 'Failed to query tabs of a window'))
117125
}));
118126
return Promise.all(promises);
119127
};
@@ -129,7 +137,8 @@ const doesWindowHaveOtherTabs = (browserWindow: WindowWithTabsNotOptional) =>
129137

130138
const closeAllTabsAndOpenPopup = async () => {
131139
try {
132-
const allWindows = await enrichWithTabsDataIfMissing(await windows.getAll());
140+
const allWindowsRaw = await brandPotentialExtApiError(windows.getAll(), 'Failed to query all browser windows');
141+
const allWindows = await enrichWithTabsDataIfMissing(allWindowsRaw);
133142
if (allWindows.length === 0) return;
134143

135144
const windowsWith3rdPartyTabs = allWindows.filter((w) => doesWindowHaveOtherTabs(w));
@@ -141,14 +150,16 @@ const closeAllTabsAndOpenPopup = async () => {
141150
const noSingleWindowWith3rdPartyTabsOpen = !nextFocusedWindow;
142151
if (noSingleWindowWith3rdPartyTabsOpen) {
143152
nextFocusedWindow = allWindows[0];
144-
await tabs.create({ active: true, windowId: nextFocusedWindow.id });
153+
await brandPotentialExtApiError(
154+
tabs.create({ active: true, windowId: nextFocusedWindow.id }),
155+
'Failed to open empty tab to prevent window from closing'
156+
);
145157
}
146158

147-
await windows.update(nextFocusedWindow.id, { focused: true });
159+
await brandPotentialExtApiError(windows.update(nextFocusedWindow.id, { focused: true }), 'Failed to focus window');
148160
await closeAllLaceOrNamiTabs();
149-
await action.openPopup();
161+
await brandPotentialExtApiError(action.openPopup(), 'Failed to open popup');
150162
} catch (error) {
151-
// unable to programatically open the popup again
152163
logger.error(error);
153164
}
154165
};

0 commit comments

Comments
 (0)