Skip to content

Commit

Permalink
wip: core-in-whatever
Browse files Browse the repository at this point in the history
  • Loading branch information
mroz22 committed Oct 7, 2024
1 parent 86667cc commit 0042288
Show file tree
Hide file tree
Showing 7 changed files with 578 additions and 149 deletions.
134 changes: 134 additions & 0 deletions packages/connect-web/src/impl/core-in-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import EventEmitter from 'events';

import {
POPUP,
IFRAME,
UI_EVENT,
createErrorMessage,
UiResponseEvent,
CallMethodPayload,
CallMethodAnyResponse,
} from '@trezor/connect';
import { Login } from '@trezor/connect/src/types/api/requestLogin';

import * as ERRORS from '@trezor/connect/src/constants/errors';
import type {
ConnectSettings,
ConnectSettingsPublic,
Manifest,
Response,
} from '@trezor/connect/src/types';
import { ConnectFactoryDependencies, factory } from '@trezor/connect/src/factory';
import { initLog, setLogWriter, LogMessage, LogWriter, Log } from '@trezor/connect/src/utils/debug';

import { parseConnectSettings } from '../connectSettings';
import { createDeferred } from '@trezor/utils';

import { coreManager } from '@trezor/connect/src/core';
/**
* Base class for CoreInPopup methods for TrezorConnect factory.
* This implementation is directly used here in connect-web, but it is also extended in connect-webextension.
*/
export class CoreInModule implements ConnectFactoryDependencies {
public eventEmitter = new EventEmitter();
protected _settings: ConnectSettings;

protected logger: Log;

public constructor() {
this._settings = parseConnectSettings();
this.logger = initLog('@trezor/connect-web');
}

private logWriterFactory(): LogWriter {
return {
add: (message: LogMessage) => {
// popupManager.channel.postMessage(
// {
// event: UI_EVENT,
// type: IFRAME.LOG,
// payload: message,
// },
// { usePromise: false, useQueue: true },
// );
},
};
}

public manifest(data: Manifest) {
this._settings = parseConnectSettings({
...this._settings,
manifest: data,
});
}

public dispose() {
this.eventEmitter.removeAllListeners();
this._settings = parseConnectSettings();

return Promise.resolve(undefined);
}

public cancel(error?: string) {}

public init(settings: Partial<ConnectSettingsPublic> = {}): Promise<void> {
this._settings = parseConnectSettings({
settings,
});

this.logger.enabled = !!settings.debug;

if (!this._settings.manifest) {
throw ERRORS.TypedError('Init_ManifestMissing');
}

this.logger.debug('initiated');

return Promise.resolve();
}

/**
*/
public async call(params: CallMethodPayload): Promise<CallMethodAnyResponse> {

Check failure on line 92 in packages/connect-web/src/impl/core-in-module.ts

View workflow job for this annotation

GitHub Actions / Linting and formatting

Async method 'call' has no 'await' expression
this.logger.debug('call', params);
}

uiResponse(response: UiResponseEvent) {
const { type, payload } = response;
}

renderWebUSBButton() {}

requestLogin(): Response<Login> {
// todo: not supported yet
throw ERRORS.TypedError('Method_InvalidPackage');
}

disableWebUSB() {
// todo: not supported yet, probably not needed
throw ERRORS.TypedError('Method_InvalidPackage');
}

requestWebUSBDevice() {
// not needed - webusb pairing happens in popup
throw ERRORS.TypedError('Method_InvalidPackage');
}
}

const impl = new CoreInModule();

// Exported to enable using directly
export const TrezorConnect = factory({
// Bind all methods due to shadowing `this`
eventEmitter: impl.eventEmitter,
init: impl.init.bind(impl),
call: impl.call.bind(impl),
manifest: impl.manifest.bind(impl),
requestLogin: impl.requestLogin.bind(impl),
uiResponse: impl.uiResponse.bind(impl),
renderWebUSBButton: impl.renderWebUSBButton.bind(impl),
disableWebUSB: impl.disableWebUSB.bind(impl),
requestWebUSBDevice: impl.requestWebUSBDevice.bind(impl),
cancel: impl.cancel.bind(impl),
dispose: impl.dispose.bind(impl),
});
158 changes: 14 additions & 144 deletions packages/connect-web/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,155 +1,25 @@
import { ConnectFactoryDependencies, factory } from '@trezor/connect/src/factory';
import { factory } from '@trezor/connect/src/factory';
import { CoreInIframe } from './impl/core-in-iframe';
import { CoreInPopup } from './impl/core-in-popup';
import ProxyEventEmitter from './utils/proxy-event-emitter';
import type { ConnectSettings, ConnectSettingsPublic, Manifest } from '@trezor/connect/src/types';
import EventEmitter from 'events';
import { CallMethodPayload } from '@trezor/connect/src/events';
import { getEnv } from './connectSettings';

type TrezorConnectType = 'core-in-popup' | 'iframe';

const IFRAME_ERRORS = ['Init_IframeBlocked', 'Init_IframeTimeout', 'Transport_Missing'];

/**
* Implementation of TrezorConnect that can dynamically switch between iframe and core-in-popup implementations
*/
export class TrezorConnectDynamicImpl implements ConnectFactoryDependencies {
public eventEmitter: EventEmitter;

private currentTarget: TrezorConnectType = 'iframe';
private coreInIframeImpl: CoreInIframe;
private coreInPopupImpl: CoreInPopup;

private lastSettings?: Partial<ConnectSettings>;

public constructor() {
this.coreInIframeImpl = new CoreInIframe();
this.coreInPopupImpl = new CoreInPopup();
this.eventEmitter = new ProxyEventEmitter([
this.coreInIframeImpl.eventEmitter,
this.coreInPopupImpl.eventEmitter,
]);
}

private getTarget() {
return this.currentTarget === 'iframe' ? this.coreInIframeImpl : this.coreInPopupImpl;
}

private async switchTarget(target: TrezorConnectType) {
if (this.currentTarget === target) {
return;
}

await this.getTarget().dispose();
this.currentTarget = target;
await this.getTarget().init(this.lastSettings);
}

public manifest(manifest: Manifest) {
this.lastSettings = {
...this.lastSettings,
manifest,
};

this.getTarget().manifest(manifest);
}

public async init(settings: Partial<ConnectSettingsPublic> = {}) {
const env = getEnv();
if (settings.coreMode === 'iframe' || settings.popup === false || env === 'webextension') {
this.currentTarget = 'iframe';
} else if (settings.coreMode === 'popup') {
this.currentTarget = 'core-in-popup';
} else {
// Default to auto mode with iframe as the first choice
settings.coreMode = 'auto';
this.currentTarget = 'iframe';
}

// Save settings for later use
this.lastSettings = settings;

// Initialize the target
try {
return await this.getTarget().init(settings);
} catch (error) {
// Handle iframe errors by switching to core-in-popup
if (await this.handleErrorFallback(error.code)) {
return await this.getTarget().init(settings);
}

throw error;
}
}

public async call(params: CallMethodPayload) {
const response = await this.getTarget().call(params);
if (!response.success) {
if (await this.handleErrorFallback(response.payload.code)) {
return await this.getTarget().call(params);
}
}

return response;
}

private async handleErrorFallback(errorCode: string) {
// Handle iframe errors by switching to core-in-popup
if (this.lastSettings?.coreMode === 'auto' && IFRAME_ERRORS.includes(errorCode)) {
// Check if WebUSB is available and enabled
const webUsbUnavailableInBrowser = !navigator.usb;
const webUsbDisabledInSettings =
this.lastSettings.transports?.includes('WebUsbTransport') === false ||
this.lastSettings.webusb === false;
if (
errorCode === 'Transport_Missing' &&
(webUsbUnavailableInBrowser || webUsbDisabledInSettings)
) {
// WebUSB not available, no benefit in switching to core-in-popup
return false;
}

await this.switchTarget('core-in-popup');

return true;
}

return false;
}

public requestLogin(params: any) {
return this.getTarget().requestLogin(params);
}

public uiResponse(params: any) {
return this.getTarget().uiResponse(params);
}

public renderWebUSBButton() {
return this.getTarget().renderWebUSBButton();
}

public disableWebUSB() {
return this.getTarget().disableWebUSB();
}

public requestWebUSBDevice() {
return this.getTarget().requestWebUSBDevice();
}

public cancel(error?: string) {
return this.getTarget().cancel(error);
}

public dispose() {
this.eventEmitter.removeAllListeners();

return this.getTarget().dispose();
}
}

const impl = new TrezorConnectDynamicImpl();
const impl = new TrezorConnectDynamicImpl({
impls: [
{
type: 'iframe',
impl: new CoreInIframe(),
},
{
type: 'core-in-popup',
impl: new CoreInPopup(),
},
],
getEnv,
});

const TrezorConnect = factory({
eventEmitter: impl.eventEmitter,
Expand Down
Loading

0 comments on commit 0042288

Please sign in to comment.