GramJS: Rework error handling (#6891)

This commit is contained in:
zubiden 2026-04-27 14:29:27 +02:00 committed by Alexander Zinchuk
parent 230e9797d4
commit e5b932b8ea
13 changed files with 97 additions and 131 deletions

View File

@ -2,6 +2,7 @@ import { Api as GramJs, errors } from '../../../lib/gramjs';
import type { RegularLangKey } from '../../../types/language'; import type { RegularLangKey } from '../../../types/language';
import type { RegularLangFnParameters } from '../../../util/localization'; import type { RegularLangFnParameters } from '../../../util/localization';
import type { ApiError } from '../../types';
import { DEBUG } from '../../../config'; import { DEBUG } from '../../../config';
import { import {
@ -39,6 +40,10 @@ const ERROR_KEYS: Record<string, RegularLangKey> = {
PASSKEY_CREDENTIAL_NOT_FOUND: 'ErrorPasskeyUnknown', PASSKEY_CREDENTIAL_NOT_FOUND: 'ErrorPasskeyUnknown',
}; };
function resolveErrorKey(errorMessage: string) {
return ERROR_KEYS[errorMessage] || ERROR_KEYS[errorMessage.replace(/_\d+$/, '')];
}
export type MessageRepairContext = Pick<GramJs.TypeMessage, 'peerId' | 'id'>; export type MessageRepairContext = Pick<GramJs.TypeMessage, 'peerId' | 'id'>;
export type MediaRepairContext = MessageRepairContext; export type MediaRepairContext = MessageRepairContext;
@ -102,6 +107,20 @@ export function checkErrorType(error: unknown): error is Error {
return true; return true;
} }
export function buildApiError(error: Error): Pick<ApiError, 'message' | 'code' | 'hasErrorKey'> {
if (error instanceof errors.RPCError) {
return {
message: error.errorMessage,
code: error.code,
hasErrorKey: true,
};
}
return {
message: error.message,
};
}
export function wrapError<T extends Error>(error: T): WrappedError<T> { export function wrapError<T extends Error>(error: T): WrappedError<T> {
let messageKey: RegularLangFnParameters | undefined; let messageKey: RegularLangFnParameters | undefined;
@ -123,9 +142,12 @@ export function wrapError<T extends Error>(error: T): WrappedError<T> {
variables: { time: formatWait(error.seconds) }, variables: { time: formatWait(error.seconds) },
}; };
} else if (error instanceof errors.RPCError) { } else if (error instanceof errors.RPCError) {
messageKey = { const key = resolveErrorKey(error.errorMessage);
key: ERROR_KEYS[error.errorMessage], if (key) {
}; messageKey = {
key,
};
}
} }
if (!messageKey) { if (!messageKey) {

View File

@ -82,7 +82,9 @@ import {
import { import {
addPhotoToLocalDb, addPhotoToLocalDb,
} from '../helpers/localDb'; } from '../helpers/localDb';
import { checkErrorType, isChatFolder, wrapError } from '../helpers/misc'; import {
buildApiError, checkErrorType, isChatFolder, wrapError,
} from '../helpers/misc';
import { scheduleMutedChatUpdate } from '../scheduleUnmute'; import { scheduleMutedChatUpdate } from '../scheduleUnmute';
import { sendApiUpdate } from '../updates/apiUpdateEmitter'; import { sendApiUpdate } from '../updates/apiUpdateEmitter';
import { import {
@ -1676,12 +1678,10 @@ export async function addChatMembers(chat: ApiChat, users: ApiUser[]) {
return addChatUsersResult.flat().filter(Boolean); return addChatUsersResult.flat().filter(Boolean);
} }
} catch (err: unknown) { } catch (err: unknown) {
const message = err instanceof RPCError ? err.errorMessage : (err as Error).message; const apiError = buildApiError(err as Error);
sendApiUpdate({ sendApiUpdate({
'@type': 'error', '@type': 'error',
error: { error: apiError,
message,
},
}); });
} }
return undefined; return undefined;

View File

@ -1,5 +1,6 @@
import { import {
Api as GramJs, Api as GramJs,
errors,
sessions, sessions,
type Update, type Update,
} from '../../../lib/gramjs'; } from '../../../lib/gramjs';
@ -40,6 +41,7 @@ import {
addWebPageMediaToLocalDb, addWebPageMediaToLocalDb,
} from '../helpers/localDb'; } from '../helpers/localDb';
import { import {
buildApiError,
isResponseUpdate, log, isResponseUpdate, log,
} from '../helpers/misc'; } from '../helpers/misc';
import localDb, { clearLocalDb, type RepairInfo } from '../localDb'; import localDb, { clearLocalDb, type RepairInfo } from '../localDb';
@ -451,9 +453,9 @@ export async function fetchCurrentUser() {
} }
export function dispatchErrorUpdate<T extends GramJs.AnyRequest>(err: Error, request: T) { export function dispatchErrorUpdate<T extends GramJs.AnyRequest>(err: Error, request: T) {
const message = err instanceof RPCError ? err.errorMessage : err.message; const { message, code } = buildApiError(err);
const isSlowMode = message === 'FLOOD' && ( const isSlowMode = err instanceof errors.FloodError && (
request instanceof GramJs.messages.SendMessage request instanceof GramJs.messages.SendMessage
|| request instanceof GramJs.messages.SendMedia || request instanceof GramJs.messages.SendMedia
|| request instanceof GramJs.messages.SendMultiMedia || request instanceof GramJs.messages.SendMultiMedia
@ -463,6 +465,7 @@ export function dispatchErrorUpdate<T extends GramJs.AnyRequest>(err: Error, req
'@type': 'error', '@type': 'error',
error: { error: {
message, message,
code,
isSlowMode, isSlowMode,
hasErrorKey: true, hasErrorKey: true,
}, },

View File

@ -109,6 +109,7 @@ import {
getEntityTypeById, getEntityTypeById,
} from '../gramjsBuilders'; } from '../gramjsBuilders';
import { import {
buildApiError,
deserializeBytes, deserializeBytes,
resolveMessageApiChatId, resolveMessageApiChatId,
} from '../helpers/misc'; } from '../helpers/misc';
@ -237,7 +238,7 @@ export async function fetchMessage({ chat, messageId }: { chat: ApiChat; message
}, },
); );
} catch (err: any) { } catch (err: any) {
const { message } = err; const { message, code } = buildApiError(err);
// When fetching messages for the bot @replies, there may be situations when the user was banned // When fetching messages for the bot @replies, there may be situations when the user was banned
// in the comment group or this group was deleted // in the comment group or this group was deleted
@ -246,6 +247,7 @@ export async function fetchMessage({ chat, messageId }: { chat: ApiChat; message
'@type': 'error', '@type': 'error',
error: { error: {
message, message,
code,
isSlowMode: false, isSlowMode: false,
hasErrorKey: true, hasErrorKey: true,
}, },
@ -821,12 +823,12 @@ export async function editMessage({
console.warn(err); console.warn(err);
} }
const { message: messageErr } = err as Error; const apiError = buildApiError(err as Error);
sendApiUpdate({ sendApiUpdate({
'@type': 'error', '@type': 'error',
error: { error: {
message: messageErr, ...apiError,
hasErrorKey: true, hasErrorKey: true,
}, },
}); });
@ -887,12 +889,12 @@ export async function editTodo({
console.warn(err); console.warn(err);
} }
const { message: messageErr } = err as Error; const apiError = buildApiError(err as Error);
sendApiUpdate({ sendApiUpdate({
'@type': 'error', '@type': 'error',
error: { error: {
message: messageErr, ...apiError,
hasErrorKey: true, hasErrorKey: true,
}, },
}); });
@ -936,12 +938,12 @@ export async function appendTodoList({
console.warn(err); console.warn(err);
} }
const { message: messageErr } = err as Error; const apiError = buildApiError(err as Error);
sendApiUpdate({ sendApiUpdate({
'@type': 'error', '@type': 'error',
error: { error: {
message: messageErr, ...apiError,
hasErrorKey: true, hasErrorKey: true,
}, },
}); });

View File

@ -1,6 +1,6 @@
import type { Api } from '../../../lib/gramjs'; import type { Api } from '../../../lib/gramjs';
import type { TypedBroadcastChannel } from '../../../util/browser/multitab'; import type { TypedBroadcastChannel } from '../../../util/browser/multitab';
import type { ApiInitialArgs, ApiOnProgress, OnApiUpdate } from '../../types'; import type { ApiError, ApiInitialArgs, ApiOnProgress, OnApiUpdate } from '../../types';
import type { LocalDb } from '../localDb'; import type { LocalDb } from '../localDb';
import type { MethodArgs, MethodResponse, Methods } from '../methods/types'; import type { MethodArgs, MethodResponse, Methods } from '../methods/types';
import type { OriginPayload, ThenArg, WorkerMessageEvent } from './types'; import type { OriginPayload, ThenArg, WorkerMessageEvent } from './types';
@ -313,7 +313,7 @@ function subscribeToWorker(onUpdate: OnApiUpdate) {
export function handleMethodResponse(data: { export function handleMethodResponse(data: {
messageId: string; messageId: string;
response?: ThenArg<MethodResponse<keyof Methods>>; response?: ThenArg<MethodResponse<keyof Methods>>;
error?: { message: string }; error?: Pick<ApiError, 'message' | 'code' | 'hasErrorKey'>;
}) { }) {
const requestState = requestStates.get(data.messageId); const requestState = requestStates.get(data.messageId);
if (requestState) { if (requestState) {

View File

@ -1,6 +1,6 @@
import type { DebugLevel } from '../../../util/debugConsole'; import type { DebugLevel } from '../../../util/debugConsole';
import type { import type {
ApiInitialArgs, ApiUpdate, ApiError, ApiInitialArgs, ApiUpdate,
} from '../../types'; } from '../../types';
import type { LocalDb } from '../localDb'; import type { LocalDb } from '../localDb';
import type { MethodArgs, MethodResponse, Methods } from '../methods/types'; import type { MethodArgs, MethodResponse, Methods } from '../methods/types';
@ -17,7 +17,7 @@ export type WorkerPayload =
type: 'methodResponse'; type: 'methodResponse';
messageId: string; messageId: string;
response?: ThenArg<MethodResponse<keyof Methods>>; response?: ThenArg<MethodResponse<keyof Methods>>;
error?: { message: string }; error?: Pick<ApiError, 'message' | 'code' | 'hasErrorKey'>;
} }
| |
{ {

View File

@ -7,7 +7,7 @@ import type { OriginMessageEvent, WorkerPayload } from './types';
import { DEBUG } from '../../../config'; import { DEBUG } from '../../../config';
import { DEBUG_LEVELS } from '../../../util/debugConsole'; import { DEBUG_LEVELS } from '../../../util/debugConsole';
import { throttleWithTickEnd } from '../../../util/schedulers'; import { throttleWithTickEnd } from '../../../util/schedulers';
import { log } from '../helpers/misc'; import { buildApiError, log } from '../helpers/misc';
import { callApi, cancelApiProgress, initApi } from '../methods/init'; import { callApi, cancelApiProgress, initApi } from '../methods/init';
declare const self: WorkerGlobalScope; declare const self: WorkerGlobalScope;
@ -110,7 +110,7 @@ onmessage = ({ data }: OriginMessageEvent) => {
sendToOrigin({ sendToOrigin({
type: 'methodResponse', type: 'methodResponse',
messageId, messageId,
error: { message: error.message }, error: buildApiError(error),
}); });
} }
} }

View File

@ -174,6 +174,7 @@ export type ApiDialog = ApiDialogError | ApiDialogMessage | ApiDialogContact | A
export type ApiError = { export type ApiError = {
message: string; message: string;
code?: number;
entities?: ApiMessageEntity[]; entities?: ApiMessageEntity[];
hasErrorKey?: boolean; hasErrorKey?: boolean;
isSlowMode?: boolean; isSlowMode?: boolean;

View File

@ -12,7 +12,7 @@ import { IS_WAVE_TRANSFORM_SUPPORTED } from '../../../util/browser/windowEnviron
import { getAllMultitabTokens, getCurrentTabId, reestablishMasterToSelf } from '../../../util/establishMultitabRole'; import { getAllMultitabTokens, getCurrentTabId, reestablishMasterToSelf } from '../../../util/establishMultitabRole';
import { getAllNotificationsCount } from '../../../util/folderManager'; import { getAllNotificationsCount } from '../../../util/folderManager';
import getIsAppUpdateNeeded from '../../../util/getIsAppUpdateNeeded'; import getIsAppUpdateNeeded from '../../../util/getIsAppUpdateNeeded';
import getReadableErrorText from '../../../util/getReadableErrorText'; import { shouldShowErrorDialog } from '../../../util/getReadableErrorText';
import { compact, unique } from '../../../util/iteratees'; import { compact, unique } from '../../../util/iteratees';
import { refreshFromCache } from '../../../util/localization'; import { refreshFromCache } from '../../../util/localization';
import * as langProvider from '../../../util/oldLangProvider'; import * as langProvider from '../../../util/oldLangProvider';
@ -388,7 +388,7 @@ addActionHandler('showDialog', (global, actions, payload): ActionReturnType => {
const { data, tabId = getCurrentTabId() } = payload; const { data, tabId = getCurrentTabId() } = payload;
// Filter out errors that we don't want to show to the user // Filter out errors that we don't want to show to the user
if (data.type === 'error' && data.hasErrorKey && !getReadableErrorText(data)) { if (data.type === 'error' && !shouldShowErrorDialog(data)) {
return global; return global;
} }

View File

@ -4,14 +4,14 @@ import type { Api } from '../tl';
* Base class for all Remote Procedure Call errors. * Base class for all Remote Procedure Call errors.
*/ */
export class RPCError extends Error { export class RPCError extends Error {
public code: number | undefined; public code: number;
public errorMessage: string; public errorMessage: string;
constructor(message: string, request: Api.AnyRequest, code?: number) { constructor(message: string, request: Api.AnyRequest, code: number) {
super( super(
'RPCError {0}: {1}{2}' 'RPCError {0}: {1}{2}'
.replace('{0}', code?.toString() || '') .replace('{0}', code.toString())
.replace('{1}', message) .replace('{1}', message)
.replace('{2}', RPCError._fmtRequest(request)), .replace('{2}', RPCError._fmtRequest(request)),
); );
@ -32,63 +32,37 @@ export class RPCError extends Error {
/** /**
* The request must be repeated, but directed to a different data center. * The request must be repeated, but directed to a different data center.
*/ */
export class InvalidDCError extends RPCError { export class InvalidDCError extends RPCError {}
constructor(message: string, request: Api.AnyRequest, code?: number) {
super(message, request, code);
this.code = code || 303;
this.errorMessage = message || 'ERROR_SEE_OTHER';
}
}
/** /**
* The query contains errors. In the event that a request was created * The query contains errors. In the event that a request was created
* using a form and contains user generated data, the user should be * using a form and contains user generated data, the user should be
* notified that the data must be corrected before the query is repeated. * notified that the data must be corrected before the query is repeated.
*/ */
export class BadRequestError extends RPCError { export class BadRequestError extends RPCError {}
code = 400;
errorMessage = 'BAD_REQUEST';
}
/** /**
* There was an unauthorized attempt to use functionality available only * There was an unauthorized attempt to use functionality available only
* to authorized users. * to authorized users.
*/ */
export class UnauthorizedError extends RPCError { export class UnauthorizedError extends RPCError {}
code = 401;
errorMessage = 'UNAUTHORIZED';
}
/** /**
* Privacy violation. For example, an attempt to write a message to * Privacy violation. For example, an attempt to write a message to
* someone who has blacklisted the current user. * someone who has blacklisted the current user.
*/ */
export class ForbiddenError extends RPCError { export class ForbiddenError extends RPCError {}
code = 403;
errorMessage = 'FORBIDDEN';
}
/** /**
* An attempt to invoke a non-existent object, such as a method. * An attempt to invoke a non-existent object, such as a method.
*/ */
export class NotFoundError extends RPCError { export class NotFoundError extends RPCError {}
code = 404;
errorMessage = 'NOT_FOUND';
}
/** /**
* Errors related to invalid authorization key, like * Errors related to invalid authorization key, like
* AUTH_KEY_DUPLICATED which can cause the connection to fail. * AUTH_KEY_DUPLICATED which can cause the connection to fail.
*/ */
export class AuthKeyError extends RPCError { export class AuthKeyError extends RPCError {}
code = 406;
errorMessage = 'AUTH_KEY';
}
/** /**
* The maximum allowed number of attempts to invoke the given method * The maximum allowed number of attempts to invoke the given method
@ -96,29 +70,21 @@ export class AuthKeyError extends RPCError {
* attempt to request a large number of text messages (SMS) for the same * attempt to request a large number of text messages (SMS) for the same
* phone number. * phone number.
*/ */
export class FloodError extends RPCError { export class FloodError extends RPCError {}
code = 420;
errorMessage = 'FLOOD';
}
/** /**
* An internal server error occurred while a request was being processed * An internal server error occurred while a request was being processed
* for example, there was a disruption while accessing a database or file * for example, there was a disruption while accessing a database or file
* storage * storage
*/ */
export class ServerError extends RPCError { export class ServerError extends RPCError {}
code = 500; // Also witnessed as -500
errorMessage = 'INTERNAL';
}
/** /**
* Clicking the inline buttons of bots that never (or take to long to) * Clicking the inline buttons of bots that never (or take to long to)
* call ``answerCallbackQuery`` will result in this "special" RPCError. * call ``answerCallbackQuery`` will result in this "special" RPCError.
*/ */
export class TimedOutError extends RPCError { export class TimedOutError extends RPCError {
code = 503; // Only witnessed as -503 constructor(args: { request: Api.AnyRequest; code: number }) {
super('Timeout', args.request, args.code); // Only witnessed as -503
errorMessage = 'Timeout'; }
} }

View File

@ -1,4 +1,3 @@
/* eslint-disable @stylistic/max-len */
import { import {
BadRequestError, FloodError, InvalidDCError, RPCError, TimedOutError, BadRequestError, FloodError, InvalidDCError, RPCError, TimedOutError,
} from './RPCBaseErrors'; } from './RPCBaseErrors';
@ -8,8 +7,7 @@ export class UserMigrateError extends InvalidDCError {
constructor(args: any) { constructor(args: any) {
const newDc = Number(args.capture || 0); const newDc = Number(args.capture || 0);
super(`The user whose identity is being used to execute queries is associated with DC ${newDc}${RPCError._fmtRequest(args.request)}`, args.request); super(args.errorMessage, args.request, args.code);
this.message = `The user whose identity is being used to execute queries is associated with DC ${newDc}${RPCError._fmtRequest(args.request)}`;
this.newDc = newDc; this.newDc = newDc;
} }
} }
@ -19,8 +17,7 @@ export class PhoneMigrateError extends InvalidDCError {
constructor(args: any) { constructor(args: any) {
const newDc = Number(args.capture || 0); const newDc = Number(args.capture || 0);
super(`The phone number a user is trying to use for authorization is associated with DC ${newDc}${RPCError._fmtRequest(args.request)}`, args.request); super(args.errorMessage, args.request, args.code);
this.message = `The phone number a user is trying to use for authorization is associated with DC ${newDc}${RPCError._fmtRequest(args.request)}`;
this.newDc = newDc; this.newDc = newDc;
} }
} }
@ -30,11 +27,7 @@ export class SlowModeWaitError extends FloodError {
constructor(args: any) { constructor(args: any) {
const seconds = Number(args.capture || 0); const seconds = Number(args.capture || 0);
super( super(args.errorMessage, args.request, args.code);
`A wait of ${seconds} seconds is required before sending another message in this chat ${RPCError._fmtRequest(args.request)}`,
args.request,
);
this.message = `A wait of ${seconds} seconds is required before sending another message in this chat${RPCError._fmtRequest(args.request)}`;
this.seconds = seconds; this.seconds = seconds;
} }
} }
@ -44,11 +37,7 @@ export class FloodWaitError extends FloodError {
constructor(args: any) { constructor(args: any) {
const seconds = Number(args.capture || 0); const seconds = Number(args.capture || 0);
super( super(args.errorMessage, args.request, args.code);
`A wait of ${seconds} seconds is required${RPCError._fmtRequest(args.request)}`,
args.request,
);
this.message = `A wait of ${seconds} seconds is required${RPCError._fmtRequest(args.request)}`;
this.seconds = seconds; this.seconds = seconds;
} }
} }
@ -56,21 +45,14 @@ export class FloodWaitError extends FloodError {
export class FloodPremiumWaitError extends FloodWaitError { export class FloodPremiumWaitError extends FloodWaitError {
constructor(args: any) { constructor(args: any) {
const seconds = Number(args.capture || 0); const seconds = Number(args.capture || 0);
super(`A wait of ${seconds} seconds is required${RPCError._fmtRequest(args.request)}`); super(args);
this.message = `A wait of ${seconds} seconds is required${RPCError._fmtRequest(args.request)}`;
this.seconds = seconds; this.seconds = seconds;
} }
} }
export class MsgWaitError extends FloodError { export class MsgWaitError extends FloodError {
constructor(args: any) { constructor(args: any) {
super( super(args.errorMessage, args.request, args.code);
`Message failed to be sent.${RPCError._fmtRequest(args.request)}`,
args.request,
);
this.message = `Message failed to be sent.${RPCError._fmtRequest(
args.request,
)}`;
} }
} }
@ -79,11 +61,7 @@ export class FloodTestPhoneWaitError extends FloodError {
constructor(args: any) { constructor(args: any) {
const seconds = Number(args.capture || 0); const seconds = Number(args.capture || 0);
super( super(args.errorMessage, args.request, args.code);
`A wait of ${seconds} seconds is required in the test servers${RPCError._fmtRequest(args.request)}`,
args.request,
);
this.message = `A wait of ${seconds} seconds is required in the test servers${RPCError._fmtRequest(args.request)}`;
this.seconds = seconds; this.seconds = seconds;
} }
} }
@ -93,11 +71,7 @@ export class FileMigrateError extends InvalidDCError {
constructor(args: any) { constructor(args: any) {
const newDc = Number(args.capture || 0); const newDc = Number(args.capture || 0);
super( super(args.errorMessage, args.request, args.code);
`The file to be accessed is currently stored in DC ${newDc}${RPCError._fmtRequest(args.request)}`,
args.request,
);
this.message = `The file to be accessed is currently stored in DC ${newDc}${RPCError._fmtRequest(args.request)}`;
this.newDc = newDc; this.newDc = newDc;
} }
} }
@ -107,11 +81,7 @@ export class NetworkMigrateError extends InvalidDCError {
constructor(args: any) { constructor(args: any) {
const newDc = Number(args.capture || 0); const newDc = Number(args.capture || 0);
super( super(args.errorMessage, args.request, args.code);
`The source IP address is associated with DC ${newDc}${RPCError._fmtRequest(args.request)}`,
args.request,
);
this.message = `The source IP address is associated with DC ${newDc}${RPCError._fmtRequest(args.request)}`;
this.newDc = newDc; this.newDc = newDc;
} }
} }
@ -121,17 +91,7 @@ export class EmailUnconfirmedError extends BadRequestError {
constructor(args: any) { constructor(args: any) {
const codeLength = Number(args.capture || 0); const codeLength = Number(args.capture || 0);
super( super(args.errorMessage, args.request, args.code);
`Email unconfirmed, the length of the code must be ${codeLength}${RPCError._fmtRequest(
args.request,
)}`,
args.request,
400,
);
this.message = `Email unconfirmed, the length of the code must be ${codeLength}${RPCError._fmtRequest(
args.request,
)}`;
this.codeLength = codeLength; this.codeLength = codeLength;
} }
} }
@ -141,9 +101,7 @@ export class PasswordFreshError extends BadRequestError {
constructor(args: any) { constructor(args: any) {
const seconds = Number(args.capture || 0); const seconds = Number(args.capture || 0);
super(`The password was modified less than 24 hours ago, try again in ${seconds} seconds.`, args.request); super(args.errorMessage, args.request, args.code);
this.message = `The password was modified less than 24 hours ago, try again in ${seconds} seconds.`;
this.seconds = seconds; this.seconds = seconds;
} }
} }
@ -153,9 +111,7 @@ export class SessionFreshError extends BadRequestError {
constructor(args: any) { constructor(args: any) {
const seconds = Number(args.capture || 0); const seconds = Number(args.capture || 0);
super(`Session is fresh, please try again in ${seconds} seconds.`, args.request); super(args.errorMessage, args.request, args.code);
this.message = `Session is fresh, please try again in ${seconds} seconds.`;
this.seconds = seconds; this.seconds = seconds;
} }
} }
@ -181,7 +137,7 @@ export class UserAlreadyAuthorizedError extends Error {
export class PasskeyCredentialNotFoundError extends RPCError { export class PasskeyCredentialNotFoundError extends RPCError {
constructor(args: any) { constructor(args: any) {
super('PASSKEY_CREDENTIAL_NOT_FOUND', args.request); super(args.errorMessage, args.request, args.code);
} }
} }

View File

@ -17,7 +17,12 @@ export function RPCMessageToError(
const m = rpcError.errorMessage.match(msgRegex); const m = rpcError.errorMessage.match(msgRegex);
if (m) { if (m) {
const capture = m.length === 2 ? parseInt(m[1], 10) : undefined; const capture = m.length === 2 ? parseInt(m[1], 10) : undefined;
return new Cls({ request, capture }); return new Cls({
request,
capture,
code: rpcError.errorCode,
errorMessage: rpcError.errorMessage,
});
} }
} }
return new RPCError(rpcError.errorMessage, request, rpcError.errorCode); return new RPCError(rpcError.errorMessage, request, rpcError.errorCode);

View File

@ -136,6 +136,10 @@ const FINAL_PAYMENT_ERRORS = new Set([
'PAYMENT_FAILED', 'PAYMENT_FAILED',
]); ]);
const ERROR_CODES_WITHOUT_DIALOG = new Set([
406,
]);
export default function getReadableErrorText(error: ApiError) { export default function getReadableErrorText(error: ApiError) {
const { message, isSlowMode, textParams } = error; const { message, isSlowMode, textParams } = error;
// Currently, Telegram API doesn't return `SLOWMODE_WAIT_X` error as described in the docs // Currently, Telegram API doesn't return `SLOWMODE_WAIT_X` error as described in the docs
@ -159,3 +163,10 @@ export function getShippingError(error: ApiError): ApiFieldError | undefined {
export function shouldClosePaymentModal(error: ApiError): boolean { export function shouldClosePaymentModal(error: ApiError): boolean {
return FINAL_PAYMENT_ERRORS.has(error.message); return FINAL_PAYMENT_ERRORS.has(error.message);
} }
export function shouldShowErrorDialog(error: ApiError): boolean {
if (error.code && ERROR_CODES_WITHOUT_DIALOG.has(error.code)) return false;
if (error.hasErrorKey && !getReadableErrorText(error)) return false;
return true;
}