Skip to content

Commit

Permalink
refactor: improve error handling of extension tab api interactions
Browse files Browse the repository at this point in the history
  • Loading branch information
szymonmaslowski committed Feb 20, 2025
1 parent 958032a commit ca1cf40
Show file tree
Hide file tree
Showing 18 changed files with 240 additions and 89 deletions.
19 changes: 13 additions & 6 deletions apps/browser-extension-wallet/src/components/Layout/MainLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useNetworkError } from '@hooks/useNetworkError';
import { Announcement } from '@components/Announcement/Announcement';
import { storage } from 'webextension-polyfill';
import { ABOUT_EXTENSION_KEY, ExtensionUpdateData } from '@lib/scripts/types';
import { brandPotentialExtApiError } from '@utils/brand-potential-ext-api-error';

interface MainLayoutProps {
children: React.ReactNode;
Expand Down Expand Up @@ -44,19 +45,25 @@ export const MainLayout = ({
useNetworkError(showNetworkError);

const getAboutExtensionData = useCallback(async () => {
const data = await storage.local.get(ABOUT_EXTENSION_KEY);
const data = await brandPotentialExtApiError(
storage.local.get(ABOUT_EXTENSION_KEY),
'Failed to get "about extension" data'
);
setAboutExtension(data?.[ABOUT_EXTENSION_KEY] || {});
}, []);

useEffect(() => {
getAboutExtensionData();
}, [getAboutExtensionData]);

const onUpdateAknowledge = useCallback(async () => {
const onUpdateAcknowledge = useCallback(async () => {
const data = { version, acknowledged: true, reason };
await storage.local.set({
[ABOUT_EXTENSION_KEY]: data
});
await brandPotentialExtApiError(
storage.local.set({
[ABOUT_EXTENSION_KEY]: data
}),
'Failed update "about extension" data'
);
setAboutExtension(data);
}, [reason, version]);

Expand All @@ -72,7 +79,7 @@ export const MainLayout = ({
</div>
<Announcement
visible={showAnnouncement && version && !acknowledged}
onConfirm={onUpdateAknowledge}
onConfirm={onUpdateAcknowledge}
version={version}
reason={reason}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,15 @@ export const DappConfirmData = (): React.ReactElement => {

useEffect(() => {
const subscription = signingCoordinator.signDataRequest$.pipe(take(1)).subscribe(async (r) => {
setDappInfo(await senderToDappInfo(r.signContext.sender));
try {
setDappInfo(await senderToDappInfo(r.signContext.sender));
} catch (error) {
logger.error(error);
void cancelTransaction();
redirectToSignFailure();
return;
}

setSignDataRequest(r);
});

Expand All @@ -86,7 +94,7 @@ export const DappConfirmData = (): React.ReactElement => {
subscription.unsubscribe();
api.shutdown();
};
}, [setSignDataRequest]);
}, [cancelTransaction, redirectToSignFailure, setSignDataRequest]);

useEffect(() => {
if (!req) return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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);
Expand All @@ -51,9 +54,17 @@ export const ConfirmTransaction = (): React.ReactElement => {

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);
redirectToDappTxSignFailure();
return emptyFn;
}

setDappInfo(await senderToDappInfo(txWitnessRequest.signContext.sender));
setSignTxRequest(txWitnessRequest);

const api = exposeApi<Pick<UserPromptService, 'readyToSignTx'>>(
Expand All @@ -73,7 +84,7 @@ export const ConfirmTransaction = (): React.ReactElement => {
api.shutdown();
};
})();
}, [setSignTxRequest, setDappInfo, txWitnessRequest]);
}, [setSignTxRequest, setDappInfo, txWitnessRequest, redirectToDappTxSignFailure]);

const onCancelTransaction = () => {
analytics.sendEventToPostHog(PostHogAction.SendTransactionSummaryCancelClick, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 { brandPotentialExtApiError } from '@utils/brand-potential-ext-api-error';

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

Expand Down Expand Up @@ -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 }) =>
brandPotentialExtApiError(
tabs.create({ url }),
`[NAMI MIGRATION] laceMigrationOpenListener failed to create tab with url ${url}`,
{
reThrow: false
}
)
)
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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` });
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Wallet.DappInfo>(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') {
Expand All @@ -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 });
Original file line number Diff line number Diff line change
Expand Up @@ -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 { brandPotentialExtApiError } from '@utils/brand-potential-ext-api-error';

export const requestMessage$ = new Subject<Message>();
export const backendFailures$ = new BehaviorSubject(0);
Expand Down Expand Up @@ -103,17 +104,24 @@ 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 brandPotentialExtApiError(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 brandPotentialExtApiError(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 brandPotentialExtApiError(tabs.query({ windowId: w.id }), 'Failed to query tabs of a window'))
}));
return Promise.all(promises);
};
Expand All @@ -129,7 +137,8 @@ const doesWindowHaveOtherTabs = (browserWindow: WindowWithTabsNotOptional) =>

const closeAllTabsAndOpenPopup = async () => {
try {
const allWindows = await enrichWithTabsDataIfMissing(await windows.getAll());
const allWindowsRaw = await brandPotentialExtApiError(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));
Expand All @@ -141,14 +150,16 @@ const closeAllTabsAndOpenPopup = async () => {
const noSingleWindowWith3rdPartyTabsOpen = !nextFocusedWindow;
if (noSingleWindowWith3rdPartyTabsOpen) {
nextFocusedWindow = allWindows[0];
await tabs.create({ active: true, windowId: nextFocusedWindow.id });
await brandPotentialExtApiError(
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 brandPotentialExtApiError(windows.update(nextFocusedWindow.id, { focused: true }), 'Failed to focus window');
await closeAllLaceOrNamiTabs();
await action.openPopup();
await brandPotentialExtApiError(action.openPopup(), 'Failed to open popup');
} catch (error) {
// unable to programatically open the popup again
logger.error(error);
}
};
Expand Down
Original file line number Diff line number Diff line change
@@ -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 { brandPotentialExtApiError } from '@utils/brand-potential-ext-api-error';

type WindowId = number;

Expand All @@ -21,10 +22,13 @@ const tabActivated$ = fromEventPattern<Tabs.OnActivatedActiveInfoType>(
export const isLaceTabActive$ = merge(windowRemoved$, tabUpdated$, tabActivated$).pipe(
switchMap(() =>
from(
tabs.query({
active: true,
url: `chrome-extension://${LACE_EXTENSION_ID}/*`
})
brandPotentialExtApiError(
tabs.query({
active: true,
url: `chrome-extension://${LACE_EXTENSION_ID}/*`
}),
'Failed to query for currently active lace tab'
)
)
),
map((activeLaceTabs) => activeLaceTabs.length > 0),
Expand Down
31 changes: 19 additions & 12 deletions apps/browser-extension-wallet/src/lib/scripts/background/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
WalletType
} from '@cardano-sdk/web-extension';
import { getBackgroundStorage } from './storage';
import { brandPotentialExtApiError } from '@utils/brand-potential-ext-api-error';

const { blake2b } = Wallet.Crypto;
const DAPP_CONNECTOR_REGEX = new RegExp(/dappconnector/i);
Expand Down Expand Up @@ -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,
Expand All @@ -74,7 +68,14 @@ const createWindow = (
*/
export const launchCip30Popup = async (url: string): Promise<Tabs.Tab> => {
const currentWindow = await windows.getCurrent();
const tab = await createTab(`../dappConnector.html${url}`, false);
const tab = await brandPotentialExtApiError(
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;

Expand Down Expand Up @@ -125,12 +126,18 @@ export const getActiveWallet = async ({
};

export const closeAllLaceOrNamiTabs = async (shouldRemoveTab?: (url: string) => boolean): Promise<void> => {
const openTabs = await tabs.query({ title: 'Lace' });
const namiTabs = await tabs.query({ title: POPUP_WINDOW_NAMI_TITLE });
openTabs.push(...namiTabs);
const openTabs = [
...(await brandPotentialExtApiError(tabs.query({ title: 'Lace' }), 'Failed to query lace tabs for closing')),
...(await brandPotentialExtApiError(
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 brandPotentialExtApiError(tabs.remove(tab.id), `Failed to close tab with url ${tab.url}`);
}
}
};

Expand Down
Loading

0 comments on commit ca1cf40

Please sign in to comment.