Skip to content

Commit

Permalink
Move a few listeners to background script top-level/first event loop
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Shadowfiend committed Jan 7, 2025
1 parent 73231ad commit f60926b
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 81 deletions.
10 changes: 7 additions & 3 deletions background/services/ledger/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,13 @@ export default class LedgerService extends BaseService<Events> {

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<T>(operation: () => Promise<T>) {
Expand Down Expand Up @@ -289,9 +296,6 @@ export default class LedgerService extends BaseService<Events> {
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<void> {
Expand Down
82 changes: 40 additions & 42 deletions background/services/provider-bridge/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,48 +99,6 @@ export default class ProviderBridgeService extends BaseService<Events> {
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<browser.Runtime.Port>,
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<void> {
Expand Down Expand Up @@ -692,4 +650,44 @@ export default class ProviderBridgeService extends BaseService<Events> {
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<browser.Runtime.Port>,
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
}
}
52 changes: 25 additions & 27 deletions background/services/redux/index.ts
Original file line number Diff line number Diff line change
@@ -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"

Expand Down Expand Up @@ -462,8 +462,6 @@ export default class ReduxService extends BaseService<never> {
)

this.store.dispatch(clearApprovalInProgress())

this.connectPopupMonitor()
}

async addAccount(addressNetwork: AddressOnNetwork): Promise<void> {
Expand Down Expand Up @@ -633,12 +631,9 @@ export default class ReduxService extends BaseService<never> {

// 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
Expand Down Expand Up @@ -1845,30 +1840,33 @@ export default class ReduxService extends BaseService<never> {
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()
})
}

Expand Down
18 changes: 9 additions & 9 deletions src/background.ts
Original file line number Diff line number Diff line change
@@ -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) => {
Expand All @@ -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<ReduxService>

// 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()

0 comments on commit f60926b

Please sign in to comment.