From f60926bc2c50a340a052038f38faa23d554fde4b Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 7 Jan 2025 17:02:03 -0500 Subject: [PATCH] Move a few listeners to background script top-level/first event loop This silences warnings about navigator.usb listeners, but also fixes a potential issue where the onConnect listeners might be attached in such a way as to remove the ability for the service worker to be woken up when a page connects to it. --- background/services/ledger/index.ts | 10 ++- background/services/provider-bridge/index.ts | 82 ++++++++++---------- background/services/redux/index.ts | 52 ++++++------- src/background.ts | 18 ++--- 4 files changed, 81 insertions(+), 81 deletions(-) diff --git a/background/services/ledger/index.ts b/background/services/ledger/index.ts index 0eebbfbb9..8fe49d1ed 100644 --- a/background/services/ledger/index.ts +++ b/background/services/ledger/index.ts @@ -180,6 +180,13 @@ export default class LedgerService extends BaseService { private constructor(private db: LedgerDatabase) { super() + + navigator.usb.addEventListener("connect", this.#handleUSBConnect) + navigator.usb.addEventListener("disconnect", this.#handleUSBDisconnect) + + // Block serial oprations until the service is started, in case a + // connection or disconnection event occurs to soon. + this.#lastOperationPromise = this.started().then(() => {}) } private runSerialized(operation: () => Promise) { @@ -289,9 +296,6 @@ export default class LedgerService extends BaseService { await super.internalStartService() // Not needed, but better to stick to the patterns this.refreshConnectedLedger() - - navigator.usb.addEventListener("connect", this.#handleUSBConnect) - navigator.usb.addEventListener("disconnect", this.#handleUSBDisconnect) } protected override async internalStopService(): Promise { diff --git a/background/services/provider-bridge/index.ts b/background/services/provider-bridge/index.ts index 9511a8e8a..b6d692d75 100644 --- a/background/services/provider-bridge/index.ts +++ b/background/services/provider-bridge/index.ts @@ -99,48 +99,6 @@ export default class ProviderBridgeService extends BaseService { private preferenceService: PreferenceService, ) { super() - - browser.runtime.onConnect.addListener(async (port) => { - if (port.name === EXTERNAL_PORT_NAME && port.sender?.url) { - port.onMessage.addListener((event) => { - if ( - !event || - typeof event !== "object" || - !("id" in event) || - typeof event.id !== "string" || - !("request" in event) || - typeof event.request !== "object" - ) { - logger.error("Unexpected event on port", event) - return - } - - this.onMessageListener( - port as Required, - event as PortRequestEvent, - ) - }) - port.onDisconnect.addListener(() => { - this.openPorts = this.openPorts.filter( - (openPort) => openPort !== port, - ) - }) - this.openPorts.push(port) - - // we need to send this info ASAP so it arrives before the webpage is initializing - // so we can set our provider into the correct state, BEFORE the page has a chance to - // to cache it, store it etc. - port.postMessage({ - id: "tallyHo", - jsonrpc: "2.0", - result: { - method: "tally_getConfig", - defaultWallet: await this.preferenceService.getDefaultWallet(), - }, - }) - } - // TODO: on internal provider handlers connect, disconnect, account change, network change - }) } protected override async internalStartService(): Promise { @@ -692,4 +650,44 @@ export default class ProviderBridgeService extends BaseService { request.reject() } } + + async connectPort(port: Runtime.Port) { + if (port.name === EXTERNAL_PORT_NAME && port.sender?.url) { + port.onMessage.addListener((event) => { + if ( + !event || + typeof event !== "object" || + !("id" in event) || + typeof event.id !== "string" || + !("request" in event) || + typeof event.request !== "object" + ) { + logger.error("Unexpected event on port", event) + return + } + + this.onMessageListener( + port as Required, + event as PortRequestEvent, + ) + }) + port.onDisconnect.addListener(() => { + this.openPorts = this.openPorts.filter((openPort) => openPort !== port) + }) + this.openPorts.push(port) + + // we need to send this info ASAP so it arrives before the webpage is initializing + // so we can set our provider into the correct state, BEFORE the page has a chance to + // to cache it, store it etc. + port.postMessage({ + id: "tallyHo", + jsonrpc: "2.0", + result: { + method: "tally_getConfig", + defaultWallet: await this.preferenceService.getDefaultWallet(), + }, + }) + } + // TODO: on internal provider handlers connect, disconnect, account change, network change + } } diff --git a/background/services/redux/index.ts b/background/services/redux/index.ts index 25fe831cb..db45dced1 100644 --- a/background/services/redux/index.ts +++ b/background/services/redux/index.ts @@ -1,4 +1,4 @@ -import { runtime } from "webextension-polyfill" +import { Runtime } from "webextension-polyfill" import { PermissionRequest } from "@tallyho/provider-bridge-shared" import { utils } from "ethers" @@ -462,8 +462,6 @@ export default class ReduxService extends BaseService { ) this.store.dispatch(clearApprovalInProgress()) - - this.connectPopupMonitor() } async addAccount(addressNetwork: AddressOnNetwork): Promise { @@ -633,12 +631,9 @@ export default class ReduxService extends BaseService { // Set up initial state. const existingAccounts = await this.chainService.getAccountsToTrack() - existingAccounts.forEach(async (addressNetwork) => { + existingAccounts.forEach((addressNetwork) => { // Mark as loading and wire things up. this.store.dispatch(loadAccount(addressNetwork)) - - // Force a refresh of the account balance to populate the store. - this.chainService.getLatestBaseAccountBalance(addressNetwork) }) // Set up Island Monitoring @@ -1845,30 +1840,33 @@ export default class ReduxService extends BaseService { return this.indexingService.importCustomToken(asset) } - private connectPopupMonitor() { - runtime.onConnect.addListener((port) => { - if (port.name !== POPUP_MONITOR_PORT_NAME) return + async connectPort(port: Runtime.Port) { + this.connectPopupMonitor(port) + this.providerBridgeService.connectPort(port) + } - const openTime = Date.now() + private connectPopupMonitor(port: Runtime.Port) { + if (port.name !== POPUP_MONITOR_PORT_NAME) return - const originalNetworkName = - this.store.getState().ui.selectedAccount.network.name + const openTime = Date.now() - port.onDisconnect.addListener(() => { - const networkNameAtClose = - this.store.getState().ui.selectedAccount.network.name - this.analyticsService.sendAnalyticsEvent(AnalyticsEvent.UI_SHOWN, { - openTime: new Date(openTime).toISOString(), - closeTime: new Date().toISOString(), - openLength: (Date.now() - openTime) / 1e3, - networkName: - originalNetworkName === networkNameAtClose - ? originalNetworkName - : "switched networks", - unit: "s", - }) - this.onPopupDisconnected() + const originalNetworkName = + this.store.getState().ui.selectedAccount.network.name + + port.onDisconnect.addListener(() => { + const networkNameAtClose = + this.store.getState().ui.selectedAccount.network.name + this.analyticsService.sendAnalyticsEvent(AnalyticsEvent.UI_SHOWN, { + openTime: new Date(openTime).toISOString(), + closeTime: new Date().toISOString(), + openLength: (Date.now() - openTime) / 1e3, + networkName: + originalNetworkName === networkNameAtClose + ? originalNetworkName + : "switched networks", + unit: "s", }) + this.onPopupDisconnected() }) } diff --git a/src/background.ts b/src/background.ts index 219f1f506..ee4dac87f 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,10 +1,10 @@ import { browser, startRedux } from "@tallyho/tally-background" -import { SECOND } from "@tallyho/tally-background/constants" import { FeatureFlags, isEnabled, RuntimeFlag, } from "@tallyho/tally-background/features" +import ReduxService from "@tallyho/tally-background/services/redux" import { ONBOARDING_ROOT } from "@tallyho/tally-ui/pages/Onboarding/Tabbed/Routes" browser.runtime.onInstalled.addListener((obj) => { @@ -21,17 +21,17 @@ browser.runtime.onInstalled.addListener((obj) => { !isEnabled(FeatureFlags.SWITCH_RUNTIME_FLAGS) ) { Object.keys(RuntimeFlag).forEach( + // Holding until the approach can be reworked around browser.storage.local. + // eslint-disable-next-line @typescript-eslint/no-unused-vars (flagName) => "", // localStorage.removeItem(flagName), ) } }) -startRedux() +let redux: Promise -// FIXME: Temporary workaround to prevent the service worker from being suspended -// This ensures we keep state updates persisted to local storage as the extension -// syncs chain data -// https://developer.chrome.com/docs/extensions/develop/migrate/to-service-workers#keep_a_service_worker_alive_until_a_long-running_operation_is_finished -setInterval(() => { - chrome.runtime.getPlatformInfo() -}, 25 * SECOND) +browser.runtime.onConnect.addListener(async (port) => { + ;(await redux).connectPort(port) +}) + +redux ??= startRedux()