From 2d731b7bcd422a4e23d648421db58f555b556718 Mon Sep 17 00:00:00 2001 From: Marek Polak Date: Wed, 5 Feb 2025 16:52:59 +0100 Subject: [PATCH 1/5] refactor(utils): add abort to resolveAfter --- packages/utils/src/resolveAfter.ts | 14 +++++++++----- packages/utils/tests/resolveAfter.test.ts | 7 ++++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/utils/src/resolveAfter.ts b/packages/utils/src/resolveAfter.ts index a3eed144508..002fa37a985 100644 --- a/packages/utils/src/resolveAfter.ts +++ b/packages/utils/src/resolveAfter.ts @@ -1,11 +1,15 @@ import { createDeferred } from './createDeferred'; -export const resolveAfter = (msec: number, value?: T) => { +export const resolveAfter = (msec: number, signal?: AbortSignal, value?: T) => { const { promise, reject, resolve } = createDeferred(); const timeout = setTimeout(resolve, msec, value); - return { - promise: promise.finally(() => clearTimeout(timeout)), - reject, - }; + const onAbort = () => reject(signal?.reason); + signal?.addEventListener('abort', onAbort); + if (signal?.aborted) onAbort(); + + return promise.finally(() => { + clearTimeout(timeout); + signal?.removeEventListener('abort', onAbort); + }); }; diff --git a/packages/utils/tests/resolveAfter.test.ts b/packages/utils/tests/resolveAfter.test.ts index e4d9bc53545..e63e928125b 100644 --- a/packages/utils/tests/resolveAfter.test.ts +++ b/packages/utils/tests/resolveAfter.test.ts @@ -4,7 +4,7 @@ describe('resolveAfter', () => { jest.useFakeTimers(); it('resolves after specified time', async () => { - const { promise } = resolveAfter(200, 'foo'); + const promise = resolveAfter(200, undefined, 'foo'); jest.advanceTimersByTime(200); @@ -12,10 +12,11 @@ describe('resolveAfter', () => { }); it('rejects if the promise is rejected', async () => { - const { promise, reject } = resolveAfter(200); + const abort = new AbortController(); + const promise = resolveAfter(200, abort.signal); // Reject the promise after 100ms - setTimeout(() => reject(new Error('bar')), 100); + setTimeout(() => abort.abort(new Error('bar')), 100); jest.advanceTimersByTime(100); From 8a46b1c0ab350fd12788c9909bc25e9aee687571 Mon Sep 17 00:00:00 2001 From: Marek Polak Date: Wed, 5 Feb 2025 16:54:26 +0100 Subject: [PATCH 2/5] chore(connect): adjust resolveAfter calls --- packages/connect/src/api/composeTransaction.ts | 4 ++-- packages/connect/src/api/getAccountInfo.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/connect/src/api/composeTransaction.ts b/packages/connect/src/api/composeTransaction.ts index 265bca1758b..e0cde7af22d 100644 --- a/packages/connect/src/api/composeTransaction.ts +++ b/packages/connect/src/api/composeTransaction.ts @@ -301,7 +301,7 @@ export default class ComposeTransaction extends AbstractMethod<'composeTransacti discovery.stop(); if (!discovery.completed) { - await resolveAfter(501).promise; // temporary solution, TODO: immediately resolve will cause "device call in progress" + await resolveAfter(501); // temporary solution, TODO: immediately resolve will cause "device call in progress" } const account = discovery.accounts[uiResp.payload]; @@ -336,7 +336,7 @@ export default class ComposeTransaction extends AbstractMethod<'composeTransacti // show error view this.postMessage(createUiMessage(UI.INSUFFICIENT_FUNDS)); // wait few seconds... - await resolveAfter(2000, null).promise; + await resolveAfter(2000); // and go back to discovery return 'change-account'; diff --git a/packages/connect/src/api/getAccountInfo.ts b/packages/connect/src/api/getAccountInfo.ts index d0eaba323be..6cc8918e772 100644 --- a/packages/connect/src/api/getAccountInfo.ts +++ b/packages/connect/src/api/getAccountInfo.ts @@ -371,7 +371,7 @@ export default class GetAccountInfo extends AbstractMethod<'getAccountInfo', Req const account = discovery.accounts[uiResp.payload]; if (!discovery.completed) { - await resolveAfter(501).promise; // temporary solution, TODO: immediately resolve will cause "device call in progress" + await resolveAfter(501); // temporary solution, TODO: immediately resolve will cause "device call in progress" } // get account info from backend From 8d35f1d246f3a841317a4b9b529d4c1b2d94ed63 Mon Sep 17 00:00:00 2001 From: Marek Polak Date: Wed, 5 Feb 2025 16:55:48 +0100 Subject: [PATCH 3/5] chore(connect): replace abortablePromise with resolveAfter --- packages/connect/src/device/DeviceList.ts | 7 ++----- packages/connect/src/utils/abortablePromise.ts | 13 ------------- 2 files changed, 2 insertions(+), 18 deletions(-) delete mode 100644 packages/connect/src/utils/abortablePromise.ts diff --git a/packages/connect/src/device/DeviceList.ts b/packages/connect/src/device/DeviceList.ts index 35a99b5f80e..db0b1d209fd 100644 --- a/packages/connect/src/device/DeviceList.ts +++ b/packages/connect/src/device/DeviceList.ts @@ -18,6 +18,7 @@ import { createDeferred, getSynchronize, isNotUndefined, + resolveAfter, } from '@trezor/utils'; import { ERRORS } from '../constants'; @@ -26,7 +27,6 @@ import { Device } from './Device'; import { getBridgeInfo } from '../data/transportInfo'; import { ConnectSettings, DeviceUniquePath, StaticSessionId } from '../types'; import { typedObjectKeys } from '../types/utils'; -import { abortablePromise } from '../utils/abortablePromise'; import { initLog } from '../utils/debug'; const createAuthPenaltyManager = (priority: number) => { @@ -396,10 +396,7 @@ export class DeviceList extends TypedEmitter implements IDevic } private createReconnectDelay(signal: AbortSignal) { - const { promise, resolve } = abortablePromise(signal); - const timeout = setTimeout(resolve, 1000); - - return promise.finally(() => clearTimeout(timeout)); + return resolveAfter(1000, signal); } private scheduleUpgradeCheck(apiType: TransportApiType, initParams: InitParams) { diff --git a/packages/connect/src/utils/abortablePromise.ts b/packages/connect/src/utils/abortablePromise.ts deleted file mode 100644 index 6c8cabff49e..00000000000 --- a/packages/connect/src/utils/abortablePromise.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { createDeferred } from '@trezor/utils'; - -export const abortablePromise = (signal: AbortSignal) => { - const { promise, reject, resolve } = createDeferred(); - const onAbort = () => reject(signal.reason); - signal.addEventListener('abort', onAbort); - if (signal.aborted) onAbort(); - - return { - promise: promise.finally(() => signal.removeEventListener('abort', onAbort)), - resolve, - }; -}; From 42ba8aed210b875dadb343eea11481c822f0d535 Mon Sep 17 00:00:00 2001 From: Marek Polak Date: Wed, 5 Feb 2025 16:57:23 +0100 Subject: [PATCH 4/5] chore: replace createTimeoutPromise with resolveAfter --- packages/connect-popup/e2e/tests/popup-close.test.ts | 4 ++-- packages/connect/src/core/onCallFirmwareUpdate.ts | 6 +++--- packages/connect/src/device/Device.ts | 6 +++--- packages/connect/src/device/DeviceCommands.ts | 4 ++-- packages/suite-desktop-core/src/app.ts | 4 ++-- packages/suite/src/components/suite/WordInput.tsx | 4 ++-- packages/suite/src/components/suite/WordInputAdvanced.tsx | 4 ++-- .../AddAccountButton/AddCoinjoinAccountButton.tsx | 4 ++-- packages/transport-bridge/tests/http.test.ts | 4 ++-- packages/transport/src/api/udp.ts | 4 ++-- packages/transport/src/api/usb.ts | 4 ++-- packages/transport/src/transports/bridge.ts | 4 ++-- 12 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/connect-popup/e2e/tests/popup-close.test.ts b/packages/connect-popup/e2e/tests/popup-close.test.ts index cc2ca225f7a..e9f191e5135 100644 --- a/packages/connect-popup/e2e/tests/popup-close.test.ts +++ b/packages/connect-popup/e2e/tests/popup-close.test.ts @@ -1,7 +1,7 @@ import { BrowserContext, Page, test } from '@playwright/test'; import { TrezorUserEnvLink } from '@trezor/trezor-user-env-link'; -import { addDashesToSpaces, createTimeoutPromise } from '@trezor/utils'; +import { addDashesToSpaces, resolveAfter } from '@trezor/utils'; import { checkHasLogs, @@ -146,7 +146,7 @@ test.afterEach(async ({ context: _context }, testInfo) => { await context.close(); await new Promise(resolve => setTimeout(resolve, 1000)); } - await createTimeoutPromise(WAIT_AFTER_TEST); + await resolveAfter(WAIT_AFTER_TEST); }); test(`popup closed by user`, async ({ page, context }) => { diff --git a/packages/connect/src/core/onCallFirmwareUpdate.ts b/packages/connect/src/core/onCallFirmwareUpdate.ts index ed9de9530e1..79ef2b21601 100644 --- a/packages/connect/src/core/onCallFirmwareUpdate.ts +++ b/packages/connect/src/core/onCallFirmwareUpdate.ts @@ -1,6 +1,6 @@ import { randomBytes } from 'crypto'; -import { createTimeoutPromise } from '@trezor/utils'; +import { resolveAfter } from '@trezor/utils'; import { isEqual, isNewer } from '@trezor/utils/src/versionUtils'; import { @@ -90,7 +90,7 @@ const waitForReconnectedDevice = async ( }), ); - await createTimeoutPromise(2000); + await resolveAfter(2000); try { reconnectedDevice = deviceList.getOnlyDevice(); } catch { @@ -460,7 +460,7 @@ export const onCallFirmwareUpdate = async ({ // This delay is crucial see https://github.com/trezor/trezor-firmware/issues/1983 if (device.features.major_version === 1) { - await createTimeoutPromise(2000); + await resolveAfter(2000); } reconnectedDevice = await waitForReconnectedDevice( { bootloader: true, method: 'auto' }, diff --git a/packages/connect/src/device/Device.ts b/packages/connect/src/device/Device.ts index 1472622e0e5..aabf9b99b17 100644 --- a/packages/connect/src/device/Device.ts +++ b/packages/connect/src/device/Device.ts @@ -8,8 +8,8 @@ import { Deferred, TypedEmitter, createDeferred, - createTimeoutPromise, isArrayMember, + resolveAfter, serializeError, versionUtils, } from '@trezor/utils'; @@ -343,7 +343,7 @@ export class Device extends TypedEmitter { // call only once, right after device creation async handshake(delay?: number) { if (delay) { - await createTimeoutPromise(501 + delay); + await resolveAfter(501 + delay); } while (true) { @@ -384,7 +384,7 @@ export class Device extends TypedEmitter { this.unreadableError = error?.message; this.emitLifecycle(DEVICE.CONNECT_UNACQUIRED); } else { - await createTimeoutPromise(501); + await resolveAfter(501); continue; } } diff --git a/packages/connect/src/device/DeviceCommands.ts b/packages/connect/src/device/DeviceCommands.ts index b5fb0aa69d9..5fcc4c6a746 100644 --- a/packages/connect/src/device/DeviceCommands.ts +++ b/packages/connect/src/device/DeviceCommands.ts @@ -3,7 +3,7 @@ import { MessagesSchema as Messages } from '@trezor/protobuf'; import { Assert } from '@trezor/schema-utils'; import { Session, Transport } from '@trezor/transport'; -import { createTimeoutPromise, versionUtils } from '@trezor/utils'; +import { resolveAfter, versionUtils } from '@trezor/utils'; import { ERRORS } from '../constants'; import { Device } from './Device'; @@ -561,7 +561,7 @@ export class DeviceCommands { */ try { // UI_EVENT is send right before ButtonAck, make sure that ButtonAck is sent - await createTimeoutPromise(1); + await resolveAfter(1); await this.device.acquire(); await cancelPrompt(this.device, false); } catch { diff --git a/packages/suite-desktop-core/src/app.ts b/packages/suite-desktop-core/src/app.ts index 730e21318fb..3c33f954ad3 100644 --- a/packages/suite-desktop-core/src/app.ts +++ b/packages/suite-desktop-core/src/app.ts @@ -6,7 +6,7 @@ import { isMacOs } from '@trezor/env-utils'; import { validateIpcMessage } from '@trezor/ipc-proxy'; import type { HandshakeClient } from '@trezor/suite-desktop-api'; import { TimerId } from '@trezor/type-utils'; -import { createDeferred, createTimeoutPromise } from '@trezor/utils'; +import { createDeferred, resolveAfter } from '@trezor/utils'; import { hangDetect } from './hang-detect'; import { processStatePatch, restartApp } from './libs/app-utils'; @@ -300,7 +300,7 @@ const init = async () => { // await quitting all registered modules Promise.allSettled([quitModules(), quitTorModule(), quitBackgroundModules()]), // or timeout after 5s - createTimeoutPromise(5000), + resolveAfter(5000), ]); // global cleanup diff --git a/packages/suite/src/components/suite/WordInput.tsx b/packages/suite/src/components/suite/WordInput.tsx index 208828b118c..51cefb402d0 100644 --- a/packages/suite/src/components/suite/WordInput.tsx +++ b/packages/suite/src/components/suite/WordInput.tsx @@ -8,7 +8,7 @@ import styled from 'styled-components'; import { Select, variables } from '@trezor/components'; import TrezorConnect, { UI } from '@trezor/connect'; import { bip39 } from '@trezor/crypto-utils'; -import { createTimeoutPromise } from '@trezor/utils'; +import { resolveAfter } from '@trezor/utils'; import { useTranslation } from 'src/hooks/suite/useTranslation'; @@ -80,7 +80,7 @@ export const WordInput = memo(() => { translationString('TR_WORD_DOES_NOT_EXIST', { word: inputValue }) } onChange={async (item: Option, ref?: SelectInstance | null) => { - await createTimeoutPromise(600); + await resolveAfter(600); TrezorConnect.uiResponse({ type: UI.RECEIVE_WORD, payload: item.value }); ref?.clearValue(); }} diff --git a/packages/suite/src/components/suite/WordInputAdvanced.tsx b/packages/suite/src/components/suite/WordInputAdvanced.tsx index 5b0e3e9fa26..db4f6a9502f 100644 --- a/packages/suite/src/components/suite/WordInputAdvanced.tsx +++ b/packages/suite/src/components/suite/WordInputAdvanced.tsx @@ -5,7 +5,7 @@ import styled from 'styled-components'; import { Button, KEYBOARD_CODE, PinButton } from '@trezor/components'; import TrezorConnect, { DeviceModelInternal, UI } from '@trezor/connect'; import { HELP_CENTER_ADVANCED_RECOVERY_URL } from '@trezor/urls'; -import { createTimeoutPromise } from '@trezor/utils'; +import { resolveAfter } from '@trezor/utils'; import { DeviceMatrixExplanation, Translation, TrezorLink } from 'src/components/suite'; @@ -40,7 +40,7 @@ interface WordInputAdvancedProps { export const WordInputAdvanced = ({ count }: WordInputAdvancedProps) => { const onSubmit = useCallback(async (value: string) => { - await createTimeoutPromise(600); + await resolveAfter(600); TrezorConnect.uiResponse({ type: UI.RECEIVE_WORD, payload: value }); }, []); diff --git a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/AddAccountModal/AddAccountButton/AddCoinjoinAccountButton.tsx b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/AddAccountModal/AddAccountButton/AddCoinjoinAccountButton.tsx index 2fda4dcc42a..512da2f309c 100644 --- a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/AddAccountModal/AddAccountButton/AddCoinjoinAccountButton.tsx +++ b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/AddAccountModal/AddAccountButton/AddCoinjoinAccountButton.tsx @@ -6,7 +6,7 @@ import { Network, NetworkAccount, NetworkSymbol } from '@suite-common/wallet-con import { selectSelectedDevice } from '@suite-common/wallet-core'; import { UnavailableCapabilities } from '@trezor/connect'; import { isDesktop } from '@trezor/env-utils'; -import { createTimeoutPromise } from '@trezor/utils'; +import { resolveAfter } from '@trezor/utils'; import { openDeferredModal, openModal } from 'src/actions/suite/modalActions'; import { toggleTor } from 'src/actions/suite/suiteActions'; @@ -111,7 +111,7 @@ export const AddCoinjoinAccountButton = ({ network, selectedAccount }: AddCoinjo // When Tor was not loaded it means there was an error or user canceled it, stop the coinjoin account activation. if (!isTorLoaded) return; } - await createTimeoutPromise(1000); // TODO fix properly: https://github.com/trezor/trezor-suite/issues/6902 + await resolveAfter(1000); // TODO fix properly: https://github.com/trezor/trezor-suite/issues/6902 await createAccount(); }; diff --git a/packages/transport-bridge/tests/http.test.ts b/packages/transport-bridge/tests/http.test.ts index 70211efc828..75dc5f14524 100644 --- a/packages/transport-bridge/tests/http.test.ts +++ b/packages/transport-bridge/tests/http.test.ts @@ -4,7 +4,7 @@ import { getFreePort } from '@trezor/node-utils'; import { AbstractApi } from '@trezor/transport/src/api/abstract'; import { UdpApi } from '@trezor/transport/src/api/udp'; import { bridgeApiCall } from '@trezor/transport/src/utils/bridgeApiCall'; -import { createTimeoutPromise } from '@trezor/utils'; +import { resolveAfter } from '@trezor/utils'; import { TrezordNode } from '../src/http'; @@ -703,7 +703,7 @@ describe('http', () => { client.listen(); // it takes some tome for /listen request to propagate. // todo: solve later - await createTimeoutPromise(1000); + await resolveAfter(1000); return { server, diff --git a/packages/transport/src/api/udp.ts b/packages/transport/src/api/udp.ts index 5b10f5fe839..31280b6c239 100644 --- a/packages/transport/src/api/udp.ts +++ b/packages/transport/src/api/udp.ts @@ -1,6 +1,6 @@ import UDP from 'dgram'; -import { arrayPartition, createTimeoutPromise, isNotUndefined } from '@trezor/utils'; +import { arrayPartition, isNotUndefined, resolveAfter } from '@trezor/utils'; import { AbstractApi, @@ -36,7 +36,7 @@ export class UdpApi extends AbstractApi { private async listenLoop() { while (this.listening) { - await createTimeoutPromise(500); + await resolveAfter(500); if (!this.listening) break; await this.enumerate(this.listenAbortController.signal); } diff --git a/packages/transport/src/api/usb.ts b/packages/transport/src/api/usb.ts index 39b1f12c158..190f7f500bd 100644 --- a/packages/transport/src/api/usb.ts +++ b/packages/transport/src/api/usb.ts @@ -1,4 +1,4 @@ -import { createDeferred, createTimeoutPromise, getSynchronize } from '@trezor/utils'; +import { createDeferred, getSynchronize, resolveAfter } from '@trezor/utils'; import { AbstractApi, AbstractApiConstructorParams, DEVICE_TYPE } from './abstract'; import { @@ -277,7 +277,7 @@ export class UsbApi extends AbstractApi { return res; } - await createTimeoutPromise(100 * i); + await resolveAfter(100 * i); } return this.openInternal(path, first, signal); diff --git a/packages/transport/src/transports/bridge.ts b/packages/transport/src/transports/bridge.ts index ee3f7d63d83..a924cbbf501 100644 --- a/packages/transport/src/transports/bridge.ts +++ b/packages/transport/src/transports/bridge.ts @@ -4,7 +4,7 @@ import { bridge as protocolBridge, v1 as protocolV1, } from '@trezor/protocol'; -import { createTimeoutPromise, versionUtils } from '@trezor/utils'; +import { resolveAfter, versionUtils } from '@trezor/utils'; import { AbstractTransport, @@ -146,7 +146,7 @@ export class BridgeTransport extends AbstractTransport { this.emit('transport-error', response.error); break; } - await createTimeoutPromise(1000); + await resolveAfter(1000); } else { this.handleDescriptorsChange(response.payload); } From f4d872e383357a4fb7e875d357fe686da665e297 Mon Sep 17 00:00:00 2001 From: Marek Polak Date: Wed, 5 Feb 2025 16:57:39 +0100 Subject: [PATCH 5/5] chore(utils): remove createTimeoutPromise --- packages/utils/src/createTimeoutPromise.ts | 2 -- packages/utils/src/index.ts | 1 - 2 files changed, 3 deletions(-) delete mode 100644 packages/utils/src/createTimeoutPromise.ts diff --git a/packages/utils/src/createTimeoutPromise.ts b/packages/utils/src/createTimeoutPromise.ts deleted file mode 100644 index 68eba55d1ac..00000000000 --- a/packages/utils/src/createTimeoutPromise.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const createTimeoutPromise = (timeout: number) => - new Promise(resolve => setTimeout(resolve, timeout)); diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 32700f6ae1e..42c217bf38d 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -19,7 +19,6 @@ export * from './createCooldown'; export * from './createDeferred'; export * from './createDeferredManager'; export * from './createLazy'; -export * from './createTimeoutPromise'; export * from './extractUrlsFromText'; export * from './getLocaleSeparators'; export * from './getMutex';