Skip to content

Commit

Permalink
"wip: dummy POC 'popup in suite-desktop'"
Browse files Browse the repository at this point in the history
  • Loading branch information
mroz22 committed Oct 2, 2024
1 parent 03fd53f commit dbdfa54
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 5 deletions.
132 changes: 132 additions & 0 deletions packages/connect-web/src/impl/core-in-suite-desktop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import EventEmitter from 'events';

// NOTE: @trezor/connect part is intentionally not imported from the index so we do include the whole library.
import {
IFRAME,
UiResponseEvent,
CallMethodPayload,
CallMethodAnyResponse,
} from '@trezor/connect/src/events';
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 { parseConnectSettings } from '../connectSettings';
import { Login } from '@trezor/connect/src/types/api/requestLogin';
// import { createDeferred } from '@trezor/utils';

const ws = new WebSocket('ws://localhost:8090');

ws.addEventListener('error', console.error);

ws.addEventListener('open', function open() {});

/**
* 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 CoreInSuiteDesktop implements ConnectFactoryDependencies {
public eventEmitter = new EventEmitter();
protected _settings: ConnectSettings;

public constructor() {
this._settings = parseConnectSettings();
}

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> {
const newSettings = parseConnectSettings({
...this._settings,
...settings,
});

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

return Promise.resolve();
}

/**
* 1. opens popup
* 2. sends request to popup where the request is handled by core
* 3. returns response
*/
public async call(params: CallMethodPayload): Promise<CallMethodAnyResponse> {
ws.send(
JSON.stringify({
type: IFRAME.CALL,
payload: params,
}),
);

return new Promise(resolve => {
ws.addEventListener('message', function message(event) {
console.log('received: %s', event.data);
resolve(JSON.parse(event.data));
});
});
}

uiResponse(_response: UiResponseEvent) {
// this shouldn't be needed, ui response should be handled in suite-desktop
throw ERRORS.TypedError('Method_InvalidPackage');
}

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 CoreInSuiteDesktop();

// 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),
});
14 changes: 10 additions & 4 deletions packages/connect-web/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ConnectFactoryDependencies, factory } from '@trezor/connect/src/factory';
import { CoreInIframe } from './impl/core-in-iframe';
import { CoreInPopup } from './impl/core-in-popup';
import { CoreInSuiteDesktop } from './impl/core-in-suite-desktop';
import ProxyEventEmitter from './utils/proxy-event-emitter';
import type { ConnectSettings, ConnectSettingsPublic, Manifest } from '@trezor/connect/src/types';
import EventEmitter from 'events';
Expand All @@ -20,20 +21,24 @@ export class TrezorConnectDynamicImpl implements ConnectFactoryDependencies {
private currentTarget: TrezorConnectType = 'iframe';
private coreInIframeImpl: CoreInIframe;
private coreInPopupImpl: CoreInPopup;
private coreInSuiteDesktopImpl: CoreInSuiteDesktop;

private lastSettings?: Partial<ConnectSettings>;

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

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

private async switchTarget(target: TrezorConnectType) {
Expand Down Expand Up @@ -86,9 +91,9 @@ export class TrezorConnectDynamicImpl implements ConnectFactoryDependencies {
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);
}
// if (await this.handleErrorFallback(response.payload.code)) {
// return await this.getTarget().call(params);
// }
}

return response;
Expand Down Expand Up @@ -119,6 +124,7 @@ export class TrezorConnectDynamicImpl implements ConnectFactoryDependencies {
}

public requestLogin(params: any) {
// @ts-expect-error
return this.getTarget().requestLogin(params);
}

Expand Down
20 changes: 20 additions & 0 deletions packages/suite-desktop-core/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,26 @@ const initUi = async ({
mainThreadEmitter,
});

mainThreadEmitter.on('focus-window', () => {
console.log('====focus====');
let mainWindow = mainWindowProxy.getInstance();
if (!mainWindow || mainWindow.isDestroyed()) {
logger.info('main', 'Main window destroyed, recreating');
mainWindow = createMainWindow(winBounds);
mainWindowProxy.setInstance(mainWindow);
}

app.dock?.show();
if (isMacOs()) app.show();
if (!mainWindow.isVisible()) mainWindow.show();
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
});

mainThreadEmitter.on('blur-window', () => {
app.hide();
});

app.on('second-instance', () => {
// Someone tried to run a second instance, we should focus our window.
logger.info('main', 'Second instance detected, focusing main window');
Expand Down
2 changes: 2 additions & 0 deletions packages/suite-desktop-core/src/modules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ interface MainThreadMessages {
'module/request-interceptor': InterceptedEvent;
'module/reset-tor-circuits': Extract<InterceptedEvent, { type: 'CIRCUIT_MISBEHAVING' }>;
'module/tor-status-update': TorStatus;
'focus-window': void;
'blur-window': void;
}
export const mainThreadEmitter = new TypedEmitter<MainThreadMessages>();
export type MainThreadEmitter = typeof mainThreadEmitter;
Expand Down
53 changes: 52 additions & 1 deletion packages/suite-desktop-core/src/modules/trezor-connect.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ipcMain } from 'electron';
import { WebSocketServer } from 'ws';

import TrezorConnect from '@trezor/connect';
import { createIpcProxyHandler, IpcProxyHandlerOptions } from '@trezor/ipc-proxy';
Expand All @@ -7,7 +8,7 @@ import type { Module } from './index';

export const SERVICE_NAME = '@trezor/connect';

export const init: Module = ({ store }) => {
export const init: Module = ({ store, mainThreadEmitter }) => {
const { logger } = global;
logger.info(SERVICE_NAME, `Starting service`);

Expand All @@ -25,9 +26,59 @@ export const init: Module = ({ store }) => {
onRequest: async (method, params) => {
logger.debug(SERVICE_NAME, `call ${method}`);
if (method === 'init') {
console.log('aprams, params', params);
const response = await TrezorConnect[method](...params);
await setProxy(true);

const wss = new WebSocketServer({
port: 8090,
perMessageDeflate: {
zlibDeflateOptions: {
// See zlib defaults.
chunkSize: 1024,
memLevel: 7,
level: 3,
},
zlibInflateOptions: {
chunkSize: 10 * 1024,
},
// Other options settable:
clientNoContextTakeover: true, // Defaults to negotiated value.
serverNoContextTakeover: true, // Defaults to negotiated value.
serverMaxWindowBits: 10, // Defaults to negotiated value.
// Below options specified as default values.
concurrencyLimit: 10, // Limits zlib concurrency for perf.
threshold: 1024, // Size (in bytes) below which messages
// should not be compressed if context takeover is disabled.
},
});

wss.on('connection', function connection(ws) {
ws.on('error', console.error);

ws.on('message', async function message(data) {
try {
const parsed = JSON.parse(data.toString());
const { method, ...rest } = parsed.payload;
console.log('method', method);
console.log('rest', rest);
// focus renderer window
mainThreadEmitter.emit('focus-window');
// @ts-expect-error
const response = await TrezorConnect[method](rest);
console.log('response', response);
ws.send(JSON.stringify(response));
} catch (err) {
console.log('=== err', err);
} finally {
// blur renderer window
mainThreadEmitter.emit('blur-window');
}
});

ws.send('ack');
});

return response;
}

Expand Down

0 comments on commit dbdfa54

Please sign in to comment.