signal-desktop/ts/textsecure/Errors.ts

302 lines
7.2 KiB
TypeScript

// Copyright 2020-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable max-classes-per-file */
import { parseRetryAfter } from '../util/parseRetryAfter';
import type { CallbackResultType } from './Types.d';
import type { HeaderListType } from './WebAPI';
function appendStack(newError: Error, originalError: Error) {
// eslint-disable-next-line no-param-reassign
newError.stack += `\nOriginal stack:\n${originalError.stack}`;
}
export type HTTPErrorHeadersType = {
[name: string]: string | ReadonlyArray<string>;
};
export class HTTPError extends Error {
public override readonly name = 'HTTPError';
public readonly code: number;
public readonly responseHeaders: HTTPErrorHeadersType;
public readonly response: unknown;
constructor(
message: string,
options: {
code: number;
headers: HTTPErrorHeadersType;
response?: unknown;
stack?: string;
}
) {
super(`${message}; code: ${options.code}`);
const { code: providedCode, headers, response, stack } = options;
this.code = providedCode > 999 || providedCode < 100 ? -1 : providedCode;
this.responseHeaders = headers;
this.stack += `\nOriginal stack:\n${stack}`;
this.response = response;
}
}
export class ReplayableError extends Error {
functionCode?: number;
constructor(options: {
name?: string;
message: string;
functionCode?: number;
}) {
super(options.message);
this.name = options.name || 'ReplayableError';
this.message = options.message;
// Maintains proper stack trace, where our error was thrown (only available on V8)
// via https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
if (Error.captureStackTrace) {
Error.captureStackTrace(this);
}
this.functionCode = options.functionCode;
}
}
export class OutgoingIdentityKeyError extends ReplayableError {
identifier: string;
// Note: Data to resend message is no longer captured
constructor(incomingIdentifier: string) {
const identifier = incomingIdentifier.split('.')[0];
super({
name: 'OutgoingIdentityKeyError',
message: `The identity of ${identifier} has changed.`,
});
this.identifier = identifier;
}
}
export class OutgoingMessageError extends ReplayableError {
readonly identifier: string;
readonly httpError?: HTTPError;
// Note: Data to resend message is no longer captured
constructor(
incomingIdentifier: string,
_m: unknown,
_t: unknown,
httpError?: HTTPError
) {
const identifier = incomingIdentifier.split('.')[0];
super({
name: 'OutgoingMessageError',
message: httpError ? httpError.message : 'no http error',
});
this.identifier = identifier;
if (httpError) {
this.httpError = httpError;
appendStack(this, httpError);
}
}
get code(): undefined | number {
return this.httpError?.code;
}
}
export class SendMessageNetworkError extends ReplayableError {
readonly identifier: string;
readonly httpError: HTTPError;
constructor(identifier: string, _m: unknown, httpError: HTTPError) {
super({
name: 'SendMessageNetworkError',
message: httpError.message,
});
[this.identifier] = identifier.split('.');
this.httpError = httpError;
appendStack(this, httpError);
}
get code(): number {
return this.httpError.code;
}
get responseHeaders(): undefined | HeaderListType {
return this.httpError.responseHeaders;
}
}
export type SendMessageChallengeData = {
readonly token?: string;
readonly options?: ReadonlyArray<string>;
};
export class SendMessageChallengeError extends ReplayableError {
public identifier: string;
public readonly httpError: HTTPError;
public readonly data: SendMessageChallengeData | undefined;
public readonly retryAt?: number;
constructor(identifier: string, httpError: HTTPError) {
super({
name: 'SendMessageChallengeError',
message: httpError.message,
});
[this.identifier] = identifier.split('.');
this.httpError = httpError;
this.data = httpError.response as SendMessageChallengeData;
const headers = httpError.responseHeaders || {};
const retryAfter = parseRetryAfter(headers['retry-after']);
if (retryAfter) {
this.retryAt = Date.now() + retryAfter;
}
appendStack(this, httpError);
}
get code(): number {
return this.httpError.code;
}
}
export class SendMessageProtoError extends Error implements CallbackResultType {
public readonly successfulIdentifiers?: Array<string>;
public readonly failoverIdentifiers?: Array<string>;
public readonly errors?: CallbackResultType['errors'];
public readonly unidentifiedDeliveries?: Array<string>;
public readonly dataMessage?: Uint8Array;
// Fields necessary for send log save
public readonly contentHint?: number;
public readonly contentProto?: Uint8Array;
public readonly timestamp?: number;
public readonly recipients?: Record<string, Array<number>>;
constructor({
successfulIdentifiers,
failoverIdentifiers,
errors,
unidentifiedDeliveries,
dataMessage,
contentHint,
contentProto,
timestamp,
recipients,
}: CallbackResultType) {
super(`SendMessageProtoError: ${SendMessageProtoError.getMessage(errors)}`);
this.successfulIdentifiers = successfulIdentifiers;
this.failoverIdentifiers = failoverIdentifiers;
this.errors = errors;
this.unidentifiedDeliveries = unidentifiedDeliveries;
this.dataMessage = dataMessage;
this.contentHint = contentHint;
this.contentProto = contentProto;
this.timestamp = timestamp;
this.recipients = recipients;
}
protected static getMessage(errors: CallbackResultType['errors']): string {
if (!errors) {
return 'No errors';
}
return errors
.map(error => (error.stackForLog ? error.stackForLog : error.toString()))
.join(', ');
}
}
export class SignedPreKeyRotationError extends ReplayableError {
constructor() {
super({
name: 'SignedPreKeyRotationError',
message: 'Too many signed prekey rotation failures',
});
}
}
export class MessageError extends ReplayableError {
readonly httpError: HTTPError;
constructor(_m: unknown, httpError: HTTPError) {
super({
name: 'MessageError',
message: httpError.message,
});
this.httpError = httpError;
appendStack(this, httpError);
}
get code(): number {
return this.httpError.code;
}
}
export class UnregisteredUserError extends Error {
readonly identifier: string;
readonly httpError: HTTPError;
constructor(identifier: string, httpError: HTTPError) {
const { message } = httpError;
super(message);
this.message = message;
this.name = 'UnregisteredUserError';
// Maintains proper stack trace, where our error was thrown (only available on V8)
// via https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
if (Error.captureStackTrace) {
Error.captureStackTrace(this);
}
this.identifier = identifier;
this.httpError = httpError;
appendStack(this, httpError);
}
get code(): number {
return this.httpError.code;
}
}
export class ConnectTimeoutError extends Error {}
export class WarnOnlyError extends Error {}