From 7f616ec97976f6425b10e3bb14571fad5bd6496b Mon Sep 17 00:00:00 2001 From: Alex Stelea Date: Mon, 29 May 2023 11:39:38 +0100 Subject: [PATCH] feat: handle stale dApp requests --- src/chrome/background/background.ts | 13 ++++++- .../dev-tools/components/LedgerSimulator.tsx | 3 +- src/chrome/messages/_types.ts | 5 +++ src/chrome/messages/create-message.ts | 9 +++++ src/chrome/messages/message-client.spec.ts | 4 +-- src/chrome/offscreen/message-handler.ts | 22 ++++++++++-- src/message-router.ts | 36 ++++++++++++++++--- 7 files changed, 80 insertions(+), 12 deletions(-) diff --git a/src/chrome/background/background.ts b/src/chrome/background/background.ts index 1dd1c7b7..e4b302f3 100644 --- a/src/chrome/background/background.ts +++ b/src/chrome/background/background.ts @@ -61,8 +61,19 @@ const handleConnectionPasswordChange = (connectionPassword?: string) => }, config.popup.closeDelayTime) }) -const tabRemovedListener = (tabId: number) => +const tabRemovedListener = (tabId: number) => { ledgerTabWatcher.triggerTabRemoval(tabId) + messageHandler.sendMessageAndWaitForConfirmation( + createMessage.closeDappTab('background', tabId) + ) +} + +chrome.tabs.onUpdated.addListener((tabId, changeInfo) => { + const isTabReload = changeInfo.status === 'loading' && !changeInfo.url + if (isTabReload) { + tabRemovedListener(tabId) + } +}) chrome.runtime.onMessage.addListener((message, sender) => { messageHandler.onMessage(message, sender.tab?.id) diff --git a/src/chrome/dev-tools/components/LedgerSimulator.tsx b/src/chrome/dev-tools/components/LedgerSimulator.tsx index eb9f6dad..4c56c2f0 100644 --- a/src/chrome/dev-tools/components/LedgerSimulator.tsx +++ b/src/chrome/dev-tools/components/LedgerSimulator.tsx @@ -60,7 +60,8 @@ const signingConfig = { const getLocalStorageMnemonics = () => { try { const mnemonics = - JSON.parse(localStorage.getItem('radix-dev-tools-mnemonics') ?? '[]') || [] + JSON.parse(localStorage.getItem('radix-dev-tools-mnemonics') ?? '[]') || + [] return mnemonics } catch (e) { return [] diff --git a/src/chrome/messages/_types.ts b/src/chrome/messages/_types.ts index 3ea9a1a1..ca69c77a 100644 --- a/src/chrome/messages/_types.ts +++ b/src/chrome/messages/_types.ts @@ -7,6 +7,7 @@ export const messageDiscriminator = { setConnectionPassword: 'setConnectionPassword', dAppRequest: 'dAppRequest', closeLedgerTab: 'closeLedgerTab', + closeDappTab: 'closeDappTab', ledgerResponse: 'ledgerResponse', walletToLedger: 'walletToLedger', convertPopupToTab: 'convertPopupToTab', @@ -107,6 +108,10 @@ export type Messages = { MessageDiscriminator['closeLedgerTab'], {} > + [messageDiscriminator.closeDappTab]: MessageBuilder< + MessageDiscriminator['closeDappTab'], + { tabId: number } + > [messageDiscriminator.incomingDappMessage]: MessageBuilder< MessageDiscriminator['incomingDappMessage'], { data: Record } diff --git a/src/chrome/messages/create-message.ts b/src/chrome/messages/create-message.ts index c91a822e..79931717 100644 --- a/src/chrome/messages/create-message.ts +++ b/src/chrome/messages/create-message.ts @@ -51,6 +51,15 @@ export const createMessage = { discriminator: 'closeLedgerTab', messageId: crypto.randomUUID(), }), + closeDappTab: ( + source: MessageSource, + tabId: number + ): Messages['closeDappTab'] => ({ + source, + tabId, + discriminator: 'closeDappTab', + messageId: crypto.randomUUID(), + }), walletToLedger: ( source: MessageSource, message: LedgerRequest diff --git a/src/chrome/messages/message-client.spec.ts b/src/chrome/messages/message-client.spec.ts index 94e80fe9..92b0ba9f 100644 --- a/src/chrome/messages/message-client.spec.ts +++ b/src/chrome/messages/message-client.spec.ts @@ -139,7 +139,7 @@ describe('message client', () => { // so it has to proxy the message through background message handler it('should send wallet response to dApp', async () => { const testHelper = createTestHelper({}) - testHelper.messageRouter.add(1, '456') + testHelper.messageRouter.add(1, '456', '') testHelper.mockIncomingWalletMessage({ interactionId: '456' }, 1) await Promise.all([ @@ -192,7 +192,7 @@ describe('message client', () => { ), }) - testHelper.messageRouter.add(1, '456') + testHelper.messageRouter.add(1, '456', '') testHelper.mockIncomingWalletMessage({ interactionId: '456' }, 1) await Promise.all([ diff --git a/src/chrome/offscreen/message-handler.ts b/src/chrome/offscreen/message-handler.ts index b812c95d..5bd82440 100644 --- a/src/chrome/offscreen/message-handler.ts +++ b/src/chrome/offscreen/message-handler.ts @@ -1,7 +1,7 @@ import { createMessage } from 'chrome/messages/create-message' import { ConnectorClient } from 'connector/connector-client' import { MessagesRouter } from 'message-router' -import { errAsync, okAsync } from 'neverthrow' +import { ResultAsync, errAsync, okAsync } from 'neverthrow' import { Queue } from 'queues/queue' import { AppLogger, logger as appLogger } from 'utils/logger' import { @@ -64,9 +64,9 @@ export const OffscreenMessageHandler = (input: { } case messageDiscriminator.dAppRequest: { - const { interactionId } = message.data + const { interactionId, metadata } = message.data return messageRouter - .add(tabId!, interactionId) + .add(tabId!, interactionId, metadata.origin) .asyncAndThen(() => { if (message.data?.items?.discriminator === 'cancelRequest') return dAppRequestQueue @@ -104,6 +104,22 @@ export const OffscreenMessageHandler = (input: { .add(message.data, message.data.interactionId) .map(() => ({ sendConfirmation: false })) + case messageDiscriminator.closeDappTab: { + const { tabId } = message + return messageRouter + .getAndRemoveByTabId(tabId) + .mapErr(() => ({ reason: 'tabIdNotFound' })) + .map((interactionIds) => { + for (const interactionId of interactionIds) { + ResultAsync.combine([ + dAppRequestQueue.cancel(interactionId), + incomingWalletMessageQueue.cancel(interactionId), + ]) + } + }) + .map(() => ({ sendConfirmation: false })) + } + default: return errAsync({ reason: 'unhandledMessageDiscriminator', diff --git a/src/message-router.ts b/src/message-router.ts index fd2b44fd..00ee2eb8 100644 --- a/src/message-router.ts +++ b/src/message-router.ts @@ -4,21 +4,47 @@ import { AppLogger } from 'utils/logger' export type MessagesRouter = ReturnType export const MessagesRouter = ({ logger }: { logger: AppLogger }) => { - const store = new Map() + const store = new Map() - const add = (tabId: number, interactionId: string) => { - store.set(interactionId, tabId) + const add = (tabId: number, interactionId: string, origin: string) => { + store.set(interactionId, { tabId, origin }) return ok(undefined) } const getTabId = (interactionId: string): ResultAsync => { - const tabId = store.get(interactionId) - return tabId ? okAsync(tabId) : errAsync(new Error('No tab found')) + const values = store.get(interactionId) + return values ? okAsync(values.tabId) : errAsync(new Error('No tab found')) } + const getInteractionIdsByTabId = ( + tabId: number + ): ResultAsync => { + const interactionIds = [...store.entries()] + .filter(([, value]) => value.tabId === tabId) + .map(([key]) => key) + + return interactionIds.length + ? okAsync(interactionIds) + : errAsync(new Error('No interactionId found')) + } + + const removeByTabId = (tabId: number) => + getInteractionIdsByTabId(tabId).map((interactionIds) => + interactionIds.forEach((interactionId) => store.delete(interactionId)) + ) + + const getAndRemoveByTabId = (tabId: number) => + getInteractionIdsByTabId(tabId).map((interactionIds) => { + interactionIds.forEach((interactionId) => store.delete(interactionId)) + return interactionIds + }) + return { add, getTabId, store, + getInteractionIdsByTabId, + removeByTabId, + getAndRemoveByTabId, } }