From fb2a153fbc5770abf48ce14961e08c4c197f1312 Mon Sep 17 00:00:00 2001 From: Martin Varmuza Date: Tue, 24 Sep 2024 14:28:22 +0200 Subject: [PATCH 01/11] chore(transport): mark methods as public in SessionsClient --- packages/transport/src/sessions/client.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/transport/src/sessions/client.ts b/packages/transport/src/sessions/client.ts index 7c06e35ca13..a450f6edd5a 100644 --- a/packages/transport/src/sessions/client.ts +++ b/packages/transport/src/sessions/client.ts @@ -51,31 +51,31 @@ export class SessionsClient extends TypedEmitter<{ } } - handshake() { + public handshake() { return this.request({ type: 'handshake' }); } - enumerateDone(payload: EnumerateDoneRequest) { + public enumerateDone(payload: EnumerateDoneRequest) { return this.request({ type: 'enumerateDone', payload }); } - acquireIntent(payload: AcquireIntentRequest) { + public acquireIntent(payload: AcquireIntentRequest) { return this.request({ type: 'acquireIntent', payload }); } - acquireDone(payload: AcquireDoneRequest) { + public acquireDone(payload: AcquireDoneRequest) { return this.request({ type: 'acquireDone', payload }); } - releaseIntent(payload: ReleaseIntentRequest) { + public releaseIntent(payload: ReleaseIntentRequest) { return this.request({ type: 'releaseIntent', payload }); } - releaseDone(payload: ReleaseDoneRequest) { + public releaseDone(payload: ReleaseDoneRequest) { return this.request({ type: 'releaseDone', payload }); } - getSessions() { + public getSessions() { return this.request({ type: 'getSessions' }); } - getPathBySession(payload: GetPathBySessionRequest) { + public getPathBySession(payload: GetPathBySessionRequest) { return this.request({ type: 'getPathBySession', payload }); } - dispose() { + public dispose() { this.removeAllListeners('descriptors'); return this.request({ type: 'dispose' }); From 4163e02f69119563e2d1a95acdf6ca1edaea46c5 Mon Sep 17 00:00:00 2001 From: Martin Varmuza Date: Tue, 24 Sep 2024 14:36:58 +0200 Subject: [PATCH 02/11] chore(connect): introduce new connect setting '_sessionsBackgroundUrl' --- packages/connect/src/data/connectSettings.ts | 4 ++++ packages/connect/src/types/settings.ts | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/packages/connect/src/data/connectSettings.ts b/packages/connect/src/data/connectSettings.ts index c5e73f26779..f278b0eb778 100644 --- a/packages/connect/src/data/connectSettings.ts +++ b/packages/connect/src/data/connectSettings.ts @@ -144,5 +144,9 @@ export const parseConnectSettings = (input: Partial = {}) => { settings._extendWebextensionLifetime = input._extendWebextensionLifetime; } + if (typeof input._sessionsBackgroundUrl === 'string') { + settings._sessionsBackgroundUrl = input._sessionsBackgroundUrl; + } + return settings; }; diff --git a/packages/connect/src/types/settings.ts b/packages/connect/src/types/settings.ts index b5f1c235bc8..842b2fdbe66 100644 --- a/packages/connect/src/types/settings.ts +++ b/packages/connect/src/types/settings.ts @@ -26,6 +26,11 @@ export interface ConnectSettingsPublic { coreMode?: 'auto' | 'popup' | 'iframe'; /* _extendWebextensionLifetime features makes the service worker in @trezor/connect-webextension stay alive longer */ _extendWebextensionLifetime?: boolean; + /** + * for internal use only! + * in some exotic setups (suite-web where iframe is embedded locally), you might need to tell connect where it should search for sessions background shared-worker + */ + _sessionsBackgroundUrl?: string; deeplinkOpen?: (url: string) => void; deeplinkCallbackUrl?: string; deeplinkUrl?: string; From 68a25facd6a7a9804db135d3504f8edbf7744c2f Mon Sep 17 00:00:00 2001 From: Martin Varmuza Date: Tue, 24 Sep 2024 14:37:44 +0200 Subject: [PATCH 03/11] chore(connect): pass '_sessionsBackgroundUrl' to DeviceList --- packages/connect/src/core/index.ts | 3 ++- packages/connect/src/device/DeviceList.ts | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/connect/src/core/index.ts b/packages/connect/src/core/index.ts index c0a5a849df5..561767f337c 100644 --- a/packages/connect/src/core/index.ts +++ b/packages/connect/src/core/index.ts @@ -1192,7 +1192,7 @@ export class Core extends EventEmitter { try { await DataManager.load(settings); - const { debug, priority } = DataManager.getSettings(); + const { debug, priority, _sessionsBackgroundUrl } = DataManager.getSettings(); const messages = DataManager.getProtobufMessages(); enableLog(debug); @@ -1206,6 +1206,7 @@ export class Core extends EventEmitter { debug, messages, priority, + _sessionsBackgroundUrl, }); initDeviceList(this.getCoreContext()); diff --git a/packages/connect/src/device/DeviceList.ts b/packages/connect/src/device/DeviceList.ts index 366ed370117..91e1755960d 100644 --- a/packages/connect/src/device/DeviceList.ts +++ b/packages/connect/src/device/DeviceList.ts @@ -82,7 +82,7 @@ export const assertDeviceListConnected: ( if (!deviceList.isConnected()) throw ERRORS.TypedError('Transport_Missing'); }; -type ConstructorParams = Pick & { +type ConstructorParams = Pick & { messages: Record; }; type InitParams = Pick; @@ -115,7 +115,7 @@ export class DeviceList extends TypedEmitter implements IDevic return this.initPromise; } - constructor({ messages, priority, debug }: ConstructorParams) { + constructor({ messages, priority, debug, _sessionsBackgroundUrl }: ConstructorParams) { super(); const transportLogger = initLog('@trezor/transport', debug); @@ -128,6 +128,7 @@ export class DeviceList extends TypedEmitter implements IDevic messages, logger: transportLogger, signal: abortController.signal, + _sessionsBackgroundUrl, }; this.transports = [ From 74a3f499b2a555e524b5c68202534f45d1455704 Mon Sep 17 00:00:00 2001 From: Martin Varmuza Date: Tue, 24 Sep 2024 15:08:50 +0200 Subject: [PATCH 04/11] chore(transport): move sessions initialization to transport.init from constructor --- packages/connect/setupJest.ts | 35 ++--- packages/transport-bridge/src/core.ts | 4 +- packages/transport-native/src/nativeUsb.ts | 35 +++-- packages/transport/src/sessions/client.ts | 12 +- packages/transport/src/transports/abstract.ts | 6 +- .../transport/src/transports/abstractApi.ts | 6 +- packages/transport/src/transports/nodeusb.ts | 37 ++--- packages/transport/src/transports/udp.ts | 36 ++--- .../src/transports/webusb.browser.ts | 21 ++- packages/transport/tests/abstractUsb.test.ts | 135 ++++++------------ packages/transport/tests/sessions.test.ts | 12 +- 11 files changed, 162 insertions(+), 177 deletions(-) diff --git a/packages/connect/setupJest.ts b/packages/connect/setupJest.ts index 065347c9088..4556f914448 100644 --- a/packages/connect/setupJest.ts +++ b/packages/connect/setupJest.ts @@ -1,15 +1,27 @@ /* WARNING! This file should be imported ONLY in tests! */ -import { - AbstractApiTransport, - UsbApi, - SessionsClient, - SessionsBackground, -} from '@trezor/transport'; +import { AbstractApiTransport, UsbApi, SessionsBackground } from '@trezor/transport'; import { DeviceModelInternal, type Features, type FirmwareRelease } from './src/types'; class TestTransport extends AbstractApiTransport { name = 'TestTransport' as any; + + init() { + return this.scheduleAction(() => { + const sessionsBackground = new SessionsBackground(); + this.sessionsClient.init({ + requestFn: params => sessionsBackground.handleMessage(params), + registerBackgroundCallbacks: onDescriptorsCallback => { + sessionsBackground.on('descriptors', descriptors => { + onDescriptorsCallback(descriptors); + }); + }, + }); + this.stopped = false; + + return this.sessionsClient.handshake(); + }); + } } // mock of navigator.usb @@ -41,19 +53,8 @@ const createTransportApi = (override = {}) => }) as unknown as UsbApi; export const createTestTransport = (apiMethods = {}) => { - const sessionsBackground = new SessionsBackground(); - const sessionsClient = new SessionsClient({ - requestFn: params => sessionsBackground.handleMessage(params), - registerBackgroundCallbacks: onDescriptorsCallback => { - sessionsBackground.on('descriptors', descriptors => { - onDescriptorsCallback(descriptors); - }); - }, - }); - const transport = new TestTransport({ api: createTransportApi(apiMethods), - sessionsClient, }); return transport; diff --git a/packages/transport-bridge/src/core.ts b/packages/transport-bridge/src/core.ts index 0fc16fae461..3f527fbc352 100644 --- a/packages/transport-bridge/src/core.ts +++ b/packages/transport-bridge/src/core.ts @@ -19,11 +19,11 @@ export const createCore = (apiArg: 'usb' | 'udp' | AbstractApi, logger?: Log) => const sessionsBackground = new SessionsBackground(); - const sessionsClient = new SessionsClient({ + const sessionsClient = new SessionsClient(); + sessionsClient.init({ requestFn: args => sessionsBackground.handleMessage(args), registerBackgroundCallbacks: () => {}, }); - sessionsBackground.on('descriptors', descriptors => { sessionsClient.emit('descriptors', descriptors); }); diff --git a/packages/transport-native/src/nativeUsb.ts b/packages/transport-native/src/nativeUsb.ts index 636da579028..7dbb9c7ef07 100644 --- a/packages/transport-native/src/nativeUsb.ts +++ b/packages/transport-native/src/nativeUsb.ts @@ -2,7 +2,6 @@ import { WebUSB } from '@trezor/react-native-usb'; import { Transport as AbstractTransport, AbstractApiTransport, - SessionsClient, SessionsBackground, UsbApi, } from '@trezor/transport'; @@ -11,20 +10,10 @@ export class NativeUsbTransport extends AbstractApiTransport { // TODO: Not sure how to solve this type correctly. public name = 'NativeUsbTransport' as any; - private readonly sessionsBackground; + private readonly sessionsBackground = new SessionsBackground(); constructor(params: ConstructorParameters[0]) { const { messages, logger } = params; - const sessionsBackground = new SessionsBackground(); - - const sessionsClient = new SessionsClient({ - requestFn: args => sessionsBackground.handleMessage(args), - registerBackgroundCallbacks: () => {}, - }); - - sessionsBackground.on('descriptors', descriptors => { - sessionsClient.emit('descriptors', descriptors); - }); super({ messages, @@ -32,9 +21,27 @@ export class NativeUsbTransport extends AbstractApiTransport { usbInterface: new WebUSB(), logger, }), - sessionsClient, }); - this.sessionsBackground = sessionsBackground; + } + + public init() { + return this.scheduleAction(async () => { + // in NativeUsbTransport there is no synchronization and probably never will be. + // sessionsClient is talking to sessionsBackground directly + this.sessionsClient.init({ + requestFn: args => this.sessionsBackground.handleMessage(args), + registerBackgroundCallbacks: () => {}, + }); + + this.sessionsBackground.on('descriptors', descriptors => { + this.sessionsClient.emit('descriptors', descriptors); + }); + + const handshakeRes = await this.sessionsClient.handshake(); + this.stopped = !handshakeRes.success; + + return handshakeRes; + }); } public listen() { diff --git a/packages/transport/src/sessions/client.ts b/packages/transport/src/sessions/client.ts index a450f6edd5a..3a065efcb1e 100644 --- a/packages/transport/src/sessions/client.ts +++ b/packages/transport/src/sessions/client.ts @@ -21,22 +21,26 @@ export class SessionsClient extends TypedEmitter<{ descriptors: Descriptor[]; }> { // request method responsible for communication with sessions background. - private request: SessionsBackground['handleMessage']; + private request: SessionsBackground['handleMessage'] = () => { + throw new Error('SessionsClient: request method not provided'); + }; // used only for debugging - discriminating sessions clients in sessions background log private caller = getWeakRandomId(3); - private id: number; + private id: number = 0; - constructor({ + public init({ requestFn, registerBackgroundCallbacks, }: { requestFn: SessionsBackground['handleMessage']; registerBackgroundCallbacks?: RegisterBackgroundCallbacks; }) { - super(); this.id = 0; this.request = params => { + if (!requestFn) { + throw new Error('SessionsClient: requestFn not provided'); + } params.caller = this.caller; params.id = this.id; this.id++; diff --git a/packages/transport/src/transports/abstract.ts b/packages/transport/src/transports/abstract.ts index 392d799cf0d..f1282bdadd3 100644 --- a/packages/transport/src/transports/abstract.ts +++ b/packages/transport/src/transports/abstract.ts @@ -179,7 +179,11 @@ export abstract class AbstractTransport extends TransportEmitter { /** * Tries to initiate transport. Transport might not be available e.g. bridge not running. */ - abstract init(params?: AbortableParam): AsyncResultWithTypedError< + abstract init( + params?: AbortableParam & { + sessionsBackgroundUrl?: string; + }, + ): AsyncResultWithTypedError< undefined, // webusb only | typeof ERRORS.SESSION_BACKGROUND_TIMEOUT diff --git a/packages/transport/src/transports/abstractApi.ts b/packages/transport/src/transports/abstractApi.ts index 668ea1a9e6b..5429b5f2030 100644 --- a/packages/transport/src/transports/abstractApi.ts +++ b/packages/transport/src/transports/abstractApi.ts @@ -15,7 +15,6 @@ import { Session } from '../types'; interface ConstructorParams extends AbstractTransportParams { api: AbstractApi; - sessionsClient: (typeof SessionsClient)['prototype']; } /** @@ -24,12 +23,11 @@ interface ConstructorParams extends AbstractTransportParams { export abstract class AbstractApiTransport extends AbstractTransport { // sessions client is a standardized interface for communicating with sessions backend // which can live in couple of context (shared worker, local module, websocket server etc) - private sessionsClient: ConstructorParams['sessionsClient']; + protected sessionsClient = new SessionsClient(); protected api: AbstractApi; - constructor({ messages, api, sessionsClient, logger }: ConstructorParams) { + constructor({ messages, api, logger }: ConstructorParams) { super({ messages, logger }); - this.sessionsClient = sessionsClient; this.api = api; } diff --git a/packages/transport/src/transports/nodeusb.ts b/packages/transport/src/transports/nodeusb.ts index 4fd3c3b1f97..7c76b808302 100644 --- a/packages/transport/src/transports/nodeusb.ts +++ b/packages/transport/src/transports/nodeusb.ts @@ -1,7 +1,6 @@ import { WebUSB } from 'usb'; import { AbstractTransportParams } from './abstract'; import { AbstractApiTransport } from './abstractApi'; -import { SessionsClient } from '../sessions/client'; import { SessionsBackground } from '../sessions/background'; import { UsbApi } from '../api/usb'; @@ -12,22 +11,10 @@ import { UsbApi } from '../api/usb'; export class NodeUsbTransport extends AbstractApiTransport { public name = 'NodeUsbTransport' as const; - private readonly sessionsBackground; + private readonly sessionsBackground = new SessionsBackground(); constructor(params: AbstractTransportParams) { const { messages, logger, debugLink } = params; - const sessionsBackground = new SessionsBackground(); - - // in nodeusb there is no synchronization yet. this is a followup and needs to be decided - // so far, sessionsClient has direct access to sessionBackground - const sessionsClient = new SessionsClient({ - requestFn: args => sessionsBackground.handleMessage(args), - registerBackgroundCallbacks: () => {}, - }); - - sessionsBackground.on('descriptors', descriptors => { - sessionsClient.emit('descriptors', descriptors); - }); super({ messages, @@ -38,9 +25,27 @@ export class NodeUsbTransport extends AbstractApiTransport { logger, debugLink, }), - sessionsClient, }); - this.sessionsBackground = sessionsBackground; + } + + public init() { + return this.scheduleAction(async () => { + // in nodeusb there is no synchronization yet. this is a followup and needs to be decided + // so far, sessionsClient has direct access to sessionBackground + this.sessionsClient.init({ + requestFn: args => this.sessionsBackground.handleMessage(args), + registerBackgroundCallbacks: () => {}, + }); + + this.sessionsBackground.on('descriptors', descriptors => { + this.sessionsClient.emit('descriptors', descriptors); + }); + + const handshakeRes = await this.sessionsClient.handshake(); + this.stopped = !handshakeRes.success; + + return handshakeRes; + }); } public listen() { diff --git a/packages/transport/src/transports/udp.ts b/packages/transport/src/transports/udp.ts index e732b54a0f3..b6ebcbad9e9 100644 --- a/packages/transport/src/transports/udp.ts +++ b/packages/transport/src/transports/udp.ts @@ -3,35 +3,39 @@ import { AbstractTransportParams } from './abstract'; import { UdpApi } from '../api/udp'; import { AbstractApiTransport } from './abstractApi'; -import { SessionsClient } from '../sessions/client'; import { SessionsBackground } from '../sessions/background'; - export class UdpTransport extends AbstractApiTransport { public name = 'UdpTransport' as const; private enumerateTimeout: ReturnType | undefined; - private readonly sessionsBackground; + private readonly sessionsBackground = new SessionsBackground(); constructor(params: AbstractTransportParams) { const { messages, logger, debugLink } = params; - const sessionsBackground = new SessionsBackground(); - - // in udp there is no synchronization yet. it depends where this transport runs (node or browser) - const sessionsClient = new SessionsClient({ - requestFn: args => sessionsBackground.handleMessage(args), - registerBackgroundCallbacks: () => {}, - }); - - sessionsBackground.on('descriptors', descriptors => { - sessionsClient.emit('descriptors', descriptors); - }); super({ messages, api: new UdpApi({ logger, debugLink }), logger, - sessionsClient, }); - this.sessionsBackground = sessionsBackground; + } + + public init() { + return this.scheduleAction(async () => { + // in udp there is no synchronization. + this.sessionsClient.init({ + requestFn: args => this.sessionsBackground.handleMessage(args), + registerBackgroundCallbacks: () => {}, + }); + + this.sessionsBackground.on('descriptors', descriptors => { + this.sessionsClient.emit('descriptors', descriptors); + }); + + const handshakeRes = await this.sessionsClient.handshake(); + this.stopped = !handshakeRes.success; + + return handshakeRes; + }); } public listen() { diff --git a/packages/transport/src/transports/webusb.browser.ts b/packages/transport/src/transports/webusb.browser.ts index 0415195e0ef..2cebdc63da9 100644 --- a/packages/transport/src/transports/webusb.browser.ts +++ b/packages/transport/src/transports/webusb.browser.ts @@ -1,6 +1,5 @@ import { AbstractTransportParams } from './abstract'; import { AbstractApiTransport } from './abstractApi'; -import { SessionsClient } from '../sessions/client'; import { UsbApi } from '../api/usb'; import { initBackgroundInBrowser } from '../sessions/background-browser'; @@ -15,7 +14,6 @@ export class WebUsbTransport extends AbstractApiTransport { constructor(params: AbstractTransportParams) { const { messages, logger } = params; - const { requestFn, registerBackgroundCallbacks } = initBackgroundInBrowser(); super({ messages, @@ -24,12 +22,23 @@ export class WebUsbTransport extends AbstractApiTransport { logger, }), logger, - // sessions client with a request fn facilitating communication with a session backend (shared worker in case of webusb) - sessionsClient: new SessionsClient({ - // @ts-expect-error + }); + } + + public init({ sessionsBackgroundUrl }: { sessionsBackgroundUrl: string }) { + return this.scheduleAction(async () => { + const { requestFn, registerBackgroundCallbacks } = + await initBackgroundInBrowser(sessionsBackgroundUrl); + // sessions client initiated with a request fn facilitating communication with a session backend (shared worker in case of webusb) + this.sessionsClient.init({ requestFn, registerBackgroundCallbacks, - }), + }); + + const handshakeRes = await this.sessionsClient.handshake(); + this.stopped = !handshakeRes.success; + + return handshakeRes; }); } diff --git a/packages/transport/tests/abstractUsb.test.ts b/packages/transport/tests/abstractUsb.test.ts index 90fc402a062..d4ebc79a393 100644 --- a/packages/transport/tests/abstractUsb.test.ts +++ b/packages/transport/tests/abstractUsb.test.ts @@ -43,40 +43,41 @@ const createUsbMock = (optional = {}) => class TestUsbTransport extends AbstractApiTransport { public name = 'WebUsbTransport' as const; + public sessionsClient = new SessionsClient({}); - constructor({ - messages, - api, - sessionsClient, - }: ConstructorParameters[0]) { + constructor({ messages, api }: ConstructorParameters[0]) { super({ messages, api, - sessionsClient, }); } -} -// we cant directly use abstract class (UsbTransport) -const initTest = async () => { - let sessionsBackground: SessionsBackground; - let sessionsClient: SessionsClient; - let transport: AbstractTransport; - let testUsbApi: UsbApi; - sessionsBackground = new SessionsBackground(); + init() { + return this.scheduleAction(async () => { + const sessionsBackground = new SessionsBackground(); + + // in nodeusb there is no synchronization yet. this is a followup and needs to be decided + // so far, sessionsClient has direct access to sessionBackground + this.sessionsClient.init({ + requestFn: args => sessionsBackground.handleMessage(args), + registerBackgroundCallbacks: () => {}, + }); - sessionsClient = new SessionsClient({ - requestFn: params => sessionsBackground.handleMessage(params), - registerBackgroundCallbacks: onDescriptorsCallback => { sessionsBackground.on('descriptors', descriptors => { - onDescriptorsCallback(descriptors); + this.sessionsClient.emit('descriptors', descriptors); }); - }, - }); - sessionsBackground.on('descriptors', descriptors => { - sessionsClient.emit('descriptors', descriptors); - }); + const handshakeRes = await this.sessionsClient.handshake(); + this.stopped = !handshakeRes.success; + + return handshakeRes; + }); + } +} +// we cant directly use abstract class (UsbTransport) +const initTest = async () => { + let transport: AbstractTransport; + let testUsbApi: UsbApi; // create usb api with navigator.usb mock testUsbApi = new UsbApi({ @@ -85,14 +86,12 @@ const initTest = async () => { transport = new TestUsbTransport({ api: testUsbApi, - sessionsClient, messages, }); - await transport.init(); + const initResponse = await transport.init(); + expect(initResponse.success).toEqual(true); return { - sessionsBackground, - sessionsClient, transport, testUsbApi, }; @@ -108,40 +107,7 @@ describe('Usb', () => { afterAll(async () => {}); describe('without initiated transport', () => { - it('init error', async () => { - const sessionsClient = new SessionsClient({ - // @ts-expect-error - requestFn: _params => ({ type: 'meow' }), - registerBackgroundCallbacks: () => {}, - }); - - // create usb api with navigator.usb mock - const testUsbApi = new UsbApi({ - usbInterface: createUsbMock(), - }); - - const transport = new TestUsbTransport({ - api: testUsbApi, - sessionsClient, - }); - - // there are no loaded messages - expect(transport.getMessage()).toEqual(false); - - const res = await transport.init(); - expect(res).toMatchObject({ - success: false, - }); - }); - it('enumerate error', async () => { - const sessionsBackground = new SessionsBackground(); - - const sessionsClient = new SessionsClient({ - requestFn: params => sessionsBackground.handleMessage(params), - registerBackgroundCallbacks: () => {}, - }); - // create usb api with navigator.usb mock const testUsbApi = new UsbApi({ usbInterface: createUsbMock({ @@ -153,7 +119,6 @@ describe('Usb', () => { const transport = new TestUsbTransport({ api: testUsbApi, - sessionsClient, }); await transport.init(); @@ -164,23 +129,20 @@ describe('Usb', () => { error: 'unexpected error', message: 'crazy error nobody expects', }); - - sessionsBackground.dispose(); }); }); describe('with initiated transport', () => { it('listen twice -> error', async () => { - const { transport, sessionsBackground } = await initTest(); + const { transport } = await initTest(); const res1 = transport.listen(); expect(res1.success).toEqual(true); const res2 = transport.listen(); expect(res2.success).toEqual(false); - sessionsBackground.dispose(); }); it('handleDescriptorsChange', async () => { - const { transport, sessionsBackground } = await initTest(); + const { transport } = await initTest(); const spy = jest.fn(); transport.on('transport-update', spy); @@ -193,11 +155,10 @@ describe('Usb', () => { expect(spy).toHaveBeenCalledWith([ { type: 'disconnected', descriptor: { path: '1', session: null, type: 1 } }, ]); - sessionsBackground.dispose(); }); it('enumerate', async () => { - const { transport, sessionsBackground } = await initTest(); + const { transport } = await initTest(); const res = await transport.enumerate(); expect(res).toEqual({ success: true, @@ -218,11 +179,10 @@ describe('Usb', () => { }, ], }); - sessionsBackground.dispose(); }); it('acquire. transport is not listening', async () => { - const { transport, sessionsBackground } = await initTest(); + const { transport } = await initTest(); jest.useFakeTimers(); const spy = jest.fn(); transport.on('transport-update', spy); @@ -240,13 +200,10 @@ describe('Usb', () => { }); expect(spy).toHaveBeenCalledTimes(0); - sessionsBackground.dispose(); }); it('acquire. transport listening. missing descriptor', async () => { - const { transport, sessionsClient, sessionsBackground } = await initTest(); - - sessionsBackground.removeAllListeners(); + const { transport } = await initTest(); const enumerateResult = await transport.enumerate(); @@ -262,7 +219,8 @@ describe('Usb', () => { }); setTimeout(() => { - sessionsClient.emit('descriptors', [ + // @ts-expect-error (private field accessed in tests) + transport.sessionsClient.emit('descriptors', [ { path: PathPublic('321'), session: Session('1'), type: 1 }, ]); }, 1); @@ -273,12 +231,10 @@ describe('Usb', () => { success: false, error: 'device disconnected during action', }); - sessionsBackground.dispose(); }); it('acquire. transport listening. unexpected session', async () => { - const { transport, sessionsClient, sessionsBackground } = await initTest(); - sessionsBackground.removeAllListeners(); + const { transport } = await initTest(); const enumerateResult = await transport.enumerate(); expect(enumerateResult.success).toEqual(true); // @ts-expect-error @@ -291,18 +247,18 @@ describe('Usb', () => { input: { path: PathPublic('1'), previous: null }, }); setTimeout(() => { - sessionsClient.emit('descriptors', [ + // @ts-expect-error (private field accessed in tests) + transport.sessionsClient.emit('descriptors', [ { path: PathPublic('1'), session: Session('2'), type: 1, product: 21441 }, ]); }, 1); const res = await acquireCall; expect(res).toMatchObject({ success: false, error: 'wrong previous session' }); - sessionsBackground.dispose(); }); it('call error - called without acquire.', async () => { - const { transport, sessionsBackground } = await initTest(); + const { transport } = await initTest(); const res = await transport.call({ name: 'GetAddress', data: {}, @@ -310,11 +266,10 @@ describe('Usb', () => { protocol: v1Protocol, }); expect(res).toEqual({ success: false, error: 'device disconnected during action' }); - sessionsBackground.dispose(); }); it('call - with valid and invalid message.', async () => { - const { transport, sessionsBackground } = await initTest(); + const { transport } = await initTest(); await transport.enumerate(); const acquireRes = await transport.acquire({ input: { path: PathPublic('1'), previous: null }, @@ -354,12 +309,10 @@ describe('Usb', () => { error: 'unexpected error', message: 'no such type: Foo-bar message', }); - - sessionsBackground.dispose(); }); it('send and receive.', async () => { - const { transport, sessionsBackground } = await initTest(); + const { transport } = await initTest(); await transport.enumerate(); const acquireRes = await transport.acquire({ input: { path: PathPublic('1'), previous: null }, @@ -393,11 +346,10 @@ describe('Usb', () => { }, }, }); - sessionsBackground.dispose(); }); it('send protocol-v1 with custom chunkSize', async () => { - const { transport, testUsbApi, sessionsBackground } = await initTest(); + const { transport, testUsbApi } = await initTest(); await transport.enumerate(); const acquireRes = await transport.acquire({ input: { path: PathPublic('1'), previous: null }, @@ -433,11 +385,10 @@ describe('Usb', () => { await send(); // bigger chunks expect(writeSpy).toHaveBeenCalledTimes(2); writeSpy.mockClear(); - sessionsBackground.dispose(); }); it('release', async () => { - const { transport, sessionsBackground } = await initTest(); + const { transport } = await initTest(); await transport.enumerate(); const acquireRes = await transport.acquire({ input: { path: PathPublic('1'), previous: null }, @@ -457,11 +408,10 @@ describe('Usb', () => { success: true, payload: undefined, }); - sessionsBackground.dispose(); }); it('call - with use abort', async () => { - const { transport, sessionsBackground } = await initTest(); + const { transport } = await initTest(); await transport.enumerate(); const acquireRes = await transport.acquire({ input: { path: PathPublic('1'), previous: null }, @@ -499,7 +449,6 @@ describe('Usb', () => { }, }, }); - sessionsBackground.dispose(); }); }); }); diff --git a/packages/transport/tests/sessions.test.ts b/packages/transport/tests/sessions.test.ts index 881acbc582a..c4d4ce3a67a 100644 --- a/packages/transport/tests/sessions.test.ts +++ b/packages/transport/tests/sessions.test.ts @@ -11,7 +11,8 @@ describe('sessions', () => { }); test('acquire without previous enumerate', async () => { - const client1 = new SessionsClient({ requestFn }); + const client1 = new SessionsClient(); + client1.init({ requestFn }); await client1.handshake(); const acquireIntent = await client1.acquireIntent({ @@ -27,7 +28,8 @@ describe('sessions', () => { }); test('acquire', async () => { - const client1 = new SessionsClient({ requestFn }); + const client1 = new SessionsClient(); + client1.init({ requestFn }); await client1.handshake(); await client1.enumerateDone({ descriptors: [{ path: PathInternal('abc'), type: 1 }] }); @@ -71,7 +73,8 @@ describe('sessions', () => { test('acquire', async () => { expect.assertions(3); - const client1 = new SessionsClient({ requestFn }); + const client1 = new SessionsClient(); + client1.init({ requestFn }); await client1.handshake(); await client1.enumerateDone({ descriptors: [{ path: PathInternal('1'), type: 1 }] }); @@ -128,7 +131,8 @@ describe('sessions', () => { }); test('acquire - release - acquire', async () => { - const client1 = new SessionsClient({ requestFn }); + const client1 = new SessionsClient(); + client1.init({ requestFn }); await client1.handshake(); await client1.enumerateDone({ descriptors: [{ path: PathInternal('1'), type: 1 }] }); From 5efd1b2193a6a579b46a8a01ebdc73d64c73466f Mon Sep 17 00:00:00 2001 From: Martin Varmuza Date: Tue, 24 Sep 2024 15:58:56 +0200 Subject: [PATCH 05/11] chore(transport): rework sessions background shared-worker initialization --- .../src/sessions/background-browser.ts | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/packages/transport/src/sessions/background-browser.ts b/packages/transport/src/sessions/background-browser.ts index 21cee1e8ac2..1b155a72e98 100644 --- a/packages/transport/src/sessions/background-browser.ts +++ b/packages/transport/src/sessions/background-browser.ts @@ -1,10 +1,14 @@ import type { Descriptor } from '../types'; import { SessionsBackground } from './background'; -// @ts-expect-error (typescript does not know this is worker constructor, this is done by webpack) -import BackgroundSharedworker from './background-sharedworker'; import { RegisterBackgroundCallbacks } from './types'; +const defaultSessionsBackgroundUrl = + window.location.origin + + `${process.env.ASSET_PREFIX || ''}/workers/sessions-background-sharedworker.js` + // just in case so that whoever defines ASSET_PREFIX does not need to worry about trailing slashes + .replace(/\/+/g, '/'); + /** * calling initBackgroundInBrowser initiates sessions-background for browser based environments and returns: * - `requestFn` which is used to send messages to sessions background @@ -13,15 +17,33 @@ import { RegisterBackgroundCallbacks } from './types'; * if possible sessions background utilizes native Sharedworker. If for whatever reason * Sharedworker is not available, it fallbacks to local module behavior */ -export const initBackgroundInBrowser = () => { +export const initBackgroundInBrowser = async ( + sessionsBackgroundUrl = defaultSessionsBackgroundUrl, +): Promise<{ + background: SessionsBackground | SharedWorker; + requestFn: SessionsBackground['handleMessage']; + registerBackgroundCallbacks: RegisterBackgroundCallbacks; +}> => { try { - const background: SharedWorker = new BackgroundSharedworker(); + const url = sessionsBackgroundUrl; - const requestFn = (params: Parameters[0]) => - new Promise>>(resolve => { - const onmessage = ( - message: MessageEvent>>, - ) => { + // fetch to validate - failed fetch via SharedWorker constructor does not throw. It even hangs resulting in all kinds of weird behaviors + await fetch(url, { method: 'HEAD' }).then(response => { + if (!response.ok) { + throw new Error( + 'Failed to fetch sessions-background SharedWorker from url: ' + url, + ); + } + }); + const background = new SharedWorker(url, { + name: '@trezor/connect-web transport sessions worker', + }); + + const requestFn: SessionsBackground['handleMessage'] = ( + params: Parameters[0], + ) => + new Promise(resolve => { + const onmessage = (message: MessageEvent) => { if (params.id === message.data.id) { resolve(message.data); background.port.removeEventListener('message', onmessage); @@ -62,7 +84,8 @@ export const initBackgroundInBrowser = () => { return { background, requestFn, registerBackgroundCallbacks }; } catch (err) { console.warn( - 'Unable to load background-sharedworker. Falling back to use local module. Say bye bye to tabs synchronization', + 'Unable to load background-sharedworker. Falling back to use local module. Say bye bye to tabs synchronization. Error details: ', + err.message, ); const background = new SessionsBackground(); From 1118e6710ed5e7d77306e2e49e6ec5efa74bc381 Mon Sep 17 00:00:00 2001 From: Martin Varmuza Date: Tue, 24 Sep 2024 15:59:27 +0200 Subject: [PATCH 06/11] chore(connect-iframe): rework how sessions worker is built (standalone entrypoint) --- packages/connect-iframe/webpack/base.webpack.config.ts | 8 -------- packages/connect-iframe/webpack/prod.webpack.config.ts | 7 +++++++ packages/transport/src/sessions/background-browser.ts | 8 +++----- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/packages/connect-iframe/webpack/base.webpack.config.ts b/packages/connect-iframe/webpack/base.webpack.config.ts index baea05112ef..1b73e0ea59f 100644 --- a/packages/connect-iframe/webpack/base.webpack.config.ts +++ b/packages/connect-iframe/webpack/base.webpack.config.ts @@ -22,14 +22,6 @@ export const config: webpack.Configuration = { mode: process.env.NODE_ENV === 'production' ? 'production' : 'development', module: { rules: [ - { - test: (input: string) => input.includes('background-sharedworker'), - loader: 'worker-loader', - options: { - worker: 'SharedWorker', - filename: './workers/sessions-background-sharedworker.[contenthash].js', - }, - }, { test: /sharedLoggerWorker.ts/i, use: [ diff --git a/packages/connect-iframe/webpack/prod.webpack.config.ts b/packages/connect-iframe/webpack/prod.webpack.config.ts index d9d1e529fda..0ed04b4c1f9 100644 --- a/packages/connect-iframe/webpack/prod.webpack.config.ts +++ b/packages/connect-iframe/webpack/prod.webpack.config.ts @@ -10,6 +10,13 @@ const config: webpack.Configuration = { // common instructions that are able to build correctly imports from @trezor/connect (reusing this in popup) entry: { iframe: path.resolve(__dirname, '../src/index.ts'), + ['sessions-background-sharedworker']: { + filename: 'workers/[name].js', + import: path.resolve( + __dirname, + '../../transport/src/sessions/background-sharedworker.ts', + ), + }, }, output: { filename: 'js/[name].[contenthash].js', diff --git a/packages/transport/src/sessions/background-browser.ts b/packages/transport/src/sessions/background-browser.ts index 1b155a72e98..b62e0dfc4c2 100644 --- a/packages/transport/src/sessions/background-browser.ts +++ b/packages/transport/src/sessions/background-browser.ts @@ -25,17 +25,15 @@ export const initBackgroundInBrowser = async ( registerBackgroundCallbacks: RegisterBackgroundCallbacks; }> => { try { - const url = sessionsBackgroundUrl; - // fetch to validate - failed fetch via SharedWorker constructor does not throw. It even hangs resulting in all kinds of weird behaviors - await fetch(url, { method: 'HEAD' }).then(response => { + await fetch(sessionsBackgroundUrl, { method: 'HEAD' }).then(response => { if (!response.ok) { throw new Error( - 'Failed to fetch sessions-background SharedWorker from url: ' + url, + `Failed to fetch sessions-background SharedWorker from url: ${sessionsBackgroundUrl}`, ); } }); - const background = new SharedWorker(url, { + const background = new SharedWorker(sessionsBackgroundUrl, { name: '@trezor/connect-web transport sessions worker', }); From a10cc5ddc078553db6c1b1777dfda1f26d232170 Mon Sep 17 00:00:00 2001 From: Martin Varmuza Date: Tue, 24 Sep 2024 16:03:48 +0200 Subject: [PATCH 07/11] chore(connect): pass _sessionsBackgroundUrl to transport --- packages/connect/src/device/DeviceList.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/connect/src/device/DeviceList.ts b/packages/connect/src/device/DeviceList.ts index 91e1755960d..81731ecfa27 100644 --- a/packages/connect/src/device/DeviceList.ts +++ b/packages/connect/src/device/DeviceList.ts @@ -338,7 +338,9 @@ export class DeviceList extends TypedEmitter implements IDevic } private async selectTransport([transport, ...rest]: Transport[]): Promise { - const result = await transport.init(); + const result = await transport.init({ + sessionsBackgroundUrl: this.transportCommonArgs._sessionsBackgroundUrl, + }); if (result.success) return transport; else if (rest.length) return this.selectTransport(rest); else throw new Error(result.error); From f64403e12edb182cd0b673b2847c1ac9e74af028 Mon Sep 17 00:00:00 2001 From: Martin Varmuza Date: Tue, 24 Sep 2024 16:05:04 +0200 Subject: [PATCH 08/11] fix(suite): pass _sessionsBackgroundUrl to prevent missing synchronization with apps using connect --- suite-common/connect-init/package.json | 2 ++ .../connect-init/src/connectInitThunks.ts | 15 +++++++++++++++ suite-common/connect-init/tsconfig.json | 2 ++ yarn.lock | 6 ++++-- 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/suite-common/connect-init/package.json b/suite-common/connect-init/package.json index bd02007ec27..3cb0d53ffbb 100644 --- a/suite-common/connect-init/package.json +++ b/suite-common/connect-init/package.json @@ -15,9 +15,11 @@ "@reduxjs/toolkit": "1.9.5", "@suite-common/redux-utils": "workspace:*", "@suite-common/suite-types": "workspace:*", + "@suite-common/suite-utils": "workspace:^", "@suite-common/test-utils": "workspace:*", "@suite-common/wallet-core": "workspace:*", "@trezor/connect": "workspace:*", + "@trezor/env-utils": "workspace:^", "@trezor/utils": "workspace:*" }, "devDependencies": { diff --git a/suite-common/connect-init/src/connectInitThunks.ts b/suite-common/connect-init/src/connectInitThunks.ts index 17c69a73c45..dd4b4e14d17 100644 --- a/suite-common/connect-init/src/connectInitThunks.ts +++ b/suite-common/connect-init/src/connectInitThunks.ts @@ -8,6 +8,7 @@ import TrezorConnect, { } from '@trezor/connect'; import { getSynchronize } from '@trezor/utils'; import { deviceConnectThunks } from '@suite-common/wallet-core'; +import { resolveStaticPath } from '@suite-common/suite-utils'; import { cardanoConnectPatch } from './cardanoConnectPatch'; @@ -108,11 +109,25 @@ export const connectInitThunk = createThunk( cardanoConnectPatch(getEnabledNetworks); + // note: + // this way, for local development you will get http://localhost:8000/static/connect/workers/sessions-background-sharedworker.js which is still the not-shared shared-worker + // meaning that testing it together with connect-explorer dev build (http://localhost:8088/workers/sessions-background-sharedworker.js) will not work locally. + // in production however, both suite and connect are served from the same domain (trezor.io, sldev.cz) so it will work as expected. + const sessionsBackground = + typeof window !== 'undefined' + ? window.location.origin + + resolveStaticPath( + 'connect/workers/sessions-background-sharedworker.js', + `${process.env.ASSET_PREFIX || ''}`, + ) + : undefined; + try { await TrezorConnect.init({ ...connectInitSettings, pendingTransportEvent: selectIsPendingTransportEvent(getState()), transports: selectDebugSettings(getState()).transports, + _sessionsBackgroundUrl: sessionsBackground, // debug: true, // Enable debug logs in TrezorConnect }); } catch (error) { diff --git a/suite-common/connect-init/tsconfig.json b/suite-common/connect-init/tsconfig.json index 739f4480f72..dfa2b7749bc 100644 --- a/suite-common/connect-init/tsconfig.json +++ b/suite-common/connect-init/tsconfig.json @@ -4,9 +4,11 @@ "references": [ { "path": "../redux-utils" }, { "path": "../suite-types" }, + { "path": "../suite-utils" }, { "path": "../test-utils" }, { "path": "../wallet-core" }, { "path": "../../packages/connect" }, + { "path": "../../packages/env-utils" }, { "path": "../../packages/utils" } ] } diff --git a/yarn.lock b/yarn.lock index 34ce9e19a3c..a23799f4715 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9067,9 +9067,11 @@ __metadata: "@reduxjs/toolkit": "npm:1.9.5" "@suite-common/redux-utils": "workspace:*" "@suite-common/suite-types": "workspace:*" + "@suite-common/suite-utils": "workspace:^" "@suite-common/test-utils": "workspace:*" "@suite-common/wallet-core": "workspace:*" "@trezor/connect": "workspace:*" + "@trezor/env-utils": "workspace:^" "@trezor/utils": "workspace:*" redux-mock-store: "npm:^1.5.4" redux-thunk: "npm:^2.4.2" @@ -9288,7 +9290,7 @@ __metadata: languageName: unknown linkType: soft -"@suite-common/suite-utils@workspace:*, @suite-common/suite-utils@workspace:suite-common/suite-utils": +"@suite-common/suite-utils@workspace:*, @suite-common/suite-utils@workspace:^, @suite-common/suite-utils@workspace:suite-common/suite-utils": version: 0.0.0-use.local resolution: "@suite-common/suite-utils@workspace:suite-common/suite-utils" dependencies: @@ -11557,7 +11559,7 @@ __metadata: languageName: unknown linkType: soft -"@trezor/env-utils@workspace:*, @trezor/env-utils@workspace:packages/env-utils": +"@trezor/env-utils@workspace:*, @trezor/env-utils@workspace:^, @trezor/env-utils@workspace:packages/env-utils": version: 0.0.0-use.local resolution: "@trezor/env-utils@workspace:packages/env-utils" dependencies: From acb63e0163fec203965392e871c1c98c889303d8 Mon Sep 17 00:00:00 2001 From: Martin Varmuza Date: Wed, 25 Sep 2024 13:09:50 +0200 Subject: [PATCH 09/11] chore(transport): get rid of duplicated code by moving it to parent class --- packages/transport-native/src/nativeUsb.ts | 30 +---------------- .../transport/src/transports/abstractApi.ts | 32 ++++++++++++------- packages/transport/src/transports/nodeusb.ts | 28 ---------------- packages/transport/src/transports/udp.ts | 22 ------------- 4 files changed, 22 insertions(+), 90 deletions(-) diff --git a/packages/transport-native/src/nativeUsb.ts b/packages/transport-native/src/nativeUsb.ts index 7dbb9c7ef07..471b5b88b98 100644 --- a/packages/transport-native/src/nativeUsb.ts +++ b/packages/transport-native/src/nativeUsb.ts @@ -1,17 +1,10 @@ import { WebUSB } from '@trezor/react-native-usb'; -import { - Transport as AbstractTransport, - AbstractApiTransport, - SessionsBackground, - UsbApi, -} from '@trezor/transport'; +import { Transport as AbstractTransport, AbstractApiTransport, UsbApi } from '@trezor/transport'; export class NativeUsbTransport extends AbstractApiTransport { // TODO: Not sure how to solve this type correctly. public name = 'NativeUsbTransport' as any; - private readonly sessionsBackground = new SessionsBackground(); - constructor(params: ConstructorParameters[0]) { const { messages, logger } = params; @@ -24,29 +17,8 @@ export class NativeUsbTransport extends AbstractApiTransport { }); } - public init() { - return this.scheduleAction(async () => { - // in NativeUsbTransport there is no synchronization and probably never will be. - // sessionsClient is talking to sessionsBackground directly - this.sessionsClient.init({ - requestFn: args => this.sessionsBackground.handleMessage(args), - registerBackgroundCallbacks: () => {}, - }); - - this.sessionsBackground.on('descriptors', descriptors => { - this.sessionsClient.emit('descriptors', descriptors); - }); - - const handshakeRes = await this.sessionsClient.handshake(); - this.stopped = !handshakeRes.success; - - return handshakeRes; - }); - } - public listen() { this.api.listen(); - this.sessionsBackground.dispose(); return super.listen(); } diff --git a/packages/transport/src/transports/abstractApi.ts b/packages/transport/src/transports/abstractApi.ts index 5429b5f2030..8bf1886b385 100644 --- a/packages/transport/src/transports/abstractApi.ts +++ b/packages/transport/src/transports/abstractApi.ts @@ -1,3 +1,4 @@ +import { SessionsBackground } from '../sessions/background'; import { createDeferred } from '@trezor/utils'; import { v1 as v1Protocol } from '@trezor/protocol'; @@ -24,6 +25,8 @@ export abstract class AbstractApiTransport extends AbstractTransport { // sessions client is a standardized interface for communicating with sessions backend // which can live in couple of context (shared worker, local module, websocket server etc) protected sessionsClient = new SessionsClient(); + private sessionsBackground = new SessionsBackground(); + protected api: AbstractApi; constructor({ messages, api, logger }: ConstructorParams) { @@ -31,18 +34,24 @@ export abstract class AbstractApiTransport extends AbstractTransport { this.api = api; } - public init({ signal }: AbstractTransportMethodParams<'init'> = {}) { - return this.scheduleAction( - async () => { - const handshakeRes = await this.sessionsClient.handshake(); - this.stopped = !handshakeRes.success; + public init(_args: AbstractTransportMethodParams<'init'>) { + return this.scheduleAction(async () => { + // in nodeusb there is no synchronization yet. this is a followup and needs to be decided + // so far, sessionsClient has direct access to sessionBackground + this.sessionsClient.init({ + requestFn: args => this.sessionsBackground.handleMessage(args), + registerBackgroundCallbacks: () => {}, + }); - return handshakeRes.success - ? this.success(undefined) - : this.unknownError('handshake error'); - }, - { signal }, - ); + this.sessionsBackground.on('descriptors', descriptors => { + this.sessionsClient.emit('descriptors', descriptors); + }); + + const handshakeRes = await this.sessionsClient.handshake(); + this.stopped = !handshakeRes.success; + + return handshakeRes; + }); } public listen() { @@ -333,5 +342,6 @@ export abstract class AbstractApiTransport extends AbstractTransport { this.api.on('transport-interface-change', () => { this.logger?.debug('device connected after transport stopped'); }); + this.sessionsBackground.dispose(); } } diff --git a/packages/transport/src/transports/nodeusb.ts b/packages/transport/src/transports/nodeusb.ts index 7c76b808302..37c9305ef13 100644 --- a/packages/transport/src/transports/nodeusb.ts +++ b/packages/transport/src/transports/nodeusb.ts @@ -1,7 +1,6 @@ import { WebUSB } from 'usb'; import { AbstractTransportParams } from './abstract'; import { AbstractApiTransport } from './abstractApi'; -import { SessionsBackground } from '../sessions/background'; import { UsbApi } from '../api/usb'; // notes: @@ -11,8 +10,6 @@ import { UsbApi } from '../api/usb'; export class NodeUsbTransport extends AbstractApiTransport { public name = 'NodeUsbTransport' as const; - private readonly sessionsBackground = new SessionsBackground(); - constructor(params: AbstractTransportParams) { const { messages, logger, debugLink } = params; @@ -28,34 +25,9 @@ export class NodeUsbTransport extends AbstractApiTransport { }); } - public init() { - return this.scheduleAction(async () => { - // in nodeusb there is no synchronization yet. this is a followup and needs to be decided - // so far, sessionsClient has direct access to sessionBackground - this.sessionsClient.init({ - requestFn: args => this.sessionsBackground.handleMessage(args), - registerBackgroundCallbacks: () => {}, - }); - - this.sessionsBackground.on('descriptors', descriptors => { - this.sessionsClient.emit('descriptors', descriptors); - }); - - const handshakeRes = await this.sessionsClient.handshake(); - this.stopped = !handshakeRes.success; - - return handshakeRes; - }); - } - public listen() { this.api.listen(); return super.listen(); } - - public stop() { - super.stop(); - this.sessionsBackground.dispose(); - } } diff --git a/packages/transport/src/transports/udp.ts b/packages/transport/src/transports/udp.ts index b6ebcbad9e9..9e69d26ebaa 100644 --- a/packages/transport/src/transports/udp.ts +++ b/packages/transport/src/transports/udp.ts @@ -3,11 +3,9 @@ import { AbstractTransportParams } from './abstract'; import { UdpApi } from '../api/udp'; import { AbstractApiTransport } from './abstractApi'; -import { SessionsBackground } from '../sessions/background'; export class UdpTransport extends AbstractApiTransport { public name = 'UdpTransport' as const; private enumerateTimeout: ReturnType | undefined; - private readonly sessionsBackground = new SessionsBackground(); constructor(params: AbstractTransportParams) { const { messages, logger, debugLink } = params; @@ -19,25 +17,6 @@ export class UdpTransport extends AbstractApiTransport { }); } - public init() { - return this.scheduleAction(async () => { - // in udp there is no synchronization. - this.sessionsClient.init({ - requestFn: args => this.sessionsBackground.handleMessage(args), - registerBackgroundCallbacks: () => {}, - }); - - this.sessionsBackground.on('descriptors', descriptors => { - this.sessionsClient.emit('descriptors', descriptors); - }); - - const handshakeRes = await this.sessionsClient.handshake(); - this.stopped = !handshakeRes.success; - - return handshakeRes; - }); - } - public listen() { this.api.listen(); @@ -49,7 +28,6 @@ export class UdpTransport extends AbstractApiTransport { clearTimeout(this.enumerateTimeout); this.enumerateTimeout = undefined; } - this.sessionsBackground.dispose(); return super.stop(); } From e60cf2486ea92b43e8ad18c5ac1c8341647cfe67 Mon Sep 17 00:00:00 2001 From: Nodonisko Date: Thu, 26 Sep 2024 11:14:35 +0200 Subject: [PATCH 10/11] fix: sharedworked in react native connect init --- .../connect-init/src/connectInitThunks.ts | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/suite-common/connect-init/src/connectInitThunks.ts b/suite-common/connect-init/src/connectInitThunks.ts index dd4b4e14d17..02c760d59e6 100644 --- a/suite-common/connect-init/src/connectInitThunks.ts +++ b/suite-common/connect-init/src/connectInitThunks.ts @@ -9,6 +9,7 @@ import TrezorConnect, { import { getSynchronize } from '@trezor/utils'; import { deviceConnectThunks } from '@suite-common/wallet-core'; import { resolveStaticPath } from '@suite-common/suite-utils'; +import { isNative } from '@trezor/env-utils'; import { cardanoConnectPatch } from './cardanoConnectPatch'; @@ -112,15 +113,16 @@ export const connectInitThunk = createThunk( // note: // this way, for local development you will get http://localhost:8000/static/connect/workers/sessions-background-sharedworker.js which is still the not-shared shared-worker // meaning that testing it together with connect-explorer dev build (http://localhost:8088/workers/sessions-background-sharedworker.js) will not work locally. - // in production however, both suite and connect are served from the same domain (trezor.io, sldev.cz) so it will work as expected. - const sessionsBackground = - typeof window !== 'undefined' - ? window.location.origin + - resolveStaticPath( - 'connect/workers/sessions-background-sharedworker.js', - `${process.env.ASSET_PREFIX || ''}`, - ) - : undefined; + // in production however, suite and connect are served from the same domain (trezor.io, sldev.cz) so it will work as expected. + let sessionsBackground: string | undefined; + if (typeof window !== 'undefined' && !isNative()) { + sessionsBackground = + window.location.origin + + resolveStaticPath( + 'connect/workers/sessions-background-sharedworker.js', + `${process.env.ASSET_PREFIX || ''}`, + ); + } try { await TrezorConnect.init({ From 8394c8754c0dec2cec265f5af7f5fd1e7d4a7cd9 Mon Sep 17 00:00:00 2001 From: Marek Polak Date: Fri, 27 Sep 2024 15:49:33 +0200 Subject: [PATCH 11/11] refactor(transport): pass `sessionsBackgroundUrl` to constructor --- packages/connect/src/device/DeviceList.ts | 10 +---- packages/transport/src/transports/abstract.ts | 6 +-- .../transport/src/transports/abstractApi.ts | 33 +++++++------- .../src/transports/webusb.browser.ts | 43 +++++++++++-------- 4 files changed, 46 insertions(+), 46 deletions(-) diff --git a/packages/connect/src/device/DeviceList.ts b/packages/connect/src/device/DeviceList.ts index 81731ecfa27..86277a773dc 100644 --- a/packages/connect/src/device/DeviceList.ts +++ b/packages/connect/src/device/DeviceList.ts @@ -120,15 +120,11 @@ export class DeviceList extends TypedEmitter implements IDevic const transportLogger = initLog('@trezor/transport', debug); - // todo: this should be passed from above - const abortController = new AbortController(); - this.authPenaltyManager = createAuthPenaltyManager(priority); this.transportCommonArgs = { messages, logger: transportLogger, - signal: abortController.signal, - _sessionsBackgroundUrl, + sessionsBackgroundUrl: _sessionsBackgroundUrl, }; this.transports = [ @@ -338,9 +334,7 @@ export class DeviceList extends TypedEmitter implements IDevic } private async selectTransport([transport, ...rest]: Transport[]): Promise { - const result = await transport.init({ - sessionsBackgroundUrl: this.transportCommonArgs._sessionsBackgroundUrl, - }); + const result = await transport.init(); if (result.success) return transport; else if (rest.length) return this.selectTransport(rest); else throw new Error(result.error); diff --git a/packages/transport/src/transports/abstract.ts b/packages/transport/src/transports/abstract.ts index f1282bdadd3..392d799cf0d 100644 --- a/packages/transport/src/transports/abstract.ts +++ b/packages/transport/src/transports/abstract.ts @@ -179,11 +179,7 @@ export abstract class AbstractTransport extends TransportEmitter { /** * Tries to initiate transport. Transport might not be available e.g. bridge not running. */ - abstract init( - params?: AbortableParam & { - sessionsBackgroundUrl?: string; - }, - ): AsyncResultWithTypedError< + abstract init(params?: AbortableParam): AsyncResultWithTypedError< undefined, // webusb only | typeof ERRORS.SESSION_BACKGROUND_TIMEOUT diff --git a/packages/transport/src/transports/abstractApi.ts b/packages/transport/src/transports/abstractApi.ts index 8bf1886b385..c194101ca53 100644 --- a/packages/transport/src/transports/abstractApi.ts +++ b/packages/transport/src/transports/abstractApi.ts @@ -34,24 +34,27 @@ export abstract class AbstractApiTransport extends AbstractTransport { this.api = api; } - public init(_args: AbstractTransportMethodParams<'init'>) { - return this.scheduleAction(async () => { - // in nodeusb there is no synchronization yet. this is a followup and needs to be decided - // so far, sessionsClient has direct access to sessionBackground - this.sessionsClient.init({ - requestFn: args => this.sessionsBackground.handleMessage(args), - registerBackgroundCallbacks: () => {}, - }); + public init({ signal }: AbstractTransportMethodParams<'init'> = {}) { + return this.scheduleAction( + async () => { + // in nodeusb there is no synchronization yet. this is a followup and needs to be decided + // so far, sessionsClient has direct access to sessionBackground + this.sessionsClient.init({ + requestFn: args => this.sessionsBackground.handleMessage(args), + registerBackgroundCallbacks: () => {}, + }); - this.sessionsBackground.on('descriptors', descriptors => { - this.sessionsClient.emit('descriptors', descriptors); - }); + this.sessionsBackground.on('descriptors', descriptors => { + this.sessionsClient.emit('descriptors', descriptors); + }); - const handshakeRes = await this.sessionsClient.handshake(); - this.stopped = !handshakeRes.success; + const handshakeRes = await this.sessionsClient.handshake(); + this.stopped = !handshakeRes.success; - return handshakeRes; - }); + return handshakeRes; + }, + { signal }, + ); } public listen() { diff --git a/packages/transport/src/transports/webusb.browser.ts b/packages/transport/src/transports/webusb.browser.ts index 2cebdc63da9..05283b2f56e 100644 --- a/packages/transport/src/transports/webusb.browser.ts +++ b/packages/transport/src/transports/webusb.browser.ts @@ -1,9 +1,11 @@ -import { AbstractTransportParams } from './abstract'; +import { AbstractTransportMethodParams, AbstractTransportParams } from './abstract'; import { AbstractApiTransport } from './abstractApi'; import { UsbApi } from '../api/usb'; import { initBackgroundInBrowser } from '../sessions/background-browser'; +type WebUsbTransportParams = AbstractTransportParams & { sessionsBackgroundUrl?: string }; + /** * WebUsbTransport * - chrome supported @@ -12,9 +14,9 @@ import { initBackgroundInBrowser } from '../sessions/background-browser'; export class WebUsbTransport extends AbstractApiTransport { public name = 'WebUsbTransport' as const; - constructor(params: AbstractTransportParams) { - const { messages, logger } = params; + private readonly sessionsBackgroundUrl; + constructor({ messages, logger, sessionsBackgroundUrl }: WebUsbTransportParams) { super({ messages, api: new UsbApi({ @@ -23,23 +25,28 @@ export class WebUsbTransport extends AbstractApiTransport { }), logger, }); + this.sessionsBackgroundUrl = sessionsBackgroundUrl; } - public init({ sessionsBackgroundUrl }: { sessionsBackgroundUrl: string }) { - return this.scheduleAction(async () => { - const { requestFn, registerBackgroundCallbacks } = - await initBackgroundInBrowser(sessionsBackgroundUrl); - // sessions client initiated with a request fn facilitating communication with a session backend (shared worker in case of webusb) - this.sessionsClient.init({ - requestFn, - registerBackgroundCallbacks, - }); - - const handshakeRes = await this.sessionsClient.handshake(); - this.stopped = !handshakeRes.success; - - return handshakeRes; - }); + public init({ signal }: AbstractTransportMethodParams<'init'> = {}) { + return this.scheduleAction( + async () => { + const { sessionsBackgroundUrl } = this; + const { requestFn, registerBackgroundCallbacks } = + await initBackgroundInBrowser(sessionsBackgroundUrl); + // sessions client initiated with a request fn facilitating communication with a session backend (shared worker in case of webusb) + this.sessionsClient.init({ + requestFn, + registerBackgroundCallbacks, + }); + + const handshakeRes = await this.sessionsClient.handshake(); + this.stopped = !handshakeRes.success; + + return handshakeRes; + }, + { signal }, + ); } public listen() {