fix(suite): properly handled deadline and timeout error in getConnectFiatRatesForTimestamp call (#18405)

This commit is contained in:
Vojtěch Tranta
2025-04-22 13:02:36 +02:00
committed by GitHub
parent 744f41d309
commit b62f339e69
2 changed files with 67 additions and 19 deletions

View File

@@ -59,10 +59,18 @@ const rejectAfterMs = (ms: number, reason: Error, clear: AbortSignal) =>
const maybeRejectAfterMs = (ms: number | undefined, reason: Error, clear: AbortSignal) =>
ms === undefined ? [] : [rejectAfterMs(ms, reason, clear)];
export class RejectWhenAbortedError extends Error {
static name = 'Aborted by signal';
constructor() {
super(RejectWhenAbortedError.name);
}
}
const rejectWhenAborted = (signal: AbortSignal | undefined, clear: AbortSignal) =>
new Promise<never>((_, reject) => {
if (clear.aborted) return reject();
const errorSignal = new Error('Aborted by signal');
const errorSignal = new RejectWhenAbortedError();
if (clear.aborted) return reject(errorSignal);
if (signal?.aborted) return reject(errorSignal);
const onAbort = () => reject(errorSignal);
signal?.addEventListener('abort', onAbort);
@@ -115,6 +123,20 @@ const attemptLoop = async <T>(
return clear.aborted ? Promise.reject() : attempt(attempts - 1, clear);
};
export class ScheduleActionTimeoutError extends Error {
static name = 'Aborted by timeout' as const;
constructor() {
super(ScheduleActionTimeoutError.name);
}
}
export class ScheduleActionDeadlineError extends Error {
static name = 'Aborted by deadline' as const;
constructor() {
super(ScheduleActionDeadlineError.name);
}
}
export const scheduleAction = async <T>(
action: ScheduledAction<T>,
params: ScheduleActionParams,
@@ -130,8 +152,8 @@ export const scheduleAction = async <T>(
? (attempt: number) => attempts[attempt]
: () => ({ timeout, gap });
const errorDeadline = new Error('Aborted by deadline');
const errorTimeout = new Error('Aborted by timeout');
const errorDeadline = new ScheduleActionDeadlineError();
const errorTimeout = new ScheduleActionTimeoutError();
try {
return await Promise.race([

View File

@@ -9,7 +9,12 @@ import type {
Timestamp,
} from '@suite-common/wallet-types';
import TrezorConnect from '@trezor/connect';
import { scheduleAction } from '@trezor/utils';
import {
RejectWhenAbortedError,
ScheduleActionDeadlineError,
ScheduleActionTimeoutError,
scheduleAction,
} from '@trezor/utils';
import * as blockbookService from './blockbook';
import { ParallelRequestsCache } from './cache';
@@ -25,23 +30,44 @@ type FiatRatesParams = {
isElectrumBackend: boolean;
};
const getConnectFiatRatesForTimestamp = (
const getConnectFiatRatesForTimestamp = async (
ticker: TickerId,
timestamps: number[],
currency: FiatCurrencyCode,
) =>
scheduleAction(
() =>
TrezorConnect.blockchainGetFiatRatesForTimestamps({
coin: ticker.symbol,
token: ticker.tokenAddress,
timestamps,
currencies: [currency],
}),
{
timeout: CONNECT_FETCH_TIMEOUT,
},
);
): Promise<
| ReturnType<typeof TrezorConnect.blockchainGetFiatRatesForTimestamps>
| { success: false; payload: null }
> => {
try {
return await scheduleAction(
() =>
TrezorConnect.blockchainGetFiatRatesForTimestamps({
coin: ticker.symbol,
token: ticker.tokenAddress,
timestamps,
currencies: [currency],
}),
{
timeout: CONNECT_FETCH_TIMEOUT,
},
);
} catch (error) {
if (
('name' in error && error.name === 'AbortError') ||
!('message' in error) ||
(error.message !== ScheduleActionTimeoutError.name &&
error.message !== ScheduleActionDeadlineError.name &&
error.message !== RejectWhenAbortedError.name)
) {
throw error;
}
return Promise.resolve({
success: false,
payload: null,
});
}
};
export const fetchCurrentFiatRates = ({
ticker,