Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: sessions backround sharedworker truly shared #14180

Merged
merged 11 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions packages/connect-iframe/webpack/base.webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down
7 changes: 7 additions & 0 deletions packages/connect-iframe/webpack/prod.webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
35 changes: 18 additions & 17 deletions packages/connect/setupJest.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion packages/connect/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -1206,6 +1206,7 @@ export class Core extends EventEmitter {
debug,
messages,
priority,
_sessionsBackgroundUrl,
});
initDeviceList(this.getCoreContext());

Expand Down
4 changes: 4 additions & 0 deletions packages/connect/src/data/connectSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,5 +144,9 @@ export const parseConnectSettings = (input: Partial<ConnectSettings> = {}) => {
settings._extendWebextensionLifetime = input._extendWebextensionLifetime;
}

if (typeof input._sessionsBackgroundUrl === 'string') {
settings._sessionsBackgroundUrl = input._sessionsBackgroundUrl;
}

return settings;
};
9 changes: 3 additions & 6 deletions packages/connect/src/device/DeviceList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const assertDeviceListConnected: (
if (!deviceList.isConnected()) throw ERRORS.TypedError('Transport_Missing');
};

type ConstructorParams = Pick<ConnectSettings, 'priority' | 'debug'> & {
type ConstructorParams = Pick<ConnectSettings, 'priority' | 'debug' | '_sessionsBackgroundUrl'> & {
messages: Record<string, any>;
};
type InitParams = Pick<ConnectSettings, 'pendingTransportEvent' | 'transportReconnect'>;
Expand Down Expand Up @@ -115,19 +115,16 @@ export class DeviceList extends TypedEmitter<DeviceListEvents> implements IDevic
return this.initPromise;
}

constructor({ messages, priority, debug }: ConstructorParams) {
constructor({ messages, priority, debug, _sessionsBackgroundUrl }: ConstructorParams) {
super();

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,
};

this.transports = [
Expand Down
5 changes: 5 additions & 0 deletions packages/connect/src/types/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions packages/transport-bridge/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down
23 changes: 1 addition & 22 deletions packages/transport-native/src/nativeUsb.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,24 @@
import { WebUSB } from '@trezor/react-native-usb';
import {
Transport as AbstractTransport,
AbstractApiTransport,
SessionsClient,
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;

constructor(params: ConstructorParameters<typeof AbstractTransport>[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);
});
mroz22 marked this conversation as resolved.
Show resolved Hide resolved

super({
messages,
api: new UsbApi({
usbInterface: new WebUSB(),
logger,
}),
sessionsClient,
});
this.sessionsBackground = sessionsBackground;
}

public listen() {
this.api.listen();
this.sessionsBackground.dispose();
marekrjpolak marked this conversation as resolved.
Show resolved Hide resolved

return super.listen();
}
Expand Down
41 changes: 31 additions & 10 deletions packages/transport/src/sessions/background-browser.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -13,15 +17,31 @@ 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();
// fetch to validate - failed fetch via SharedWorker constructor does not throw. It even hangs resulting in all kinds of weird behaviors
await fetch(sessionsBackgroundUrl, { method: 'HEAD' }).then(response => {
if (!response.ok) {
throw new Error(
`Failed to fetch sessions-background SharedWorker from url: ${sessionsBackgroundUrl}`,
);
}
});
const background = new SharedWorker(sessionsBackgroundUrl, {
name: '@trezor/connect-web transport sessions worker',
});

const requestFn = (params: Parameters<SessionsBackground['handleMessage']>[0]) =>
new Promise<Awaited<ReturnType<SessionsBackground['handleMessage']>>>(resolve => {
const onmessage = (
message: MessageEvent<Awaited<ReturnType<SessionsBackground['handleMessage']>>>,
) => {
const requestFn: SessionsBackground['handleMessage'] = (
params: Parameters<SessionsBackground['handleMessage']>[0],
) =>
new Promise(resolve => {
const onmessage = (message: MessageEvent<any>) => {
if (params.id === message.data.id) {
resolve(message.data);
background.port.removeEventListener('message', onmessage);
Expand Down Expand Up @@ -62,7 +82,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();
Expand Down
30 changes: 17 additions & 13 deletions packages/transport/src/sessions/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
Expand All @@ -51,31 +55,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' });
Expand Down
Loading
Loading