Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
karliatto committed Oct 21, 2024
1 parent 324ba30 commit ab19949
Show file tree
Hide file tree
Showing 7 changed files with 345 additions and 8 deletions.
8 changes: 4 additions & 4 deletions packages/connect-iframe/webpack/base.webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const config: webpack.Configuration = {
// TODO: we are not using contenthash here because we want to use that worker from
// different environments (iframe, popup, connect-web, etc.) and we would not know the
// name of the file.
filename: './workers/shared-logger-worker.js',
filename: '/workers/shared-logger-worker.js',
},
},
{
Expand All @@ -47,21 +47,21 @@ export const config: webpack.Configuration = {
test: /\workers\/blockbook\/index/i,
loader: 'worker-loader',
options: {
filename: './workers/blockbook-worker.[contenthash].js',
filename: '/workers/blockbook-worker.[contenthash].js',
},
},
{
test: /\workers\/ripple\/index/i,
loader: 'worker-loader',
options: {
filename: './workers/ripple-worker.[contenthash].js',
filename: '/workers/ripple-worker.[contenthash].js',
},
},
{
test: /\workers\/blockfrost\/index/i,
loader: 'worker-loader',
options: {
filename: './workers/blockfrost-worker.[contenthash].js',
filename: '/workers/blockfrost-worker.[contenthash].js',
},
},
{
Expand Down
2 changes: 1 addition & 1 deletion packages/connect-iframe/webpack/core.webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const config: webpack.Configuration = {
output: {
filename: 'js/[name].js',
path: DIST,
publicPath: './',
publicPath: './static/connect',
library: {
type: 'module',
},
Expand Down
299 changes: 299 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,299 @@
import EventEmitter from 'events';

// NOTE: @trezor/connect part is intentionally not imported from the index due to NormalReplacementPlugin
// in packages/suite-build/configs/web.webpack.config.ts
import * as ERRORS from '@trezor/connect/src/constants/errors';
import {
POPUP,
IFRAME,
UI,
UI_EVENT,
DEVICE_EVENT,
RESPONSE_EVENT,
TRANSPORT_EVENT,
BLOCKCHAIN_EVENT,
createErrorMessage,
UiResponseEvent,
CoreEventMessage,
CallMethodPayload,
CORE_EVENT,
} from '@trezor/connect/src/events';
import type { ConnectSettings, DeviceIdentity, Manifest } from '@trezor/connect/src/types';
import { ConnectFactoryDependencies, factory } from '@trezor/connect/src/factory';
import { Log, initLog } from '@trezor/connect/src/utils/debug';
import { DeferredManager, createDeferredManager } from '@trezor/utils/src/createDeferredManager';

import webUSBButton from '../webusb/button';
import { parseConnectSettings } from '../connectSettings';

export class CoreInModule implements ConnectFactoryDependencies {
public eventEmitter = new EventEmitter();
protected _settings: ConnectSettings;

private _log: Log;
private _coreManager?: any;
private _messagePromises: DeferredManager<{
id: number;
success: boolean;
payload: any;
device?: DeviceIdentity;
}>;

private readonly boundOnCoreEvent = this.onCoreEvent.bind(this);

public constructor() {
this._settings = parseConnectSettings();
this._log = initLog('@trezor/connect-web');
this._messagePromises = createDeferredManager({ initialId: 1 });
}

private async initCoreManager() {
const connectSrc = this._settings.connectSrc;
const { initCoreState, initTransport } = await import(
/* webpackIgnore: true */ `${connectSrc}js/core.js`
).catch(_err => {
this._log.error('_err', _err);
});

if (!initCoreState) return;

if (initTransport) {
this._log.debug('initiating transport with settings: ', this._settings);
await initTransport(this._settings);
}

this._coreManager = initCoreState();
return this._coreManager;
}

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

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

return Promise.resolve(undefined);
}

public cancel(error?: string) {
if (this._coreManager) {
const core = this._coreManager.get();
if (!core) {
throw ERRORS.TypedError('Runtime', 'postMessage: _core not found');
}

core.handleMessage({
type: POPUP.CLOSED,
payload: error ? { error } : null,
});
}
}

// handle message received from Core
private onCoreEvent(message: CoreEventMessage) {
const { event, type, payload } = message;

if (type === UI.REQUEST_UI_WINDOW) {
this._coreManager.get()?.handleMessage({ type: POPUP.HANDSHAKE });

return;
}

if (type === POPUP.CANCEL_POPUP_REQUEST) return;

switch (event) {
case RESPONSE_EVENT: {
const { id = 0, success, device } = message;
const resolved = this._messagePromises.resolve(id, {
id,
success,
payload,
device,
});
if (!resolved) this._log.warn(`Unknown message id ${id}`);
break;
}
case DEVICE_EVENT:
// pass DEVICE event up to html
this.eventEmitter.emit(event, message);
this.eventEmitter.emit(type, payload); // DEVICE_EVENT also emit single events (connect/disconnect...)
break;

case TRANSPORT_EVENT:
this.eventEmitter.emit(event, message);
this.eventEmitter.emit(type, payload);
break;

case BLOCKCHAIN_EVENT:
this.eventEmitter.emit(event, message);
this.eventEmitter.emit(type, payload);
break;

case UI_EVENT:
// pass UI event up
this.eventEmitter.emit(event, message);
this.eventEmitter.emit(type, payload);
break;

default:
this._log.warn('Undefined message', event, message);
}
}

public async init(settings: Partial<ConnectSettings> = {}) {
if (this._coreManager && (this._coreManager.get() || this._coreManager.getPending())) {
throw ERRORS.TypedError('Init_AlreadyInitialized');
}

this._settings = parseConnectSettings({ ...this._settings, ...settings });

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

// defaults for connect-web
if (!this._settings.transports?.length) {
this._settings.transports = ['BridgeTransport', 'WebUsbTransport'];
}

if (!this._coreManager) {
this._coreManager = await this.initCoreManager();
await this._coreManager.getOrInit(this._settings, this.boundOnCoreEvent);
}

this._log.enabled = !!this._settings.debug;
}

initSettings = (settings: Partial<ConnectSettings> = {}) => {
this._settings = parseConnectSettings({ ...this._settings, ...settings, popup: false });

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

if (!this._settings.transports?.length) {
// default fallback for node
this._settings.transports = ['BridgeTransport'];
}
};

public async initCore() {
this.initSettings({ lazyLoad: false });

return this._coreManager.getOrInit(this._settings, this.boundOnCoreEvent);
}

public async call(params: CallMethodPayload) {
let core;
try {
core =
this._coreManager.get() ??
(await this._coreManager.getPending()) ??
(await this.initCore());
} catch (error) {
return createErrorMessage(error);
}

try {
const { promiseId, promise } = this._messagePromises.create();
core.handleMessage({
type: IFRAME.CALL,
payload: params,
id: promiseId,
});
const response = await promise;

return response ?? createErrorMessage(ERRORS.TypedError('Method_NoResponse'));
} catch (error) {
this._log.error('call', error);

return createErrorMessage(error);
}
}

public uiResponse(response: UiResponseEvent) {
const core = this._coreManager.get();
if (!core) {
throw ERRORS.TypedError('Init_NotInitialized');
}
core.handleMessage(response);
}

public renderWebUSBButton(className?: string) {
webUSBButton(className, this._settings.webusbSrc);
}

public async requestLogin(params: any) {
if (typeof params.callback === 'function') {
const { callback } = params;
const core = this._coreManager.get();

// TODO: set message listener only if _core is loaded correctly
const loginChallengeListener = async (event: MessageEvent<CoreEventMessage>) => {
const { data } = event;
if (data && data.type === UI.LOGIN_CHALLENGE_REQUEST) {
try {
const payload = await callback();
core?.handleMessage({
type: UI.LOGIN_CHALLENGE_RESPONSE,
payload,
});
} catch (error) {
core?.handleMessage({
type: UI.LOGIN_CHALLENGE_RESPONSE,
payload: error.message,
});
}
}
};

core?.on(CORE_EVENT, loginChallengeListener);
const response = await this.call({
method: 'requestLogin',
...params,
asyncChallenge: true,
callback: null,
});
core?.removeListener(CORE_EVENT, loginChallengeListener);

return response;
}

return this.call({ method: 'requestLogin', ...params });
}

public disableWebUSB() {
throw ERRORS.TypedError('Method_InvalidPackage');
}

public async requestWebUSBDevice() {
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),
});
31 changes: 31 additions & 0 deletions packages/connect-web/src/module/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { factory } from '@trezor/connect/src/factory';
import { TrezorConnectDynamic } from '@trezor/connect/src/impl/dynamic';
import { CoreInModule } from '../impl/core-in-module';

const impl = new TrezorConnectDynamic<'core-in-module'>({
implementations: [
{
type: 'core-in-module',
impl: new CoreInModule(),
},
],
getInitTarget: () => 'core-in-module',
handleErrorFallback: async () => false,
});

const TrezorConnect = factory({
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),
});

export default TrezorConnect;
export * from '@trezor/connect/src/exports';
4 changes: 2 additions & 2 deletions packages/connect/src/data/DataManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ export class DataManager {
if (!withAssets) return;

const assetPromises = config.assets.map(async asset => {
const json = await httpRequest(`${asset.url}${ts}`, 'json');
const json = await httpRequest(`${settings.connectSrc}${asset.url}${ts}`, 'json');
this.assets[asset.name] = json;
});
await Promise.all(assetPromises);

this.messages = await httpRequest(`${config.messages}${ts}`, 'json');
this.messages = await httpRequest(`${settings.connectSrc}${config.messages}${ts}`, 'json');

// parse bridge JSON
parseBridgeJSON(this.assets.bridge);
Expand Down
Loading

0 comments on commit ab19949

Please sign in to comment.