317 lines
7.8 KiB
TypeScript
317 lines
7.8 KiB
TypeScript
// Copyright 2020 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
/* eslint-disable max-classes-per-file */
|
|
|
|
import type { Response } from 'node-fetch';
|
|
import type { LibSignalErrorBase } from '@signalapp/libsignal-client';
|
|
|
|
import { parseRetryAfter } from '../util/parseRetryAfter';
|
|
import type { ServiceIdString } from '../types/ServiceId';
|
|
|
|
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 class HTTPError extends Error {
|
|
public override readonly name = 'HTTPError';
|
|
|
|
public readonly code: number;
|
|
|
|
public readonly responseHeaders: HeaderListType;
|
|
|
|
public readonly response: unknown;
|
|
|
|
static fromResponse(response: Response): HTTPError {
|
|
return new HTTPError(response.statusText, {
|
|
code: response.status,
|
|
headers: Object.fromEntries(response.headers),
|
|
response,
|
|
});
|
|
}
|
|
|
|
constructor(
|
|
message: string,
|
|
options: {
|
|
code: number;
|
|
headers: HeaderListType;
|
|
response?: unknown;
|
|
stack?: string;
|
|
cause?: unknown;
|
|
}
|
|
) {
|
|
super(`${message}; code: ${options.code}`, { cause: options.cause });
|
|
|
|
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;
|
|
cause?: unknown;
|
|
}) {
|
|
super(options.message, { cause: options.cause });
|
|
|
|
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 {
|
|
public readonly identifier: string;
|
|
|
|
// Note: Data to resend message is no longer captured
|
|
constructor(incomingIdentifier: string, cause?: LibSignalErrorBase) {
|
|
const identifier = incomingIdentifier.split('.')[0];
|
|
|
|
super({
|
|
name: 'OutgoingIdentityKeyError',
|
|
message: `The identity of ${identifier} has changed.`,
|
|
cause,
|
|
});
|
|
|
|
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;
|
|
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,
|
|
cause: httpError,
|
|
});
|
|
|
|
[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 successfulServiceIds?: Array<ServiceIdString>;
|
|
|
|
public readonly failoverServiceIds?: Array<ServiceIdString>;
|
|
|
|
public readonly errors?: CallbackResultType['errors'];
|
|
|
|
public readonly unidentifiedDeliveries?: Array<ServiceIdString>;
|
|
|
|
public readonly dataMessage: Uint8Array | undefined;
|
|
|
|
public readonly editMessage: Uint8Array | undefined;
|
|
|
|
// Fields necessary for send log save
|
|
public readonly contentHint?: number;
|
|
|
|
public readonly contentProto?: Uint8Array;
|
|
|
|
public readonly timestamp?: number;
|
|
|
|
public readonly recipients?: Record<ServiceIdString, Array<number>>;
|
|
|
|
public readonly sendIsNotFinal?: boolean;
|
|
|
|
constructor({
|
|
successfulServiceIds,
|
|
failoverServiceIds,
|
|
errors,
|
|
unidentifiedDeliveries,
|
|
dataMessage,
|
|
editMessage,
|
|
contentHint,
|
|
contentProto,
|
|
timestamp,
|
|
recipients,
|
|
sendIsNotFinal,
|
|
}: CallbackResultType) {
|
|
super(`SendMessageProtoError: ${SendMessageProtoError.getMessage(errors)}`);
|
|
|
|
this.successfulServiceIds = successfulServiceIds;
|
|
this.failoverServiceIds = failoverServiceIds;
|
|
this.errors = errors;
|
|
this.unidentifiedDeliveries = unidentifiedDeliveries;
|
|
this.dataMessage = dataMessage;
|
|
this.editMessage = editMessage;
|
|
this.contentHint = contentHint;
|
|
this.contentProto = contentProto;
|
|
this.timestamp = timestamp;
|
|
this.recipients = recipients;
|
|
this.sendIsNotFinal = sendIsNotFinal;
|
|
}
|
|
|
|
protected static getMessage(errors: CallbackResultType['errors']): string {
|
|
if (!errors) {
|
|
return 'No errors';
|
|
}
|
|
|
|
return errors.map(error => error.toString()).join(', ');
|
|
}
|
|
}
|
|
|
|
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 serviceId: string;
|
|
|
|
readonly httpError: HTTPError;
|
|
|
|
constructor(serviceId: ServiceIdString, 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.serviceId = serviceId;
|
|
this.httpError = httpError;
|
|
|
|
appendStack(this, httpError);
|
|
}
|
|
|
|
get code(): number {
|
|
return this.httpError.code;
|
|
}
|
|
}
|
|
|
|
export class ConnectTimeoutError extends Error {}
|
|
|
|
export class UnknownRecipientError extends Error {}
|
|
|
|
export class IncorrectSenderKeyAuthError extends Error {}
|
|
|
|
export class WarnOnlyError extends Error {}
|
|
|
|
export class NoSenderKeyError extends Error {}
|