Skip to content

Commit

Permalink
fixup: rejections removed up to typedCall
Browse files Browse the repository at this point in the history
  • Loading branch information
mroz22 committed Jan 31, 2025
1 parent 2919204 commit 0d12253
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 80 deletions.
2 changes: 1 addition & 1 deletion packages/connect-web/tsconfig.lib.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"extends": "../../tsconfig.lib.json",
"compilerOptions": {
"outDir": "lib",
"target": "es2017",
"target": "es2019",
"types": ["chrome", "w3c-web-usb"]
},
"include": ["./src"],
Expand Down
2 changes: 1 addition & 1 deletion packages/connect-webextension/tsconfig.lib.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"extends": "../../tsconfig.lib.json",
"compilerOptions": {
"outDir": "lib",
"target": "es2017",
"target": "es2019",
"types": ["chrome", "w3c-web-usb"]
},
"include": ["./src"],
Expand Down
2 changes: 1 addition & 1 deletion packages/connect/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -922,7 +922,7 @@ const onPopupClosed = (context: CoreContext, customErrorMessage?: string) => {
deviceList.getAllDevices().forEach(d => {
d.releaseTransportSession(); // clear transportSession on release
if (d.isUsedHere()) {
setOverridePromise(d.interruptionFromUser(error));
setOverridePromise(d.interruptionFromUser(error.toString()));
} else {
const success = uiPromises.resolve({ type: DEVICE.DISCONNECT, payload: undefined });
if (!success) {
Expand Down
12 changes: 6 additions & 6 deletions packages/connect/src/device/Device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export class Device extends TypedEmitter<DeviceEvents> {

private keepTransportSession = false;
public commands?: DeviceCommands;
private cancelableAction?: (err?: Error) => Promise<unknown>;
private cancelableAction?: (err?: string) => Promise<unknown>;

private loaded = false;

Expand Down Expand Up @@ -447,15 +447,15 @@ export class Device extends TypedEmitter<DeviceEvents> {
}

if (this.runPromise) {
await this.interruptionFromUser(error);
await this.interruptionFromUser(error.toString());
}
if (this.releasePromise) {
await this.releasePromise;
}
}

setCancelableAction(callback: NonNullable<typeof this.cancelableAction>) {
this.cancelableAction = (e?: Error) =>
this.cancelableAction = (e?: string) =>
callback(e)
.catch(e2 => {
_log.debug('cancelableAction error', e2);
Expand All @@ -469,15 +469,15 @@ export class Device extends TypedEmitter<DeviceEvents> {
this.cancelableAction = undefined;
}

async interruptionFromUser(error: Error) {
async interruptionFromUser(error: string) {
_log.debug('interruptionFromUser');

await this.cancelableAction?.(error);
await this.commands?.cancel();

if (this.runPromise) {
// reject inner defer
this.runPromise.reject(error);
this.runPromise.reject(new Error(error));
delete this.runPromise;
}
}
Expand Down Expand Up @@ -1046,7 +1046,7 @@ export class Device extends TypedEmitter<DeviceEvents> {

this.emitLifecycle(DEVICE.DISCONNECT);

// return this.interruptionFromUser(ERRORS.TypedError('Device_Disconnected'));
// return this.ERRORS.TypedError('Device_Disconnected'));
}

isBootloader() {
Expand Down
126 changes: 73 additions & 53 deletions packages/connect/src/device/DeviceCommands.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// original file https://github.com/trezor/connect/blob/develop/src/js/device/DeviceCommands.js

import { ErrorGeneric } from '@trezor/transport/src/types';
import { ReadWriteError } from '@trezor/transport/src/transports/abstract';
import { MessagesSchema as Messages } from '@trezor/protobuf';
import { Assert } from '@trezor/schema-utils';
import { Session, Transport } from '@trezor/transport';
import { ReadWriteError } from '@trezor/transport/src/transports/abstract';
import { createTimeoutPromise, versionUtils } from '@trezor/utils';

import { ERRORS } from '../constants';
Expand Down Expand Up @@ -281,23 +280,6 @@ export class DeviceCommands {
return this._getAddress();
}

// Sends an async message to the opened device.
private async call(type: MessageKey, msg: DefaultPayloadMessage['message'] = {}) {
logger.debug('Sending', type, filterForLog(type, msg));

this.callPromise = this.transport.call({
session: this.transportSession,
name: type,
data: msg,
protocol: this.device.protocol,
});

const res = await this.callPromise;
this.callPromise = undefined;

return res;
}

typedCall<T extends MessageKey, R extends MessageKey[]>(
type: T,
resType: R,
Expand All @@ -322,13 +304,17 @@ export class DeviceCommands {

const response = await this._commonCall(type, msg);

if ('isTransportError' in response) {
throw new Error(response.error, { cause: 'transport-error' });
if (!response.success) {
if (response.isTransportError) {
throw new Error(response.error, { cause: 'transport-error' });
} else {
throw new Error(response.error);
}
}

const splitResTypes = Array.isArray(resType) ? resType : resType.split('|');
if (splitResTypes.includes(response.type)) {
return response;
if (splitResTypes.includes(response.payload.type)) {
return response.payload;
}
// handle possible race condition
// Bridge may have some unread message in buffer, read it
Expand All @@ -351,20 +337,33 @@ export class DeviceCommands {

throw ERRORS.TypedError(
'Runtime',
`assertType: Response of unexpected type: ${response.type}. Should be ${resType}`,
`assertType: Response of unexpected type: ${response.payload.type}. Should be ${resType}`,
);
}

async _commonCall(
type: MessageKey,
msg?: DefaultPayloadMessage['message'],
msg: DefaultPayloadMessage['message'] = {},
): Promise<
DefaultPayloadMessage | (ErrorGeneric<ReadWriteError> & { isTransportError: true })
| { success: true; payload: DefaultPayloadMessage }
| { success: false; error: any; isTransportError: false }
| { success: false; error: ReadWriteError; message?: string; isTransportError: true }
> {
if (this.disposed) {
throw ERRORS.TypedError('Runtime', 'typedCall: DeviceCommands already disposed');
}
const res = await this.call(type, msg);

logger.debug('Sending', type, filterForLog(type, msg));

this.callPromise = this.transport.call({
session: this.transportSession,
name: type,
data: msg,
protocol: this.device.protocol,
});

const res = await this.callPromise;
this.callPromise = undefined;

if (!res.success) {
logger.warn(
Expand All @@ -389,7 +388,13 @@ export class DeviceCommands {
return this._filterCommonTypes(res.payload as DefaultPayloadMessage);
}

_filterCommonTypes(res: DefaultPayloadMessage) {
_filterCommonTypes(
res: DefaultPayloadMessage,
): Promise<
| { success: true; payload: DefaultPayloadMessage }
| { success: false; error: any; isTransportError: false }
| { success: false; error: ReadWriteError; message?: string; isTransportError: true }
> {
this.device.clearCancelableAction();

if (res.type === 'Failure') {
Expand All @@ -409,16 +414,17 @@ export class DeviceCommands {
}

// pass code and message from firmware error
return Promise.reject(
new ERRORS.TrezorError(
(code as any) || 'Failure_UnknownCode',
message || 'Failure_UnknownMessage',
),
);
return Promise.resolve({
success: false,
// todo: check renaming error vs code
error: code || 'Failure_UnknownCode',
message: message || 'Failure_UnknownMessage',
isTransportError: false,
});
}

if (res.type === 'Features') {
return Promise.resolve(res);
return Promise.resolve({ success: true, payload: res });
}

if (res.type === 'ButtonRequest') {
Expand All @@ -434,38 +440,52 @@ export class DeviceCommands {
}

if (res.type === 'PinMatrixRequest') {
return promptPin(this.device, res.message.type).then(
pin =>
this._commonCall('PinMatrixAck', { pin }).then(response => {
return promptPin(this.device, res.message.type).then(promptRes => {
if (!promptRes.success) {
return promptRes;
}

return this._commonCall('PinMatrixAck', { pin: promptRes.payload }).then(
response => {
if (!response.success) {
return response;
}
if (!this.device.features.unlocked) {
// reload features to after successful PIN
return this.device.getFeatures().then(() => response);
}

return response;
}),
error => Promise.reject(error),
);
},
);
});
}

// { value, passphraseOnDevice }
if (res.type === 'PassphraseRequest') {
return promptPassphrase(this.device).then(
({ value, passphraseOnDevice }) =>
!passphraseOnDevice
? this._commonCall('PassphraseAck', { passphrase: value.normalize('NFKD') })
: this._commonCall('PassphraseAck', { on_device: true }),
error => Promise.reject(error),
);
return promptPassphrase(this.device).then(promptRes => {
if (!promptRes.success) {
return promptRes;
}
const { value, passphraseOnDevice } = promptRes.payload;

return !passphraseOnDevice
? this._commonCall('PassphraseAck', { passphrase: value.normalize('NFKD') })
: this._commonCall('PassphraseAck', { on_device: true });
});
}

if (res.type === 'WordRequest') {
return promptWord(this.device, res.message.type).then(
word => this._commonCall('WordAck', { word }),
error => Promise.reject(error),
);
return promptWord(this.device, res.message.type).then(promptRes => {
if (!promptRes.success) {
return promptRes;
}

return this._commonCall('WordAck', { word: promptRes.payload });
});
}

return Promise.resolve(res);
return Promise.resolve({ success: true, payload: res });
}

private async _getAddress() {
Expand Down
52 changes: 35 additions & 17 deletions packages/connect/src/device/prompts.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Messages, TRANSPORT_ERROR } from '@trezor/transport';
import { ReadWriteError } from '@trezor/transport/src/transports/abstract';

import { ERRORS } from '../constants';
import { DEVICE } from '../events';
import type { Device, DeviceEvents } from './Device';

export type PromptCallback<T> = (response: T | null, error?: Error) => void;
export type PromptCallback<T> = (response: T | null, error?: string) => void;

type PromptEvents = typeof DEVICE.PIN | typeof DEVICE.PASSPHRASE | typeof DEVICE.WORD;
// infer all args of Device.emit but one (callback)
Expand Down Expand Up @@ -40,20 +40,38 @@ export const cancelPrompt = (device: Device, expectResponse = true) => {
return expectResponse ? device.transport.call(cancelArgs) : device.transport.send(cancelArgs);
};

const prompt = <E extends PromptEvents>(event: E, ...[device, ...args]: DeviceEventArgs<E>) =>
type PromptReturnType<E extends PromptEvents> = Promise<
| { success: true; payload: NonNullable<Parameters<DeviceEventCallback<E>>[0]> }
| ({ success: false } & (
| { error: string; isTransportError: false }
| { error: ReadWriteError; isTransportError: true }
))
>;

const prompt = <E extends PromptEvents>(
event: E,
...[device, ...args]: DeviceEventArgs<E>
): PromptReturnType<E> =>
// return non nullable first arg of PromptCallback<E>
new Promise<NonNullable<Parameters<DeviceEventCallback<E>>[0]>>((resolve, reject) => {
const cancelAndReject = (error?: Error) =>
cancelPrompt(device).then(onCancel =>
reject(
error ||
new Error(
onCancel.success
? (onCancel.payload?.message.message as string)
: onCancel.error,
),
),
);
new Promise(resolve => {
const cancelAndReject = (error?: string) =>
cancelPrompt(device).then(cancelResponse => {
if (cancelResponse.success) {
return resolve({
success: false,
error: error || (cancelResponse.payload?.message.message as string),
isTransportError: false,
});
}

resolve({
success: false,
error: error || cancelResponse.error,
// todo todo:
// @ts-expect-error
isTransportError: true,
});
});

if (device.listenerCount(event) > 0) {
device.setCancelableAction(cancelAndReject);
Expand All @@ -63,7 +81,7 @@ const prompt = <E extends PromptEvents>(event: E, ...[device, ...args]: DeviceEv
if (error || response == null) {
cancelAndReject(error);
} else {
resolve(response);
resolve({ success: true, payload: response });
}
};

Expand All @@ -73,7 +91,7 @@ const prompt = <E extends PromptEvents>(event: E, ...[device, ...args]: DeviceEv

device.emit(...emitArgs);
} else {
cancelAndReject(ERRORS.TypedError('Runtime', `${event} callback not configured`));
cancelAndReject(`${event} callback not configured`);
}
});
export const promptPassphrase = (device: Device) => prompt(DEVICE.PASSPHRASE, device);
Expand Down
2 changes: 1 addition & 1 deletion packages/transport/src/types/apiCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface Success<T> {
payload: T;
}

export type ErrorGeneric<ErrorType> = {
type ErrorGeneric<ErrorType> = {
success: false;
error: ErrorType;
message?: string;
Expand Down

0 comments on commit 0d12253

Please sign in to comment.