Migrate textsecure to eslint

Co-authored-by: Chris Svenningsen <chris@carbonfive.com>
This commit is contained in:
Sidney Keese 2020-09-24 14:53:21 -07:00 committed by Josh Perez
parent b5df9b4067
commit 7b6d8f55d6
24 changed files with 706 additions and 299 deletions

View File

@ -28,8 +28,3 @@ sticker-creator/**/*.js
**/*.d.ts
webpack.config.ts
# Temporarily ignored during TSLint transition
# JIRA: DESKTOP-304
ts/sql/**
ts/textsecure/**

View File

@ -1,5 +1,10 @@
// tslint:disable no-default-export no-unnecessary-local-variable
/* eslint-disable no-await-in-loop */
/* eslint-disable camelcase */
/* eslint-disable no-param-reassign */
/* eslint-disable no-continue */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/ban-types */
import { ipcRenderer } from 'electron';
import {
@ -241,7 +246,7 @@ const channels: ServerInterface = channelsAsUnknown;
// When IPC arguments are prepared for the cross-process send, they are JSON.stringified.
// We can't send ArrayBuffers or BigNumbers (what we get from proto library for dates),
// We also cannot send objects with function-value keys, like what protobufjs gives us.
function _cleanData(data: any, path: string = 'root') {
function _cleanData(data: any, path = 'root') {
if (data === null || data === undefined) {
window.log.warn(`_cleanData: null or undefined value at path ${path}`);
@ -321,8 +326,6 @@ async function _shutdown() {
}
resolve();
return;
};
});
@ -1032,7 +1035,7 @@ async function getLastConversationActivity(
if (result) {
return new Message(result);
}
return;
return undefined;
}
async function getLastConversationPreview(
conversationId: string,
@ -1045,7 +1048,7 @@ async function getLastConversationPreview(
if (result) {
return new Message(result);
}
return;
return undefined;
}
async function getMessageMetricsForConversation(conversationId: string) {
const result = await channels.getMessageMetricsForConversation(
@ -1292,7 +1295,7 @@ async function getRecentStickers() {
async function updateEmojiUsage(shortName: string) {
await channels.updateEmojiUsage(shortName);
}
async function getRecentEmojis(limit: number = 32) {
async function getRecentEmojis(limit = 32) {
return channels.getRecentEmojis(limit);
}
@ -1336,8 +1339,6 @@ async function callChannel(name: string) {
}
resolve();
return;
});
setTimeout(() => {

View File

@ -1,5 +1,15 @@
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable camelcase */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { LocaleMessagesType } from '../types/I18N';
import {
ConversationModelCollectionType,
MessageModelCollectionType,
} from '../model-types.d';
import { MessageModel } from '../models/messages';
import { ConversationModel } from '../models/conversations';
export type AttachmentDownloadJobType = any;
export type ConverationMetricsType = any;
export type ConversationType = any;
@ -17,13 +27,6 @@ export type StickerPackType = any;
export type StickerType = any;
export type UnprocessedType = any;
import {
ConversationModelCollectionType,
MessageModelCollectionType,
} from '../model-types.d';
import { MessageModel } from '../models/messages';
import { ConversationModel } from '../models/conversations';
export interface DataInterface {
close: () => Promise<void>;
removeDB: () => Promise<void>;

View File

@ -1,3 +1,10 @@
/* eslint-disable no-nested-ternary */
/* eslint-disable camelcase */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable no-await-in-loop */
/* eslint-disable no-restricted-syntax */
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-explicit-any */
// tslint:disable no-console no-default-export no-unnecessary-local-variable
import { join } from 'path';
@ -6,12 +13,6 @@ import rimraf from 'rimraf';
import PQueue from 'p-queue';
import sql from '@journeyapps/sqlcipher';
import { app, clipboard, dialog } from 'electron';
import { redactAll } from '../../js/modules/privacy';
import { remove as removeUserConfig } from '../../app/user_config';
import { combineNames } from '../util/combineNames';
import { GroupV2MemberType } from '../model-types.d';
import { LocaleMessagesType } from '../types/I18N';
import pify from 'pify';
import { v4 as generateUUID } from 'uuid';
@ -28,6 +29,13 @@ import {
pick,
} from 'lodash';
import { redactAll } from '../../js/modules/privacy';
import { remove as removeUserConfig } from '../../app/user_config';
import { combineNames } from '../util/combineNames';
import { GroupV2MemberType } from '../model-types.d';
import { LocaleMessagesType } from '../types/I18N';
import {
AttachmentDownloadJobType,
ConversationType,
@ -211,8 +219,6 @@ async function openDatabase(filePath: string): Promise<sql.Database> {
}
resolve(instance);
return;
};
instance = new sql.Database(filePath, callback);
@ -3700,7 +3706,7 @@ async function updateEmojiUsage(
}
updateEmojiUsage.needsSerial = true;
async function getRecentEmojis(limit: number = 32) {
async function getRecentEmojis(limit = 32) {
const db = getInstance();
const rows = await db.all(
'SELECT * FROM emojis ORDER BY lastUsage DESC LIMIT $limit;',

2
ts/textsecure.d.ts vendored
View File

@ -808,7 +808,7 @@ declare class ProvisioningUuidClass {
uuid?: string;
}
declare class ProvisionEnvelopeClass {
export declare class ProvisionEnvelopeClass {
static decode: (
data: ArrayBuffer | ByteBufferClass,
encoding?: string

View File

@ -1,11 +1,15 @@
// tslint:disable no-default-export no-unnecessary-local-variable
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable more/no-then */
/* eslint-disable class-methods-use-this */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import PQueue from 'p-queue';
import EventTarget from './EventTarget';
import { WebAPIType } from './WebAPI';
import MessageReceiver from './MessageReceiver';
import { KeyPairType, SignedPreKeyType } from '../libsignal.d';
import utils from './Helpers';
import PQueue from 'p-queue';
import ProvisioningCipher from './ProvisioningCipher';
import WebSocketResource, {
IncomingWebSocketRequest,
@ -42,7 +46,9 @@ type GeneratedKeysType = {
export default class AccountManager extends EventTarget {
server: WebAPIType;
pending: Promise<void>;
pendingQueue?: PQueue;
constructor(username: string, password: string) {
@ -55,9 +61,11 @@ export default class AccountManager extends EventTarget {
async requestVoiceVerification(number: string) {
return this.server.requestVerificationVoice(number);
}
async requestSMSVerification(number: string) {
return this.server.requestVerificationSMS(number);
}
async encryptDeviceName(name: string, providedIdentityKey?: KeyPairType) {
if (!name) {
return null;
@ -81,6 +89,7 @@ export default class AccountManager extends EventTarget {
const arrayBuffer = proto.encode().toArrayBuffer();
return MessageReceiver.arrayBufferToStringBase64(arrayBuffer);
}
async decryptDeviceName(base64: string) {
const identityKey = await window.textsecure.storage.protocol.getIdentityKeyPair();
@ -99,6 +108,7 @@ export default class AccountManager extends EventTarget {
return name;
}
async maybeUpdateDeviceName() {
const isNameEncrypted = window.textsecure.storage.user.getDeviceNameEncrypted();
if (isNameEncrypted) {
@ -111,15 +121,18 @@ export default class AccountManager extends EventTarget {
await this.server.updateDeviceName(base64);
}
}
async deviceNameIsEncrypted() {
await window.textsecure.storage.user.setDeviceNameEncrypted();
}
async maybeDeleteSignalingKey() {
const key = window.textsecure.storage.user.getSignalingKey();
if (key) {
await this.server.removeSignalingKey();
}
}
async registerSingleDevice(number: string, verificationCode: string) {
const registerKeys = this.server.registerKeys.bind(this.server);
const createAccount = this.createAccount.bind(this);
@ -275,6 +288,7 @@ export default class AccountManager extends EventTarget {
})
);
}
async refreshPreKeys() {
const generateKeys = this.generateKeys.bind(this, 100);
const registerKeys = this.server.registerKeys.bind(this.server);
@ -289,6 +303,7 @@ export default class AccountManager extends EventTarget {
})
);
}
async rotateSignedPreKey() {
return this.queueTask(async () => {
const signedKeyId = window.textsecure.storage.get('signedKeyId', 1);
@ -314,6 +329,7 @@ export default class AccountManager extends EventTarget {
return;
}
// eslint-disable-next-line consistent-return
return store
.getIdentityKeyPair()
.then(
@ -382,12 +398,14 @@ export default class AccountManager extends EventTarget {
});
});
}
async queueTask(task: () => Promise<any>) {
this.pendingQueue = this.pendingQueue || new PQueue({ concurrency: 1 });
const taskWithTimeout = window.textsecure.createTaskWithTimeout(task);
return this.pendingQueue.add(taskWithTimeout);
}
async cleanSignedPreKeys() {
const MINIMUM_KEYS = 3;
const store = window.textsecure.storage.protocol;
@ -600,6 +618,7 @@ export default class AccountManager extends EventTarget {
await window.textsecure.storage.put('regionCode', regionCode);
await window.textsecure.storage.protocol.hydrateCaches();
}
async clearSessionsAndPreKeys() {
const store = window.textsecure.storage.protocol;
@ -628,6 +647,7 @@ export default class AccountManager extends EventTarget {
window.log.info('confirmKeys: confirming key', key.keyId);
await store.storeSignedPreKey(key.keyId, key.keyPair, confirmed);
}
async generateKeys(count: number, providedProgressCallback?: Function) {
const progressCallback =
typeof providedProgressCallback === 'function'
@ -695,6 +715,7 @@ export default class AccountManager extends EventTarget {
);
});
}
async registrationDone({ uuid, number }: { uuid?: string; number?: string }) {
window.log.info('registration done');

View File

@ -1,3 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable max-classes-per-file */
import { ByteBufferClass } from '../window.d';
import { AttachmentType } from './SendMessage';
@ -18,6 +21,7 @@ export type PackedAttachmentType = AttachmentType & {
export class ProtoParser {
buffer: ByteBufferClass;
protobuf: ProtobufConstructorType;
constructor(arrayBuffer: ArrayBuffer, protobuf: ProtobufConstructorType) {
@ -28,7 +32,7 @@ export class ProtoParser {
this.buffer.limit = arrayBuffer.byteLength;
}
next() {
next(): ProtobufType | undefined | null {
try {
if (this.buffer.limit === this.buffer.offset) {
return undefined; // eof

View File

@ -1,13 +1,119 @@
// tslint:disable no-bitwise no-default-export
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable no-bitwise */
/* eslint-disable more/no-then */
import { ByteBufferClass } from '../window.d';
declare global {
// this is fixed in already, and won't be necessary when the new definitions
// files are used: https://github.com/microsoft/TSJS-lib-generator/pull/843
export interface SubtleCrypto {
decrypt(
algorithm:
| string
| RsaOaepParams
| AesCtrParams
| AesCbcParams
| AesCmacParams
| AesGcmParams
| AesCfbParams,
key: CryptoKey,
data:
| Int8Array
| Int16Array
| Int32Array
| Uint8Array
| Uint16Array
| Uint32Array
| Uint8ClampedArray
| Float32Array
| Float64Array
| DataView
| ArrayBuffer
): Promise<ArrayBuffer>;
digest(
algorithm: string | Algorithm,
data:
| Int8Array
| Int16Array
| Int32Array
| Uint8Array
| Uint16Array
| Uint32Array
| Uint8ClampedArray
| Float32Array
| Float64Array
| DataView
| ArrayBuffer
): Promise<ArrayBuffer>;
importKey(
format: 'raw' | 'pkcs8' | 'spki',
keyData:
| Int8Array
| Int16Array
| Int32Array
| Uint8Array
| Uint16Array
| Uint32Array
| Uint8ClampedArray
| Float32Array
| Float64Array
| DataView
| ArrayBuffer,
algorithm:
| string
| RsaHashedImportParams
| EcKeyImportParams
| HmacImportParams
| DhImportKeyParams
| AesKeyAlgorithm,
extractable: boolean,
keyUsages: string[]
): Promise<CryptoKey>;
importKey(
format: string,
keyData:
| JsonWebKey
| Int8Array
| Int16Array
| Int32Array
| Uint8Array
| Uint16Array
| Uint32Array
| Uint8ClampedArray
| Float32Array
| Float64Array
| DataView
| ArrayBuffer,
algorithm:
| string
| RsaHashedImportParams
| EcKeyImportParams
| HmacImportParams
| DhImportKeyParams
| AesKeyAlgorithm,
extractable: boolean,
keyUsages: string[]
): Promise<CryptoKey>;
}
}
const PROFILE_IV_LENGTH = 12; // bytes
const PROFILE_KEY_LENGTH = 32; // bytes
const PROFILE_TAG_LENGTH = 128; // bits
const PROFILE_NAME_PADDED_LENGTH = 53; // bytes
function verifyDigest(data: ArrayBuffer, theirDigest: ArrayBuffer) {
interface EncryptedAttachment {
ciphertext: ArrayBuffer;
digest: ArrayBuffer;
}
async function verifyDigest(
data: ArrayBuffer,
theirDigest: ArrayBuffer
): Promise<void> {
return window.crypto.subtle
.digest({ name: 'SHA-256' }, data)
.then(ourDigest => {
@ -32,7 +138,7 @@ const Crypto = {
async decryptWebsocketMessage(
message: ByteBufferClass,
signalingKey: ArrayBuffer
) {
): Promise<ArrayBuffer> {
const decodedMessage = message.toArrayBuffer();
if (signalingKey.byteLength !== 52) {
@ -75,7 +181,7 @@ const Crypto = {
encryptedBin: ArrayBuffer,
keys: ArrayBuffer,
theirDigest: ArrayBuffer
) {
): Promise<ArrayBuffer> {
if (keys.byteLength !== 64) {
throw new Error('Got invalid length attachment keys');
}
@ -112,7 +218,7 @@ const Crypto = {
plaintext: ArrayBuffer,
keys: ArrayBuffer,
iv: ArrayBuffer
) {
): Promise<EncryptedAttachment> {
if (!(plaintext instanceof ArrayBuffer) && !ArrayBuffer.isView(plaintext)) {
throw new TypeError(
`\`plaintext\` must be an \`ArrayBuffer\` or \`ArrayBufferView\`; got: ${typeof plaintext}`
@ -152,7 +258,11 @@ const Crypto = {
});
});
},
async encryptProfile(data: ArrayBuffer, key: ArrayBuffer) {
async encryptProfile(
data: ArrayBuffer,
key: ArrayBuffer
): Promise<ArrayBuffer> {
const iv = window.libsignal.crypto.getRandomBytes(PROFILE_IV_LENGTH);
if (key.byteLength !== PROFILE_KEY_LENGTH) {
throw new Error('Got invalid length profile key');
@ -179,7 +289,11 @@ const Crypto = {
})
);
},
async decryptProfile(data: ArrayBuffer, key: ArrayBuffer) {
async decryptProfile(
data: ArrayBuffer,
key: ArrayBuffer
): Promise<ArrayBuffer> {
if (data.byteLength < 12 + 16 + 1) {
throw new Error(`Got too short input: ${data.byteLength}`);
}
@ -201,8 +315,6 @@ const Crypto = {
keyForEncryption,
ciphertext
)
// Typescript says that there's no .catch() available here
// @ts-ignore
.catch((e: Error) => {
if (e.name === 'OperationError') {
// bad mac, basically.
@ -211,15 +323,25 @@ const Crypto = {
error.name = 'ProfileDecryptError';
throw error;
}
return (undefined as unknown) as ArrayBuffer; // uses of this function are not guarded
})
);
},
async encryptProfileName(name: ArrayBuffer, key: ArrayBuffer) {
async encryptProfileName(
name: ArrayBuffer,
key: ArrayBuffer
): Promise<ArrayBuffer> {
const padded = new Uint8Array(PROFILE_NAME_PADDED_LENGTH);
padded.set(new Uint8Array(name));
return Crypto.encryptProfile(padded.buffer as ArrayBuffer, key);
},
async decryptProfileName(encryptedProfileName: string, key: ArrayBuffer) {
async decryptProfileName(
encryptedProfileName: string,
key: ArrayBuffer
): Promise<{ given: ArrayBuffer; family: ArrayBuffer | null }> {
const data = window.dcodeIO.ByteBuffer.wrap(
encryptedProfileName,
'base64'
@ -261,7 +383,7 @@ const Crypto = {
});
},
getRandomBytes(size: number) {
getRandomBytes(size: number): ArrayBuffer {
return window.libsignal.crypto.getRandomBytes(size);
},
};

View File

@ -1,4 +1,5 @@
// tslint:disable max-classes-per-file
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable max-classes-per-file */
function appendStack(newError: Error, originalError: Error) {
// eslint-disable-next-line no-param-reassign
@ -7,7 +8,9 @@ function appendStack(newError: Error, originalError: Error) {
export class ReplayableError extends Error {
name: string;
message: string;
functionCode?: number;
constructor(options: {
@ -32,6 +35,7 @@ export class ReplayableError extends Error {
export class IncomingIdentityKeyError extends ReplayableError {
identifier: string;
identityKey: ArrayBuffer;
// Note: Data to resend message is no longer captured
@ -50,6 +54,7 @@ export class IncomingIdentityKeyError extends ReplayableError {
export class OutgoingIdentityKeyError extends ReplayableError {
identifier: string;
identityKey: ArrayBuffer;
// Note: Data to resend message is no longer captured
@ -73,6 +78,7 @@ export class OutgoingIdentityKeyError extends ReplayableError {
export class OutgoingMessageError extends ReplayableError {
identifier: string;
code?: any;
// Note: Data to resend message is no longer captured
@ -101,13 +107,13 @@ export class OutgoingMessageError extends ReplayableError {
export class SendMessageNetworkError extends ReplayableError {
identifier: string;
constructor(identifier: string, _m: any, httpError: Error) {
constructor(identifier: string, _m: unknown, httpError: Error) {
super({
name: 'SendMessageNetworkError',
message: httpError.message,
});
this.identifier = identifier.split('.')[0];
[this.identifier] = identifier.split('.');
this.code = httpError.code;
appendStack(this, httpError);
@ -126,7 +132,7 @@ export class SignedPreKeyRotationError extends ReplayableError {
export class MessageError extends ReplayableError {
code?: any;
constructor(_m: any, httpError: Error) {
constructor(_m: unknown, httpError: Error) {
super({
name: 'MessageError',
message: httpError.message,
@ -140,10 +146,11 @@ export class MessageError extends ReplayableError {
export class UnregisteredUserError extends Error {
identifier: string;
code?: any;
constructor(identifier: string, httpError: Error) {
const message = httpError.message;
const { message } = httpError;
super(message);

View File

@ -1,4 +1,8 @@
// tslint:disable no-default-export
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable guard-for-in */
/* eslint-disable no-restricted-syntax */
/* eslint-disable @typescript-eslint/ban-types */
/*
* Implements EventTarget
@ -8,7 +12,7 @@
export default class EventTarget {
listeners?: { [type: string]: Array<Function> };
dispatchEvent(ev: Event) {
dispatchEvent(ev: Event): Array<unknown> {
if (!(ev instanceof Event)) {
throw new Error('Expects an event');
}
@ -29,7 +33,7 @@ export default class EventTarget {
return results;
}
addEventListener(eventName: string, callback: Function) {
addEventListener(eventName: string, callback: Function): void {
if (typeof eventName !== 'string') {
throw new Error('First argument expects a string');
}
@ -47,7 +51,7 @@ export default class EventTarget {
this.listeners[eventName] = listeners;
}
removeEventListener(eventName: string, callback: Function) {
removeEventListener(eventName: string, callback: Function): void {
if (typeof eventName !== 'string') {
throw new Error('First argument expects a string');
}
@ -69,7 +73,7 @@ export default class EventTarget {
this.listeners[eventName] = listeners;
}
extend(source: any) {
extend(source: any): any {
const target = this as any;
// tslint:disable-next-line forin no-for-in no-default-export

View File

@ -1,3 +1,7 @@
/* eslint-disable guard-for-in */
/* eslint-disable no-restricted-syntax */
/* eslint-disable no-proto */
/* eslint-disable @typescript-eslint/no-explicit-any */
// tslint:disable no-default-export
import { ByteBufferClass } from '../window.d';
@ -7,11 +11,10 @@ const arrayBuffer = new ArrayBuffer(0);
const uint8Array = new Uint8Array();
let StaticByteBufferProto: any;
// @ts-ignore
const StaticArrayBufferProto = arrayBuffer.__proto__;
// @ts-ignore
const StaticUint8ArrayProto = uint8Array.__proto__;
const StaticArrayBufferProto = (arrayBuffer as any).__proto__;
const StaticUint8ArrayProto = (uint8Array as any).__proto__;
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
function getString(thing: any): string {
// Note: we must make this at runtime because it's loaded in the browser context
if (!ByteBuffer) {
@ -19,8 +22,7 @@ function getString(thing: any): string {
}
if (!StaticByteBufferProto) {
// @ts-ignore
StaticByteBufferProto = ByteBuffer.__proto__;
StaticByteBufferProto = (ByteBuffer as any).__proto__;
}
if (thing === Object(thing)) {
@ -52,28 +54,30 @@ function getStringable(thing: any): boolean {
function ensureStringed(thing: any): any {
if (getStringable(thing)) {
return getString(thing);
} else if (thing instanceof Array) {
}
if (thing instanceof Array) {
const res = [];
for (let i = 0; i < thing.length; i += 1) {
res[i] = ensureStringed(thing[i]);
}
return res;
} else if (thing === Object(thing)) {
}
if (thing === Object(thing)) {
const res: any = {};
// tslint:disable-next-line forin no-for-in no-default-export
for (const key in thing) {
res[key] = ensureStringed(thing[key]);
}
return res;
} else if (thing === null) {
}
if (thing === null) {
return null;
}
throw new Error(`unsure of how to jsonify object of type ${typeof thing}`);
}
function stringToArrayBuffer(string: string) {
function stringToArrayBuffer(string: string): ArrayBuffer {
if (typeof string !== 'string') {
throw new TypeError("'string' must be a string");
}
@ -88,11 +92,12 @@ function stringToArrayBuffer(string: string) {
// Number formatting utils
const utils = {
getString,
isNumberSane: (number: string) =>
isNumberSane: (number: string): boolean =>
number[0] === '+' && /^[0-9]+$/.test(number.substring(1)),
jsonThing: (thing: any) => JSON.stringify(ensureStringed(thing)),
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
jsonThing: (thing: unknown) => JSON.stringify(ensureStringed(thing)),
stringToArrayBuffer,
unencodeNumber: (number: string) => number.split('.'),
unencodeNumber: (number: string): Array<string> => number.split('.'),
};
export default utils;

View File

@ -1,19 +1,25 @@
// tslint:disable no-bitwise no-default-export
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable no-bitwise */
/* eslint-disable class-methods-use-this */
/* eslint-disable more/no-then */
/* eslint-disable camelcase */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable max-classes-per-file */
import { isNumber, map, omit } from 'lodash';
import { w3cwebsocket as WebSocket } from 'websocket';
import PQueue from 'p-queue';
import { v4 as getGuid } from 'uuid';
import { SessionCipherClass, SignalProtocolAddressClass } from '../libsignal.d';
import { BatcherType, createBatcher } from '../util/batcher';
import EventTarget from './EventTarget';
import { WebAPIType } from './WebAPI';
import { BatcherType, createBatcher } from '../util/batcher';
import utils from './Helpers';
import WebSocketResource, {
IncomingWebSocketRequest,
} from './WebsocketResources';
import Crypto from './Crypto';
import { SessionCipherClass, SignalProtocolAddressClass } from '../libsignal.d';
import { ContactBuffer, GroupBuffer } from './ContactsParser';
import { IncomingIdentityKeyError } from './Errors';
@ -30,6 +36,8 @@ import {
VerifiedClass,
} from '../textsecure.d';
import { WebSocket } from './WebSocket';
import { deriveGroupFields, MASTER_KEY_LENGTH } from '../groups';
const RETRY_TIMEOUT = 2 * 60 * 1000;
@ -84,30 +92,51 @@ type CacheUpdateItemType = {
class MessageReceiverInner extends EventTarget {
_onClose?: (ev: any) => Promise<void>;
appQueue: PQueue;
cacheAddBatcher: BatcherType<CacheAddItemType>;
cacheRemoveBatcher: BatcherType<string>;
cacheUpdateBatcher: BatcherType<CacheUpdateItemType>;
calledClose?: boolean;
count: number;
deviceId: number;
hasConnected?: boolean;
incomingQueue: PQueue;
isEmptied?: boolean;
// tslint:disable-next-line variable-name
number_id: string | null;
password: string;
pendingQueue: PQueue;
retryCachedTimeout: any;
server: WebAPIType;
serverTrustRoot: ArrayBuffer;
signalingKey: ArrayBuffer;
socket?: WebSocket;
stoppingProcessing?: boolean;
username: string;
uuid: string;
// tslint:disable-next-line variable-name
uuid_id: string | null;
wsr?: WebSocketResource;
constructor(
@ -175,10 +204,13 @@ class MessageReceiverInner extends EventTarget {
static stringToArrayBuffer = (string: string): ArrayBuffer =>
window.dcodeIO.ByteBuffer.wrap(string, 'binary').toArrayBuffer();
static arrayBufferToString = (arrayBuffer: ArrayBuffer): string =>
window.dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('binary');
static stringToArrayBufferBase64 = (string: string): ArrayBuffer =>
window.dcodeIO.ByteBuffer.wrap(string, 'base64').toArrayBuffer();
static arrayBufferToStringBase64 = (arrayBuffer: ArrayBuffer): string =>
window.dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('base64');
@ -237,12 +269,9 @@ class MessageReceiverInner extends EventTarget {
shutdown() {
if (this.socket) {
// @ts-ignore
this.socket.onclose = null;
// @ts-ignore
this.socket.onerror = null;
// @ts-ignore
this.socket.onopen = null;
delete this.socket.onclose;
delete this.socket.onerror;
delete this.socket.onopen;
this.socket = undefined;
}
@ -253,6 +282,7 @@ class MessageReceiverInner extends EventTarget {
this.wsr = undefined;
}
}
async close() {
window.log.info('MessageReceiver.close()');
this.calledClose = true;
@ -267,18 +297,22 @@ class MessageReceiverInner extends EventTarget {
return this.drain();
}
onopen() {
window.log.info('websocket open');
}
onerror() {
window.log.error('websocket error');
}
async dispatchAndWait(event: Event) {
// tslint:disable-next-line no-floating-promises
this.appQueue.add(async () => Promise.all(this.dispatchEvent(event)));
return Promise.resolve();
}
async onclose(ev: any) {
window.log.info(
'websocket closed',
@ -309,6 +343,7 @@ class MessageReceiverInner extends EventTarget {
return this.dispatchAndWait(event);
});
}
handleRequest(request: IncomingWebSocketRequest) {
// We do the message decryption here, instead of in the ordered pending queue,
// to avoid exposing the time it took us to process messages through the time-to-ack.
@ -395,6 +430,7 @@ class MessageReceiverInner extends EventTarget {
// tslint:disable-next-line no-floating-promises
this.incomingQueue.add(job);
}
calculateMessageAge(
headers: Array<string>,
serverTimestamp?: number
@ -404,6 +440,7 @@ class MessageReceiverInner extends EventTarget {
if (serverTimestamp) {
// The 'X-Signal-Timestamp' is usually the last item, so start there.
let it = headers.length;
// eslint-disable-next-line no-plusplus
while (--it >= 0) {
const match = headers[it].match(/^X-Signal-Timestamp:\s*(\d+)\s*$/);
if (match && match.length === 2) {
@ -422,6 +459,7 @@ class MessageReceiverInner extends EventTarget {
return messageAgeSec;
}
async addToQueue(task: () => Promise<void>) {
this.count += 1;
@ -437,9 +475,11 @@ class MessageReceiverInner extends EventTarget {
return promise;
}
hasEmptied(): boolean {
return Boolean(this.isEmptied);
}
onEmpty() {
const emitEmpty = () => {
window.log.info("MessageReceiver: emitting 'empty' event");
@ -478,6 +518,7 @@ class MessageReceiverInner extends EventTarget {
// tslint:disable-next-line no-floating-promises
waitForCacheAddBatcher();
}
async drain() {
const waitForIncomingQueue = async () =>
this.addToQueue(async () => {
@ -486,6 +527,7 @@ class MessageReceiverInner extends EventTarget {
return this.incomingQueue.add(waitForIncomingQueue);
}
updateProgress(count: number) {
// count by 10s
if (count % 10 !== 0) {
@ -495,6 +537,7 @@ class MessageReceiverInner extends EventTarget {
ev.count = count;
this.dispatchEvent(ev);
}
async queueAllCached() {
const items = await this.getAllFromCache();
const max = items.length;
@ -503,6 +546,7 @@ class MessageReceiverInner extends EventTarget {
await this.queueCached(items[i]);
}
}
async queueCached(item: UnprocessedType) {
try {
let envelopePlaintext: ArrayBuffer;
@ -573,6 +617,7 @@ class MessageReceiverInner extends EventTarget {
}
}
}
getEnvelopeId(envelope: EnvelopeClass) {
if (envelope.sourceUuid || envelope.source) {
return `${envelope.sourceUuid || envelope.source}.${
@ -582,12 +627,14 @@ class MessageReceiverInner extends EventTarget {
return envelope.id;
}
clearRetryTimeout() {
if (this.retryCachedTimeout) {
clearInterval(this.retryCachedTimeout);
this.retryCachedTimeout = null;
}
}
maybeScheduleRetryTimeout() {
if (this.isEmptied) {
this.clearRetryTimeout();
@ -597,6 +644,7 @@ class MessageReceiverInner extends EventTarget {
}, RETRY_TIMEOUT);
}
}
async getAllFromCache() {
window.log.info('getAllFromCache');
const count = await window.textsecure.storage.unprocessed.getCount();
@ -640,6 +688,7 @@ class MessageReceiverInner extends EventTarget {
})
);
}
async cacheAndQueueBatch(items: Array<CacheAddItemType>) {
const dataArray = items.map(item => item.data);
try {
@ -661,6 +710,7 @@ class MessageReceiverInner extends EventTarget {
);
}
}
cacheAndQueue(
envelope: EnvelopeClass,
plaintext: ArrayBuffer,
@ -680,9 +730,11 @@ class MessageReceiverInner extends EventTarget {
data,
});
}
async cacheUpdateBatch(items: Array<Partial<UnprocessedType>>) {
await window.textsecure.storage.unprocessed.addDecryptedDataToList(items);
}
updateCache(envelope: EnvelopeClass, plaintext: ArrayBuffer) {
const { id } = envelope;
const data = {
@ -694,13 +746,16 @@ class MessageReceiverInner extends EventTarget {
};
this.cacheUpdateBatcher.add({ id, data });
}
async cacheRemoveBatch(items: Array<string>) {
await window.textsecure.storage.unprocessed.remove(items);
}
removeFromCache(envelope: EnvelopeClass) {
const { id } = envelope;
this.cacheRemoveBatcher.add(id);
}
async queueDecryptedEnvelope(
envelope: EnvelopeClass,
plaintext: ArrayBuffer
@ -722,6 +777,7 @@ class MessageReceiverInner extends EventTarget {
);
});
}
async queueEnvelope(envelope: EnvelopeClass) {
const id = this.getEnvelopeId(envelope);
window.log.info('queueing envelope', id);
@ -747,6 +803,7 @@ class MessageReceiverInner extends EventTarget {
}
});
}
// Same as handleEnvelope, just without the decryption step. Necessary for handling
// messages which were successfully decrypted, but application logic didn't finish
// processing.
@ -764,7 +821,8 @@ class MessageReceiverInner extends EventTarget {
await this.innerHandleContentMessage(envelope, plaintext);
return;
} else if (envelope.legacyMessage) {
}
if (envelope.legacyMessage) {
await this.innerHandleLegacyMessage(envelope, plaintext);
return;
@ -773,6 +831,7 @@ class MessageReceiverInner extends EventTarget {
this.removeFromCache(envelope);
throw new Error('Received message with no content and no legacyMessage');
}
async handleEnvelope(envelope: EnvelopeClass) {
if (this.stoppingProcessing) {
return Promise.resolve();
@ -784,20 +843,24 @@ class MessageReceiverInner extends EventTarget {
if (envelope.content) {
return this.handleContentMessage(envelope);
} else if (envelope.legacyMessage) {
}
if (envelope.legacyMessage) {
return this.handleLegacyMessage(envelope);
}
this.removeFromCache(envelope);
throw new Error('Received message with no content and no legacyMessage');
}
getStatus() {
if (this.socket) {
return this.socket.readyState;
} else if (this.hasConnected) {
}
if (this.hasConnected) {
return WebSocket.CLOSED;
}
return -1;
}
async onDeliveryReceipt(envelope: EnvelopeClass) {
// tslint:disable-next-line promise-must-complete
return new Promise((resolve, reject) => {
@ -812,6 +875,7 @@ class MessageReceiverInner extends EventTarget {
this.dispatchAndWait(ev).then(resolve as any, reject as any);
});
}
unpad(paddedData: ArrayBuffer) {
const paddedPlaintext = new Uint8Array(paddedData);
let plaintext;
@ -837,11 +901,10 @@ class MessageReceiverInner extends EventTarget {
): Promise<ArrayBuffer> {
const { serverTrustRoot } = this;
let address: SignalProtocolAddressClass;
let promise;
const identifier = envelope.sourceUuid || envelope.source;
address = new window.libsignal.SignalProtocolAddress(
const address = new window.libsignal.SignalProtocolAddress(
// Using source as opposed to sourceUuid allows us to get the existing
// session if we haven't yet harvested the incoming uuid
identifier as any,
@ -1034,6 +1097,7 @@ class MessageReceiverInner extends EventTarget {
return this.dispatchAndWait(ev).then(returnError, returnError);
});
}
async decryptPreKeyWhisperMessage(
ciphertext: ArrayBuffer,
sessionCipher: SessionCipherClass,
@ -1057,6 +1121,7 @@ class MessageReceiverInner extends EventTarget {
throw e;
}
}
async handleSentMessage(
envelope: EnvelopeClass,
sentContainer: SyncMessageClass.Sent
@ -1114,7 +1179,7 @@ class MessageReceiverInner extends EventTarget {
)} ignored; destined for blocked group`
);
this.removeFromCache(envelope);
return;
return undefined;
}
const ev = new Event('sent');
@ -1136,6 +1201,7 @@ class MessageReceiverInner extends EventTarget {
})
);
}
async handleDataMessage(envelope: EnvelopeClass, msg: DataMessageClass) {
window.log.info('data message from', this.getEnvelopeId(envelope));
let p: Promise<any> = Promise.resolve();
@ -1152,7 +1218,7 @@ class MessageReceiverInner extends EventTarget {
window.log.info(
'MessageReceiver.handleDataMessage: dropping GroupV2 message'
);
return;
return undefined;
}
this.deriveGroupsV2Data(msg);
@ -1204,7 +1270,7 @@ class MessageReceiverInner extends EventTarget {
)} ignored; destined for blocked group`
);
this.removeFromCache(envelope);
return;
return undefined;
}
const ev = new Event('message');
@ -1222,6 +1288,7 @@ class MessageReceiverInner extends EventTarget {
})
);
}
async handleLegacyMessage(envelope: EnvelopeClass) {
return this.decrypt(envelope, envelope.legacyMessage).then(plaintext => {
if (!plaintext) {
@ -1231,6 +1298,7 @@ class MessageReceiverInner extends EventTarget {
return this.innerHandleLegacyMessage(envelope, plaintext);
});
}
async innerHandleLegacyMessage(
envelope: EnvelopeClass,
plaintext: ArrayBuffer
@ -1238,6 +1306,7 @@ class MessageReceiverInner extends EventTarget {
const message = window.textsecure.protobuf.DataMessage.decode(plaintext);
return this.handleDataMessage(envelope, message);
}
async handleContentMessage(envelope: EnvelopeClass) {
return this.decrypt(envelope, envelope.content).then(plaintext => {
if (!plaintext) {
@ -1247,6 +1316,7 @@ class MessageReceiverInner extends EventTarget {
return this.innerHandleContentMessage(envelope, plaintext);
});
}
async innerHandleContentMessage(
envelope: EnvelopeClass,
plaintext: ArrayBuffer
@ -1254,21 +1324,27 @@ class MessageReceiverInner extends EventTarget {
const content = window.textsecure.protobuf.Content.decode(plaintext);
if (content.syncMessage) {
return this.handleSyncMessage(envelope, content.syncMessage);
} else if (content.dataMessage) {
}
if (content.dataMessage) {
return this.handleDataMessage(envelope, content.dataMessage);
} else if (content.nullMessage) {
}
if (content.nullMessage) {
this.handleNullMessage(envelope);
return;
} else if (content.callingMessage) {
return undefined;
}
if (content.callingMessage) {
return this.handleCallingMessage(envelope, content.callingMessage);
} else if (content.receiptMessage) {
}
if (content.receiptMessage) {
return this.handleReceiptMessage(envelope, content.receiptMessage);
} else if (content.typingMessage) {
}
if (content.typingMessage) {
return this.handleTypingMessage(envelope, content.typingMessage);
}
this.removeFromCache(envelope);
throw new Error('Unsupported content message');
}
async handleCallingMessage(
envelope: EnvelopeClass,
callingMessage: CallingMessageClass
@ -1279,6 +1355,7 @@ class MessageReceiverInner extends EventTarget {
callingMessage
);
}
async handleReceiptMessage(
envelope: EnvelopeClass,
receiptMessage: ReceiptMessageClass
@ -1319,6 +1396,7 @@ class MessageReceiverInner extends EventTarget {
}
return Promise.all(results);
}
handleTypingMessage(
envelope: EnvelopeClass,
typingMessage: TypingMessageClass
@ -1366,6 +1444,7 @@ class MessageReceiverInner extends EventTarget {
return this.dispatchEvent(ev);
}
handleNullMessage(envelope: EnvelopeClass) {
window.log.info('null message from', this.getEnvelopeId(envelope));
this.removeFromCache(envelope);
@ -1404,6 +1483,7 @@ class MessageReceiverInner extends EventTarget {
groupV2.groupChange = groupV2.groupChange.toString('base64');
}
}
getGroupId(message: DataMessageClass) {
if (message.groupV2) {
return message.groupV2.id;
@ -1418,11 +1498,11 @@ class MessageReceiverInner extends EventTarget {
getDestination(sentMessage: SyncMessageClass.Sent) {
if (sentMessage.message && sentMessage.message.groupV2) {
return `groupv2(${sentMessage.message.groupV2.id})`;
} else if (sentMessage.message && sentMessage.message.group) {
return `group(${sentMessage.message.group.id.toBinary()})`;
} else {
return sentMessage.destination || sentMessage.destinationUuid;
}
if (sentMessage.message && sentMessage.message.group) {
return `group(${sentMessage.message.group.id.toBinary()})`;
}
return sentMessage.destination || sentMessage.destinationUuid;
}
// tslint:disable-next-line cyclomatic-complexity
@ -1450,7 +1530,7 @@ class MessageReceiverInner extends EventTarget {
if (!fromSelfSource && !fromSelfSourceUuid) {
throw new Error('Received sync message from another number');
}
// tslint:disable-next-line triple-equals
// eslint-disable-next-line eqeqeq
if (envelope.sourceDevice == this.deviceId) {
throw new Error('Received sync message from our own device');
}
@ -1468,7 +1548,7 @@ class MessageReceiverInner extends EventTarget {
window.log.info(
'MessageReceiver.handleSyncMessage: dropping GroupV2 message'
);
return;
return undefined;
}
this.deriveGroupsV2Data(sentMessage.message);
@ -1481,26 +1561,34 @@ class MessageReceiverInner extends EventTarget {
this.getEnvelopeId(envelope)
);
return this.handleSentMessage(envelope, sentMessage);
} else if (syncMessage.contacts) {
}
if (syncMessage.contacts) {
this.handleContacts(envelope, syncMessage.contacts);
return;
} else if (syncMessage.groups) {
return undefined;
}
if (syncMessage.groups) {
this.handleGroups(envelope, syncMessage.groups);
return;
} else if (syncMessage.blocked) {
return undefined;
}
if (syncMessage.blocked) {
return this.handleBlocked(envelope, syncMessage.blocked);
} else if (syncMessage.request) {
}
if (syncMessage.request) {
window.log.info('Got SyncMessage Request');
this.removeFromCache(envelope);
return;
} else if (syncMessage.read && syncMessage.read.length) {
return undefined;
}
if (syncMessage.read && syncMessage.read.length) {
window.log.info('read messages from', this.getEnvelopeId(envelope));
return this.handleRead(envelope, syncMessage.read);
} else if (syncMessage.verified) {
}
if (syncMessage.verified) {
return this.handleVerified(envelope, syncMessage.verified);
} else if (syncMessage.configuration) {
}
if (syncMessage.configuration) {
return this.handleConfiguration(envelope, syncMessage.configuration);
} else if (
}
if (
syncMessage.stickerPackOperation &&
syncMessage.stickerPackOperation.length > 0
) {
@ -1508,22 +1596,27 @@ class MessageReceiverInner extends EventTarget {
envelope,
syncMessage.stickerPackOperation
);
} else if (syncMessage.viewOnceOpen) {
}
if (syncMessage.viewOnceOpen) {
return this.handleViewOnceOpen(envelope, syncMessage.viewOnceOpen);
} else if (syncMessage.messageRequestResponse) {
}
if (syncMessage.messageRequestResponse) {
return this.handleMessageRequestResponse(
envelope,
syncMessage.messageRequestResponse
);
} else if (syncMessage.fetchLatest) {
}
if (syncMessage.fetchLatest) {
return this.handleFetchLatest(envelope, syncMessage.fetchLatest);
} else if (syncMessage.keys) {
}
if (syncMessage.keys) {
return this.handleKeys(envelope, syncMessage.keys);
}
this.removeFromCache(envelope);
throw new Error('Got empty SyncMessage');
}
async handleConfiguration(
envelope: EnvelopeClass,
configuration: SyncMessageClass.Configuration
@ -1534,6 +1627,7 @@ class MessageReceiverInner extends EventTarget {
ev.configuration = configuration;
return this.dispatchAndWait(ev);
}
async handleViewOnceOpen(
envelope: EnvelopeClass,
sync: SyncMessageClass.ViewOnceOpen
@ -1554,6 +1648,7 @@ class MessageReceiverInner extends EventTarget {
return this.dispatchAndWait(ev);
}
async handleMessageRequestResponse(
envelope: EnvelopeClass,
sync: SyncMessageClass.MessageRequestResponse
@ -1573,6 +1668,7 @@ class MessageReceiverInner extends EventTarget {
'MessageReceiver::handleMessageRequestResponse'
);
}
async handleFetchLatest(
envelope: EnvelopeClass,
sync: SyncMessageClass.FetchLatest
@ -1585,11 +1681,12 @@ class MessageReceiverInner extends EventTarget {
return this.dispatchAndWait(ev);
}
async handleKeys(envelope: EnvelopeClass, sync: SyncMessageClass.Keys) {
window.log.info('got keys sync message');
if (!sync.storageService) {
return;
return undefined;
}
const ev = new Event('keys');
@ -1598,6 +1695,7 @@ class MessageReceiverInner extends EventTarget {
return this.dispatchAndWait(ev);
}
async handleStickerPackOperation(
envelope: EnvelopeClass,
operations: Array<SyncMessageClass.StickerPackOperation>
@ -1615,6 +1713,7 @@ class MessageReceiverInner extends EventTarget {
}));
return this.dispatchAndWait(ev);
}
async handleVerified(envelope: EnvelopeClass, verified: VerifiedClass) {
const ev = new Event('verified');
ev.confirm = this.removeFromCache.bind(this, envelope);
@ -1631,6 +1730,7 @@ class MessageReceiverInner extends EventTarget {
);
return this.dispatchAndWait(ev);
}
async handleRead(
envelope: EnvelopeClass,
read: Array<SyncMessageClass.Read>
@ -1655,6 +1755,7 @@ class MessageReceiverInner extends EventTarget {
}
return Promise.all(results);
}
handleContacts(envelope: EnvelopeClass, contacts: SyncMessageClass.Contacts) {
window.log.info('contact sync');
const { blob } = contacts;
@ -1692,6 +1793,7 @@ class MessageReceiverInner extends EventTarget {
});
});
}
handleGroups(envelope: EnvelopeClass, groups: SyncMessageClass.Groups) {
window.log.info('group sync');
const { blob } = groups;
@ -1726,6 +1828,7 @@ class MessageReceiverInner extends EventTarget {
});
});
}
async handleBlocked(
envelope: EnvelopeClass,
blocked: SyncMessageClass.Blocked
@ -1750,19 +1853,22 @@ class MessageReceiverInner extends EventTarget {
await window.textsecure.storage.put('blocked-groups', groupIds);
this.removeFromCache(envelope);
return;
}
isBlocked(number: string) {
return window.textsecure.storage.get('blocked', []).includes(number);
}
isUuidBlocked(uuid: string) {
return window.textsecure.storage.get('blocked-uuids', []).includes(uuid);
}
isGroupBlocked(groupId: string) {
return window.textsecure.storage
.get('blocked-groups', [])
.includes(groupId);
}
cleanAttachment(attachment: AttachmentPointerClass) {
return {
...omit(attachment, 'thumbnail'),
@ -1771,6 +1877,7 @@ class MessageReceiverInner extends EventTarget {
digest: attachment.digest ? attachment.digest.toString('base64') : null,
};
}
private isLinkPreviewDateValid(value: unknown): value is number {
return (
typeof value === 'number' &&
@ -1779,6 +1886,7 @@ class MessageReceiverInner extends EventTarget {
value > 0
);
}
private cleanLinkPreviewDate(value: unknown): number | null {
if (this.isLinkPreviewDateValid(value)) {
return value;
@ -1794,6 +1902,7 @@ class MessageReceiverInner extends EventTarget {
}
return this.isLinkPreviewDateValid(result) ? result : null;
}
async downloadAttachment(
attachment: AttachmentPointerClass
): Promise<DownloadAttachmentType> {
@ -1826,12 +1935,14 @@ class MessageReceiverInner extends EventTarget {
data,
};
}
async handleAttachment(
attachment: AttachmentPointerClass
): Promise<DownloadAttachmentType> {
const cleaned = this.cleanAttachment(attachment);
return this.downloadAttachment(cleaned);
}
async handleEndSession(identifier: string) {
window.log.info('got end session');
const deviceIds = await window.textsecure.storage.protocol.getDeviceIds(
@ -1890,7 +2001,8 @@ class MessageReceiverInner extends EventTarget {
decrypted.attachments = [];
decrypted.group = null;
return Promise.resolve(decrypted);
} else if (decrypted.flags & FLAGS.EXPIRATION_TIMER_UPDATE) {
}
if (decrypted.flags & FLAGS.EXPIRATION_TIMER_UPDATE) {
decrypted.body = null;
decrypted.attachments = [];
} else if (decrypted.flags & FLAGS.PROFILE_KEY_UPDATE) {
@ -2053,20 +2165,30 @@ export default class MessageReceiver {
}
addEventListener: (name: string, handler: Function) => void;
close: () => Promise<void>;
downloadAttachment: (
attachment: AttachmentPointerClass
) => Promise<DownloadAttachmentType>;
getStatus: () => number;
hasEmptied: () => boolean;
removeEventListener: (name: string, handler: Function) => void;
stopProcessing: () => Promise<void>;
unregisterBatchers: () => void;
static stringToArrayBuffer = MessageReceiverInner.stringToArrayBuffer;
static arrayBufferToString = MessageReceiverInner.arrayBufferToString;
static stringToArrayBufferBase64 =
MessageReceiverInner.stringToArrayBufferBase64;
static arrayBufferToStringBase64 =
MessageReceiverInner.arrayBufferToStringBase64;
}

View File

@ -1,4 +1,9 @@
// tslint:disable no-default-export
/* eslint-disable guard-for-in */
/* eslint-disable no-restricted-syntax */
/* eslint-disable class-methods-use-this */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable more/no-then */
/* eslint-disable no-param-reassign */
import { reject } from 'lodash';
import { ServerKeysType, WebAPIType } from './WebAPI';
@ -24,25 +29,38 @@ type OutgoingMessageOptionsType = SendOptionsType & {
export default class OutgoingMessage {
server: WebAPIType;
timestamp: number;
identifiers: Array<string>;
message: ContentClass;
callback: (result: CallbackResultType) => void;
silent?: boolean;
plaintext?: Uint8Array;
identifiersCompleted: number;
errors: Array<any>;
successfulIdentifiers: Array<any>;
failoverIdentifiers: Array<any>;
unidentifiedDeliveries: Array<any>;
errors: Array<unknown>;
successfulIdentifiers: Array<unknown>;
failoverIdentifiers: Array<unknown>;
unidentifiedDeliveries: Array<unknown>;
discoveredIdentifierPairs: Array<{
e164: string;
uuid: string;
}>;
sendMetadata?: SendMetadataType;
senderCertificate?: ArrayBuffer;
online?: boolean;
constructor(
@ -76,12 +94,13 @@ export default class OutgoingMessage {
this.unidentifiedDeliveries = [];
this.discoveredIdentifierPairs = [];
const { sendMetadata, senderCertificate, online } = options || ({} as any);
const { sendMetadata, senderCertificate, online } = options;
this.sendMetadata = sendMetadata;
this.senderCertificate = senderCertificate;
this.online = online;
}
numberCompleted() {
numberCompleted(): void {
this.identifiersCompleted += 1;
if (this.identifiersCompleted >= this.identifiers.length) {
this.callback({
@ -93,9 +112,9 @@ export default class OutgoingMessage {
});
}
}
registerError(identifier: string, reason: string, error?: Error) {
registerError(identifier: string, reason: string, error?: Error): void {
if (!error || (error.name === 'HTTPError' && error.code !== 404)) {
// tslint:disable-next-line no-parameter-reassignment
error = new OutgoingMessageError(
identifier,
this.message.toArrayBuffer(),
@ -104,11 +123,11 @@ export default class OutgoingMessage {
);
}
// eslint-disable-next-line no-param-reassign
error.reason = reason;
this.errors[this.errors.length] = error;
this.numberCompleted();
}
reloadDevicesAndSend(
identifier: string,
recurse?: boolean
@ -123,14 +142,17 @@ export default class OutgoingMessage {
'Got empty device list when loading device keys',
undefined
);
return;
return undefined;
}
return this.doSendMessage(identifier, deviceIds, recurse);
});
}
// tslint:disable-next-line max-func-body-length
async getKeysForIdentifier(identifier: string, updateDevices: Array<number>) {
async getKeysForIdentifier(
identifier: string,
updateDevices: Array<number>
): Promise<void | Array<void | null>> {
const handleResult = async (response: ServerKeysType) =>
Promise.all(
response.devices.map(async device => {
@ -194,7 +216,7 @@ export default class OutgoingMessage {
return this.server.getKeysForIdentifier(identifier).then(handleResult);
}
let promise: Promise<any> = Promise.resolve();
let promise: Promise<void | Array<void | null>> = Promise.resolve();
updateDevices.forEach(deviceId => {
promise = promise.then(async () => {
let innerPromise;
@ -238,10 +260,10 @@ export default class OutgoingMessage {
async transmitMessage(
identifier: string,
jsonData: Array<any>,
jsonData: Array<unknown>,
timestamp: number,
{ accessKey }: { accessKey?: string } = {}
) {
): Promise<void> {
let promise;
if (accessKey) {
@ -277,7 +299,7 @@ export default class OutgoingMessage {
});
}
getPaddedMessageLength(messageLength: number) {
getPaddedMessageLength(messageLength: number): number {
const messageLengthWithTerminator = messageLength + 1;
let messagePartCount = Math.floor(messageLengthWithTerminator / 160);
@ -288,7 +310,7 @@ export default class OutgoingMessage {
return messagePartCount * 160;
}
getPlaintext() {
getPlaintext(): ArrayBuffer {
if (!this.plaintext) {
const messageBuffer = this.message.toArrayBuffer();
this.plaintext = new Uint8Array(
@ -380,22 +402,21 @@ export default class OutgoingMessage {
),
content: window.Signal.Crypto.arrayBufferToBase64(ciphertext),
};
} else {
const sessionCipher = new window.libsignal.SessionCipher(
window.textsecure.storage.protocol,
address,
options
);
ciphers[address.getDeviceId()] = sessionCipher;
const ciphertext = await sessionCipher.encrypt(plaintext);
return {
type: ciphertext.type,
destinationDeviceId: address.getDeviceId(),
destinationRegistrationId: ciphertext.registrationId,
content: btoa(ciphertext.body),
};
}
const sessionCipher = new window.libsignal.SessionCipher(
window.textsecure.storage.protocol,
address,
options
);
ciphers[address.getDeviceId()] = sessionCipher;
const ciphertext = await sessionCipher.encrypt(plaintext);
return {
type: ciphertext.type,
destinationDeviceId: address.getDeviceId(),
destinationRegistrationId: ciphertext.registrationId,
content: btoa(ciphertext.body),
};
})
)
.then(async jsonData => {
@ -446,7 +467,7 @@ export default class OutgoingMessage {
'Hit retry limit attempting to reload device list',
error
);
return;
return undefined;
}
let p: Promise<any> = Promise.resolve();
@ -479,7 +500,8 @@ export default class OutgoingMessage {
this.reloadDevicesAndSend(identifier, error.code === 409)
);
});
} else if (error.message === 'Identity key changed') {
}
if (error.message === 'Identity key changed') {
// eslint-disable-next-line no-param-reassign
error.timestamp = this.timestamp;
// eslint-disable-next-line no-param-reassign
@ -526,10 +548,14 @@ export default class OutgoingMessage {
'Failed to create or send message',
error
);
return undefined;
});
}
async getStaleDeviceIdsForIdentifier(identifier: string) {
async getStaleDeviceIdsForIdentifier(
identifier: string
): Promise<Array<number>> {
return window.textsecure.storage.protocol
.getDeviceIds(identifier)
.then(async deviceIds => {
@ -560,9 +586,8 @@ export default class OutgoingMessage {
async removeDeviceIdsForIdentifier(
identifier: string,
deviceIdsToRemove: Array<number>
) {
): Promise<void> {
let promise = Promise.resolve();
// tslint:disable-next-line forin no-for-in no-for-in-array
for (const j in deviceIdsToRemove) {
promise = promise.then(async () => {
const encodedAddress = `${identifier}.${deviceIdsToRemove[j]}`;
@ -572,7 +597,7 @@ export default class OutgoingMessage {
return promise;
}
async sendToIdentifier(providedIdentifier: string) {
async sendToIdentifier(providedIdentifier: string): Promise<void> {
let identifier = providedIdentifier;
try {
if (isRemoteFlagEnabled('desktop.cds')) {

View File

@ -1,4 +1,5 @@
// tslint:disable no-default-export
/* eslint-disable more/no-then */
/* eslint-disable max-classes-per-file */
import { KeyPairType } from '../libsignal.d';
import { ProvisionEnvelopeClass } from '../textsecure.d';
@ -73,6 +74,7 @@ class ProvisioningCipherInner {
});
});
}
async getPublicKey(): Promise<ArrayBuffer> {
return Promise.resolve()
.then(async () => {
@ -107,5 +109,6 @@ export default class ProvisioningCipher {
decrypt: (
provisionEnvelope: ProvisionEnvelopeClass
) => Promise<ProvisionDecryptResult>;
getPublicKey: () => Promise<ArrayBuffer>;
}

View File

@ -1,4 +1,9 @@
// tslint:disable no-bitwise no-default-export
/* eslint-disable no-nested-ternary */
/* eslint-disable class-methods-use-this */
/* eslint-disable more/no-then */
/* eslint-disable no-bitwise */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable max-classes-per-file */
import { Dictionary, without } from 'lodash';
import PQueue from 'p-queue';
@ -28,6 +33,7 @@ import {
GroupClass,
StorageServiceCallOptionsType,
StorageServiceCredentials,
SyncMessageClass,
} from '../textsecure.d';
import { MessageError, SignedPreKeyRotationError } from './Errors';
import { BodyRangesType } from '../types/Util';
@ -112,24 +118,38 @@ type MessageOptionsType = {
class Message {
attachments: Array<any>;
body?: string;
expireTimer?: number;
flags?: number;
group?: {
id: string;
type: number;
};
groupV2?: GroupV2InfoType;
needsSync?: boolean;
preview: any;
profileKey?: ArrayBuffer;
quote?: any;
recipients: Array<string>;
sticker?: any;
reaction?: any;
timestamp: number;
dataMessage: any;
attachmentPointers?: Array<any>;
// tslint:disable cyclomatic-complexity
@ -332,6 +352,7 @@ export type AttachmentType = {
export default class MessageSender {
server: WebAPIType;
pendingMessages: {
[id: string]: PQueue;
};
@ -341,14 +362,14 @@ export default class MessageSender {
this.pendingMessages = {};
}
_getAttachmentSizeBucket(size: number) {
_getAttachmentSizeBucket(size: number): number {
return Math.max(
541,
Math.floor(1.05 ** Math.ceil(Math.log(size) / Math.log(1.05)))
);
}
getPaddedAttachment(data: ArrayBuffer) {
getPaddedAttachment(data: ArrayBuffer): ArrayBuffer {
const size = data.byteLength;
const paddedSize = this._getAttachmentSizeBucket(size);
const padding = getZeroes(paddedSize - size);
@ -356,7 +377,9 @@ export default class MessageSender {
return concatenateBytes(data, padding);
}
async makeAttachmentPointer(attachment: AttachmentType) {
async makeAttachmentPointer(
attachment: AttachmentType
): Promise<AttachmentPointerClass | undefined> {
if (typeof attachment !== 'object' || attachment == null) {
return Promise.resolve(undefined);
}
@ -409,7 +432,10 @@ export default class MessageSender {
return proto;
}
async queueJobForIdentifier(identifier: string, runJob: () => Promise<any>) {
async queueJobForIdentifier(
identifier: string,
runJob: () => Promise<any>
): Promise<void> {
const { id } = await window.ConversationController.getOrCreateAndWait(
identifier,
'private'
@ -427,7 +453,7 @@ export default class MessageSender {
return queue.add(taskWithTimeout);
}
async uploadAttachments(message: Message) {
async uploadAttachments(message: Message): Promise<void> {
return Promise.all(
message.attachments.map(this.makeAttachmentPointer.bind(this))
)
@ -444,7 +470,7 @@ export default class MessageSender {
});
}
async uploadLinkPreviews(message: Message) {
async uploadLinkPreviews(message: Message): Promise<void> {
try {
const preview = await Promise.all(
(message.preview || []).map(async (item: PreviewType) => ({
@ -463,7 +489,7 @@ export default class MessageSender {
}
}
async uploadSticker(message: Message) {
async uploadSticker(message: Message): Promise<void> {
try {
const { sticker } = message;
@ -490,7 +516,7 @@ export default class MessageSender {
const { quote } = message;
if (!quote || !quote.attachments || quote.attachments.length === 0) {
return Promise.resolve();
return;
}
await Promise.all(
@ -546,6 +572,7 @@ export default class MessageSender {
})
);
}
sendMessageProto(
timestamp: number,
recipients: Array<string>,
@ -553,7 +580,7 @@ export default class MessageSender {
callback: (result: CallbackResultType) => void,
silent?: boolean,
options?: SendOptionsType
) {
): void {
const rejections = window.textsecure.storage.get(
'signedKeyRotationRejected',
0
@ -595,7 +622,6 @@ export default class MessageSender {
}
resolve(result);
return;
};
this.sendMessageProto(
@ -635,7 +661,7 @@ export default class MessageSender {
});
}
createSyncMessage() {
createSyncMessage(): SyncMessageClass {
const syncMessage = new window.textsecure.protobuf.SyncMessage();
// Generate a random int from 1 and 512
@ -656,9 +682,9 @@ export default class MessageSender {
expirationStartTimestamp: number | null,
sentTo: Array<string> = [],
unidentifiedDeliveries: Array<string> = [],
isUpdate: boolean = false,
isUpdate = false,
options?: SendOptionsType
) {
): Promise<CallbackResultType | void> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myDevice = window.textsecure.storage.user.getDeviceId();
@ -735,7 +761,7 @@ export default class MessageSender {
profileKeyVersion?: string;
profileKeyCredentialRequest?: string;
} = {}
) {
): Promise<any> {
const { accessKey } = options;
if (accessKey) {
@ -755,18 +781,21 @@ export default class MessageSender {
return this.server.getUuidsForE164s(numbers);
}
async getAvatar(path: string) {
async getAvatar(path: string): Promise<any> {
return this.server.getAvatar(path);
}
async getSticker(packId: string, stickerId: string) {
async getSticker(packId: string, stickerId: number): Promise<any> {
return this.server.getSticker(packId, stickerId);
}
async getStickerPackManifest(packId: string) {
async getStickerPackManifest(packId: string): Promise<any> {
return this.server.getStickerPackManifest(packId);
}
async sendRequestBlockSyncMessage(options?: SendOptionsType) {
async sendRequestBlockSyncMessage(
options?: SendOptionsType
): Promise<CallbackResultType | void> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myDevice = window.textsecure.storage.user.getDeviceId();
@ -792,7 +821,9 @@ export default class MessageSender {
return Promise.resolve();
}
async sendRequestConfigurationSyncMessage(options?: SendOptionsType) {
async sendRequestConfigurationSyncMessage(
options?: SendOptionsType
): Promise<CallbackResultType | void> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myDevice = window.textsecure.storage.user.getDeviceId();
@ -818,7 +849,9 @@ export default class MessageSender {
return Promise.resolve();
}
async sendRequestGroupSyncMessage(options?: SendOptionsType) {
async sendRequestGroupSyncMessage(
options?: SendOptionsType
): Promise<CallbackResultType | void> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myDevice = window.textsecure.storage.user.getDeviceId();
@ -843,7 +876,9 @@ export default class MessageSender {
return Promise.resolve();
}
async sendRequestContactSyncMessage(options?: SendOptionsType) {
async sendRequestContactSyncMessage(
options?: SendOptionsType
): Promise<CallbackResultType | void> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
@ -870,7 +905,9 @@ export default class MessageSender {
return Promise.resolve();
}
async sendFetchManifestSyncMessage(options?: SendOptionsType) {
async sendFetchManifestSyncMessage(
options?: SendOptionsType
): Promise<CallbackResultType | void> {
const myUuid = window.textsecure.storage.user.getUuid();
const myNumber = window.textsecure.storage.user.getNumber();
const myDevice = window.textsecure.storage.user.getDeviceId();
@ -898,7 +935,9 @@ export default class MessageSender {
);
}
async sendRequestKeySyncMessage(options?: SendOptionsType) {
async sendRequestKeySyncMessage(
options?: SendOptionsType
): Promise<CallbackResultType | void> {
const myUuid = window.textsecure.storage.user.getUuid();
const myNumber = window.textsecure.storage.user.getNumber();
const myDevice = window.textsecure.storage.user.getDeviceId();
@ -934,7 +973,7 @@ export default class MessageSender {
timestamp?: number;
},
sendOptions: SendOptionsType = {}
) {
): Promise<CallbackResultType | null> {
const ACTION_ENUM = window.textsecure.protobuf.TypingMessage.Action;
const { recipientId, groupId, groupMembers, isTyping, timestamp } = options;
@ -988,7 +1027,7 @@ export default class MessageSender {
recipients: Array<string>,
sendOptions: SendOptionsType,
groupId?: string
) {
): Promise<CallbackResultType> {
return this.sendMessage(
{
recipients,
@ -1012,7 +1051,7 @@ export default class MessageSender {
recipientId: string,
callingMessage: CallingMessageClass,
sendOptions?: SendOptionsType
) {
): Promise<void> {
const recipients = [recipientId];
const finalTimestamp = Date.now();
@ -1069,7 +1108,7 @@ export default class MessageSender {
senderUuid: string,
timestamps: Array<number>,
options?: SendOptionsType
) {
): Promise<CallbackResultType> {
const receiptMessage = new window.textsecure.protobuf.ReceiptMessage();
receiptMessage.type = window.textsecure.protobuf.ReceiptMessage.Type.READ;
receiptMessage.timestamp = timestamps;
@ -1086,6 +1125,7 @@ export default class MessageSender {
options
);
}
async syncReadMessages(
reads: Array<{
senderUuid?: string;
@ -1093,7 +1133,7 @@ export default class MessageSender {
timestamp: number;
}>,
options?: SendOptionsType
) {
): Promise<CallbackResultType | void> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myDevice = window.textsecure.storage.user.getDeviceId();
@ -1166,7 +1206,7 @@ export default class MessageSender {
type: number;
},
sendOptions?: SendOptionsType
) {
): Promise<CallbackResultType | null> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myDevice = window.textsecure.storage.user.getDeviceId();
@ -1251,7 +1291,7 @@ export default class MessageSender {
state: number,
identityKey: ArrayBuffer,
options?: SendOptionsType
) {
): Promise<CallbackResultType | void> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myDevice = window.textsecure.storage.user.getDeviceId();
@ -1318,7 +1358,9 @@ export default class MessageSender {
proto: DataMessageClass,
timestamp = Date.now(),
options = {}
) {
): Promise<
CallbackResultType | Omit<CallbackResultType, 'discoveredIdentifierPairs'>
> {
const myE164 = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const identifiers = providedIdentifiers.filter(
@ -1361,15 +1403,15 @@ export default class MessageSender {
destination: string,
body: string | undefined,
attachments: Array<AttachmentType>,
quote: any,
quote: unknown,
preview: Array<PreviewType>,
sticker: any,
reaction: any,
sticker: unknown,
reaction: unknown,
timestamp: number,
expireTimer: number | undefined,
profileKey?: ArrayBuffer,
flags?: number
) {
): Promise<ArrayBuffer> {
const attributes = {
recipients: [destination],
destination,
@ -1388,7 +1430,9 @@ export default class MessageSender {
return this.getMessageProtoObj(attributes);
}
async getMessageProtoObj(attributes: MessageOptionsType) {
async getMessageProtoObj(
attributes: MessageOptionsType
): Promise<ArrayBuffer> {
const message = new Message(attributes);
await Promise.all([
this.uploadAttachments(message),
@ -1404,15 +1448,15 @@ export default class MessageSender {
identifier: string,
messageText: string | undefined,
attachments: Array<AttachmentType> | undefined,
quote: any,
quote: unknown,
preview: Array<PreviewType> | undefined,
sticker: any,
reaction: any,
sticker: unknown,
reaction: unknown,
timestamp: number,
expireTimer: number | undefined,
profileKey?: ArrayBuffer,
options?: SendOptionsType
) {
): Promise<CallbackResultType> {
return this.sendMessage(
{
recipients: [identifier],
@ -1435,7 +1479,9 @@ export default class MessageSender {
e164: string,
timestamp: number,
options?: SendOptionsType
) {
): Promise<
CallbackResultType | void | Array<CallbackResultType | void | Array<void>>
> {
window.log.info('resetting secure session');
const silent = false;
const proto = new window.textsecure.protobuf.DataMessage();
@ -1592,15 +1638,18 @@ export default class MessageSender {
async getGroup(options: GroupCredentialsType): Promise<GroupClass> {
return this.server.getGroup(options);
}
async getGroupLog(
startVersion: number,
options: GroupCredentialsType
): Promise<GroupLogResponseType> {
return this.server.getGroupLog(startVersion, options);
}
async getGroupAvatar(key: string): Promise<ArrayBuffer> {
return this.server.getGroupAvatar(key);
}
async modifyGroup(
changes: GroupChangeClass.Actions,
options: GroupCredentialsType
@ -1654,7 +1703,7 @@ export default class MessageSender {
timestamp: number,
profileKey?: ArrayBuffer,
options?: SendOptionsType
) {
): Promise<CallbackResultType> {
return this.sendMessage(
{
recipients: [identifier],
@ -1667,7 +1716,11 @@ export default class MessageSender {
options
);
}
async makeProxiedRequest(url: string, options?: ProxiedRequestOptionsType) {
async makeProxiedRequest(
url: string,
options?: ProxiedRequestOptionsType
): Promise<any> {
return this.server.makeProxiedRequest(url, options);
}

View File

@ -1,5 +1,4 @@
// tslint:disable no-default-export
/* eslint-disable @typescript-eslint/no-explicit-any */
import utils from './Helpers';
// Default implmentation working with localStorage
@ -33,15 +32,15 @@ export interface StorageInterface {
const Storage = {
impl: localStorageImpl as StorageInterface,
put(key: string, value: any) {
put(key: string, value: unknown): Promise<void> | void {
return Storage.impl.put(key, value);
},
get(key: string, defaultValue: any) {
get(key: string, defaultValue: unknown): Promise<unknown> {
return Storage.impl.get(key, defaultValue);
},
remove(key: string) {
remove(key: string): Promise<void> | void {
return Storage.impl.remove(key);
},
};

View File

@ -1,4 +1,5 @@
// tslint:disable binary-expression-operand-order no-bitwise no-default-export
/* eslint-disable no-bitwise */
/* eslint-disable no-nested-ternary */
const StringView = {
/*
@ -9,7 +10,7 @@ const StringView = {
*/
// prettier-ignore
b64ToUint6(nChr: number) {
b64ToUint6(nChr: number): number {
return nChr > 64 && nChr < 91
? nChr - 65
: nChr > 96 && nChr < 123
@ -23,7 +24,7 @@ const StringView = {
: 0;
},
base64ToBytes(sBase64: string, nBlocksSize: number) {
base64ToBytes(sBase64: string, nBlocksSize: number): ArrayBuffer {
const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, '');
const nInLen = sB64Enc.length;
const nOutLen = nBlocksSize
@ -55,7 +56,7 @@ const StringView = {
},
// prettier-ignore
uint6ToB64(nUint6: number) {
uint6ToB64(nUint6: number): number {
return nUint6 < 26
? nUint6 + 65
: nUint6 < 52
@ -69,7 +70,7 @@ const StringView = {
: 65;
},
bytesToBase64(aBytes: Uint8Array) {
bytesToBase64(aBytes: Uint8Array): string {
let nMod3;
let sB64Enc = '';
let nUint24 = 0;

View File

@ -1,13 +1,23 @@
/* eslint-disable more/no-then */
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable max-classes-per-file */
import EventTarget from './EventTarget';
import MessageReceiver from './MessageReceiver';
import MessageSender from './SendMessage';
class SyncRequestInner extends EventTarget {
receiver: MessageReceiver;
contactSync?: boolean;
groupSync?: boolean;
timeout: any;
oncontact: Function;
ongroup: Function;
constructor(sender: MessageSender, receiver: MessageReceiver) {
@ -38,7 +48,6 @@ class SyncRequestInner extends EventTarget {
);
window.log.info('SyncRequest created. Sending config sync request...');
// tslint:disable
wrap(sender.sendRequestConfigurationSyncMessage(sendOptions));
window.log.info('SyncRequest now sending block sync request...');
@ -58,20 +67,24 @@ class SyncRequestInner extends EventTarget {
});
this.timeout = setTimeout(this.onTimeout.bind(this), 60000);
}
onContactSyncComplete() {
this.contactSync = true;
this.update();
}
onGroupSyncComplete() {
this.groupSync = true;
this.update();
}
update() {
if (this.contactSync && this.groupSync) {
this.dispatchEvent(new Event('success'));
this.cleanup();
}
}
onTimeout() {
if (this.contactSync || this.groupSync) {
this.dispatchEvent(new Event('success'));
@ -80,6 +93,7 @@ class SyncRequestInner extends EventTarget {
}
this.cleanup();
}
cleanup() {
clearTimeout(this.timeout);
this.receiver.removeEventListener('contactsync', this.oncontact);
@ -96,5 +110,6 @@ export default class SyncRequest {
}
addEventListener: (name: string, handler: Function) => void;
removeEventListener: (name: string, handler: Function) => void;
}

View File

@ -22,7 +22,7 @@ export default function createTaskWithTimeout<T>(
window.log.error(message);
reject(new Error(message));
return;
return undefined;
}
return null;
@ -47,15 +47,11 @@ export default function createTaskWithTimeout<T>(
clearTimer();
complete = true;
resolve(result);
return;
};
const failure = (error: Error) => {
clearTimer();
complete = true;
reject(error);
return;
};
let promise;
@ -70,9 +66,10 @@ export default function createTaskWithTimeout<T>(
complete = true;
resolve(promise);
return;
return undefined;
}
// eslint-disable-next-line more/no-then
return promise.then(success, failure);
});
}

View File

@ -1,4 +1,11 @@
import { w3cwebsocket as WebSocket } from 'websocket';
/* eslint-disable no-param-reassign */
/* eslint-disable more/no-then */
/* eslint-disable no-bitwise */
/* eslint-disable guard-for-in */
/* eslint-disable no-restricted-syntax */
/* eslint-disable no-nested-ternary */
/* eslint-disable @typescript-eslint/no-explicit-any */
import fetch, { Response } from 'node-fetch';
import ProxyAgent from 'proxy-agent';
import { Agent } from 'https';
@ -11,12 +18,14 @@ import {
zipObject,
} from 'lodash';
import { createVerify } from 'crypto';
import { Long } from '../window.d';
import { pki } from 'node-forge';
import is from '@sindresorhus/is';
import PQueue from 'p-queue';
import { v4 as getGuid } from 'uuid';
import { Long } from '../window.d';
import { getUserAgent } from '../util/getUserAgent';
import { isPackIdValid, redactPackId } from '../../js/modules/stickers';
import MessageSender from './SendMessage';
import {
arrayBufferToBase64,
base64ToArrayBuffer,
@ -30,11 +39,6 @@ import {
getRandomValue,
splitUuids,
} from '../Crypto';
import { getUserAgent } from '../util/getUserAgent';
import PQueue from 'p-queue';
import { v4 as getGuid } from 'uuid';
import {
AvatarUploadAttributesClass,
GroupChangeClass,
@ -44,6 +48,9 @@ import {
StorageServiceCredentials,
} from '../textsecure.d';
import { WebSocket } from './WebSocket';
import MessageSender from './SendMessage';
type SgxConstantsType = {
SGX_FLAGS_INITTED: Long;
SGX_FLAGS_DEBUG: Long;
@ -81,8 +88,6 @@ function getSgxConstants() {
return sgxConstantCache;
}
// tslint:disable no-bitwise
function _btoa(str: any) {
let buffer;
@ -142,24 +147,27 @@ function _getStringable(thing: any) {
function _ensureStringed(thing: any): any {
if (_getStringable(thing)) {
return _getString(thing);
} else if (thing instanceof Array) {
}
if (thing instanceof Array) {
const res = [];
for (let i = 0; i < thing.length; i += 1) {
res[i] = _ensureStringed(thing[i]);
}
return res;
} else if (thing === Object(thing)) {
}
if (thing === Object(thing)) {
const res: any = {};
// tslint:disable-next-line forin no-for-in
for (const key in thing) {
res[key] = _ensureStringed(thing[key]);
}
return res;
} else if (thing === null) {
}
if (thing === null) {
return null;
} else if (thing === undefined) {
}
if (thing === undefined) {
return undefined;
}
throw new Error(`unsure of how to jsonify object of type ${typeof thing}`);
@ -185,7 +193,6 @@ function _base64ToBytes(sBase64: string, nBlocksSize?: number) {
for (let nInIdx = 0; nInIdx < nInLen; nInIdx += 1) {
nMod4 = nInIdx & 3;
// tslint:disable-next-line binary-expression-operand-order
nUint24 |= _b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << (18 - 6 * nMod4);
if (nMod4 === 3 || nInLen - nInIdx === 1) {
for (
@ -219,7 +226,6 @@ function _createRedactor(
function _validateResponse(response: any, schema: any) {
try {
// tslint:disable-next-line forin no-for-in
for (const i in schema) {
switch (schema[i]) {
case 'object':
@ -327,12 +333,10 @@ type ArrayBufferWithDetailsType = {
response: Response;
};
// tslint:disable-next-line max-func-body-length
async function _promiseAjax(
providedUrl: string | null,
options: PromiseAjaxOptionsType
): Promise<any> {
// tslint:disable-next-line max-func-body-length
return new Promise((resolve, reject) => {
const url = providedUrl || `${options.host}/${options.path}`;
@ -376,8 +380,6 @@ async function _promiseAjax(
} as HeaderListType,
redirect: options.redirect,
agent,
// We patched node-fetch to add the ca param; its type definitions don't have it
// @ts-ignore
ca: options.certificateAuthority,
timeout,
};
@ -414,7 +416,6 @@ async function _promiseAjax(
}
fetch(url, fetchOptions)
// tslint:disable-next-line max-func-body-length
.then(async response => {
// Build expired!
if (response.status === 499) {
@ -439,16 +440,13 @@ async function _promiseAjax(
resultPromise = response.textConverted();
}
// tslint:disable-next-line max-func-body-length
return resultPromise.then(result => {
if (
options.responseType === 'arraybuffer' ||
options.responseType === 'arraybufferwithdetails'
) {
// tslint:disable-next-line no-parameter-reassignment
result = result.buffer.slice(
result.byteOffset,
// tslint:disable-next-line: restrict-plus-operands
result.byteOffset + result.byteLength
);
}
@ -539,8 +537,6 @@ async function _promiseAjax(
options.stack
)
);
return;
});
})
.catch(e => {
@ -757,7 +753,7 @@ export type WebAPIType = {
) => Promise<any>;
getProvisioningSocket: () => WebSocket;
getSenderCertificate: (withUuid?: boolean) => Promise<any>;
getSticker: (packId: string, stickerId: string) => Promise<any>;
getSticker: (packId: string, stickerId: number) => Promise<any>;
getStickerPackManifest: (packId: string) => Promise<StickerPackManifestType>;
getStorageCredentials: MessageSender['getStorageCredentials'];
getStorageManifest: MessageSender['getStorageManifest'];
@ -852,7 +848,6 @@ export type ProxiedRequestOptionsType = {
};
// We first set up the data that won't change during this session of the app
// tslint:disable-next-line max-func-body-length
export function initialize({
url,
storageUrl,
@ -911,7 +906,6 @@ export function initialize({
// Then we connect to the server with user-specific information. This is the only API
// exposed to the browser context, ensuring that it can't connect to arbitrary
// locations.
// tslint:disable-next-line max-func-body-length
function connect({
username: initialUsername,
password: initialPassword,
@ -1263,7 +1257,7 @@ export function initialize({
'gv2-3': true,
},
fetchesMessages: true,
name: deviceName ? deviceName : undefined,
name: deviceName || undefined,
registrationId,
supportsSms: false,
unidentifiedAccessKey: accessKey
@ -1551,7 +1545,7 @@ export function initialize({
);
}
async function getSticker(packId: string, stickerId: string) {
async function getSticker(packId: string, stickerId: number) {
if (!isPackIdValid(packId)) {
throw new Error('getSticker: pack ID was invalid');
}
@ -2031,7 +2025,6 @@ export function initialize({
window.log.info('opening message socket', url);
const fixedScheme = url
.replace('https://', 'wss://')
// tslint:disable-next-line no-http-string
.replace('http://', 'ws://');
const login = encodeURIComponent(username);
const pass = encodeURIComponent(password);
@ -2047,7 +2040,6 @@ export function initialize({
window.log.info('opening provisioning socket', url);
const fixedScheme = url
.replace('https://', 'wss://')
// tslint:disable-next-line no-http-string
.replace('http://', 'ws://');
const clientVersion = encodeURIComponent(version);
@ -2247,7 +2239,6 @@ export function initialize({
}
}
// tslint:disable-next-line max-func-body-length
async function putRemoteAttestation(auth: {
username: string;
password: string;

View File

@ -0,0 +1,17 @@
import { w3cwebsocket } from 'websocket';
type ModifiedEventSource = Omit<EventSource, 'onerror'>;
declare class ModifiedWebSocket extends w3cwebsocket
implements ModifiedEventSource {
withCredentials: boolean;
addEventListener: EventSource['addEventListener'];
removeEventListener: EventSource['removeEventListener'];
dispatchEvent: EventSource['dispatchEvent'];
}
export type WebSocket = ModifiedWebSocket;
export const WebSocket = w3cwebsocket as typeof ModifiedWebSocket;

View File

@ -1,3 +1,7 @@
/* eslint-disable no-param-reassign */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable max-classes-per-file */
/*
* WebSocket-Resources
*
@ -20,21 +24,27 @@
*
*/
// tslint:disable max-classes-per-file no-default-export no-unnecessary-class
import { w3cwebsocket as WebSocket } from 'websocket';
import { ByteBufferClass } from '../window.d';
import EventTarget from './EventTarget';
import { WebSocket } from './WebSocket';
class Request {
verb: string;
path: string;
headers: Array<string>;
body: ByteBufferClass | null;
success: Function;
error: Function;
id: number;
response?: any;
constructor(options: any) {
@ -60,14 +70,18 @@ class Request {
export class IncomingWebSocketRequest {
verb: string;
path: string;
body: ByteBufferClass | null;
headers: Array<string>;
respond: (status: number, message: string) => void;
constructor(options: any) {
constructor(options: unknown) {
const request = new Request(options);
const { socket } = options;
const { socket } = options as { socket: WebSocket };
this.verb = request.verb;
this.path = request.path;
@ -113,8 +127,11 @@ class OutgoingWebSocketRequest {
export default class WebSocketResource extends EventTarget {
closed?: boolean;
close: (code?: number, reason?: string) => void;
sendRequest: (options: any) => OutgoingWebSocketRequest;
keepalive?: KeepAlive;
// tslint:disable-next-line max-func-body-length
@ -198,21 +215,14 @@ export default class WebSocketResource extends EventTarget {
});
const resetKeepAliveTimer = this.keepalive.reset.bind(this.keepalive);
// websocket type definitions don't include an addEventListener, but it's there. And
// We can't use declaration merging on classes:
// https://www.typescriptlang.org/docs/handbook/declaration-merging.html#disallowed-merges)
// @ts-ignore
socket.addEventListener('open', resetKeepAliveTimer);
// @ts-ignore
socket.addEventListener('message', resetKeepAliveTimer);
// @ts-ignore
socket.addEventListener(
'close',
this.keepalive.stop.bind(this.keepalive)
);
}
// @ts-ignore
socket.addEventListener('close', () => {
this.closed = true;
});
@ -228,8 +238,9 @@ export default class WebSocketResource extends EventTarget {
}
socket.close(code, reason);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
socket.onmessage = null;
socket.onmessage = undefined;
// On linux the socket can wait a long time to emit its close event if we've
// lost the internet connection. On the order of minutes. This speeds that
@ -257,9 +268,13 @@ type KeepAliveOptionsType = {
class KeepAlive {
keepAliveTimer: any;
disconnectTimer: any;
path: string;
disconnect: boolean;
wsr: WebSocketResource;
constructor(

View File

@ -12848,9 +12848,8 @@
"path": "ts/components/CallScreen.js",
"line": " this.localVideoRef = react_1.default.createRef();",
"lineNumber": 98,
"reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted",
"updated": "2020-09-14T23:03:44.863Z",
"reasonDetail": "<optional>"
"reasonCategory": "usageTrusted",
"updated": "2020-09-14T23:03:44.863Z"
},
{
"rule": "React-createRef",
@ -12935,8 +12934,8 @@
"line": " this.listRef = react_1.default.createRef();",
"lineNumber": 16,
"reasonCategory": "usageTrusted",
"updated": "2019-11-05T01:14:21.081Z",
"reasonDetail": "Used for focus management"
"updated": "2020-09-11T17:24:56.124Z",
"reasonDetail": "Used for scroll calculations"
},
{
"rule": "React-createRef",
@ -13015,8 +13014,8 @@
"line": " this.containerRef = react_1.default.createRef();",
"lineNumber": 25,
"reasonCategory": "usageTrusted",
"updated": "2020-09-11T17:24:56.124Z",
"reasonDetail": "Used for scroll calculations"
"updated": "2019-08-09T00:44:31.008Z",
"reasonDetail": "SearchResults needs to interact with its child List directly"
},
{
"rule": "React-createRef",
@ -13086,7 +13085,7 @@
"line": " public audioRef: React.RefObject<HTMLAudioElement> = React.createRef();",
"lineNumber": 213,
"reasonCategory": "usageTrusted",
"updated": "2020-09-14T23:03:44.863Z"
"updated": "2020-09-08T20:19:01.913Z"
},
{
"rule": "React-createRef",
@ -13094,7 +13093,7 @@
"line": " public focusRef: React.RefObject<HTMLDivElement> = React.createRef();",
"lineNumber": 215,
"reasonCategory": "usageTrusted",
"updated": "2020-09-14T23:03:44.863Z"
"updated": "2020-09-08T20:19:01.913Z"
},
{
"rule": "React-createRef",
@ -13187,7 +13186,7 @@
"rule": "jQuery-append(",
"path": "ts/textsecure/ContactsParser.js",
"line": " this.buffer.append(arrayBuffer);",
"lineNumber": 7,
"lineNumber": 9,
"reasonCategory": "falseMatch",
"updated": "2020-04-05T23:45:16.746Z"
},
@ -13195,7 +13194,7 @@
"rule": "jQuery-append(",
"path": "ts/textsecure/ContactsParser.ts",
"line": " this.buffer.append(arrayBuffer);",
"lineNumber": 26,
"lineNumber": 30,
"reasonCategory": "falseMatch",
"updated": "2020-04-05T23:45:16.746Z"
},
@ -13203,7 +13202,7 @@
"rule": "jQuery-wrap(",
"path": "ts/textsecure/Crypto.js",
"line": " const data = window.dcodeIO.ByteBuffer.wrap(encryptedProfileName, 'base64').toArrayBuffer();",
"lineNumber": 157,
"lineNumber": 155,
"reasonCategory": "falseMatch",
"updated": "2020-04-05T23:45:16.746Z"
},
@ -13211,7 +13210,7 @@
"rule": "jQuery-wrap(",
"path": "ts/textsecure/Crypto.js",
"line": " given: window.dcodeIO.ByteBuffer.wrap(padded)",
"lineNumber": 176,
"lineNumber": 174,
"reasonCategory": "falseMatch",
"updated": "2020-04-05T23:45:16.746Z"
},
@ -13219,7 +13218,7 @@
"rule": "jQuery-wrap(",
"path": "ts/textsecure/Crypto.js",
"line": " ? window.dcodeIO.ByteBuffer.wrap(padded)",
"lineNumber": 180,
"lineNumber": 178,
"reasonCategory": "falseMatch",
"updated": "2020-04-05T23:45:16.746Z"
},
@ -13227,7 +13226,7 @@
"rule": "jQuery-wrap(",
"path": "ts/textsecure/Crypto.ts",
"line": " const data = window.dcodeIO.ByteBuffer.wrap(",
"lineNumber": 223,
"lineNumber": 345,
"reasonCategory": "falseMatch",
"updated": "2020-04-05T23:45:16.746Z"
},
@ -13235,7 +13234,7 @@
"rule": "jQuery-wrap(",
"path": "ts/textsecure/Crypto.ts",
"line": " given: window.dcodeIO.ByteBuffer.wrap(padded)",
"lineNumber": 252,
"lineNumber": 374,
"reasonCategory": "falseMatch",
"updated": "2020-04-05T23:45:16.746Z"
},
@ -13243,7 +13242,7 @@
"rule": "jQuery-wrap(",
"path": "ts/textsecure/Crypto.ts",
"line": " ? window.dcodeIO.ByteBuffer.wrap(padded)",
"lineNumber": 256,
"lineNumber": 378,
"reasonCategory": "falseMatch",
"updated": "2020-04-05T23:45:16.746Z"
},
@ -13251,7 +13250,7 @@
"rule": "jQuery-wrap(",
"path": "ts/textsecure/SyncRequest.js",
"line": " wrap(sender.sendRequestConfigurationSyncMessage(sendOptions));",
"lineNumber": 27,
"lineNumber": 30,
"reasonCategory": "falseMatch",
"updated": "2020-04-05T23:45:16.746Z"
},
@ -13259,7 +13258,7 @@
"rule": "jQuery-wrap(",
"path": "ts/textsecure/SyncRequest.js",
"line": " wrap(sender.sendRequestBlockSyncMessage(sendOptions));",
"lineNumber": 29,
"lineNumber": 32,
"reasonCategory": "falseMatch",
"updated": "2020-04-05T23:45:16.746Z"
},
@ -13267,7 +13266,7 @@
"rule": "jQuery-wrap(",
"path": "ts/textsecure/SyncRequest.js",
"line": " wrap(sender.sendRequestContactSyncMessage(sendOptions))",
"lineNumber": 31,
"lineNumber": 34,
"reasonCategory": "falseMatch",
"updated": "2020-04-05T23:45:16.746Z"
},
@ -13275,7 +13274,7 @@
"rule": "jQuery-wrap(",
"path": "ts/textsecure/SyncRequest.js",
"line": " return wrap(sender.sendRequestGroupSyncMessage(sendOptions));",
"lineNumber": 34,
"lineNumber": 37,
"reasonCategory": "falseMatch",
"updated": "2020-04-05T23:45:16.746Z"
},
@ -13283,7 +13282,7 @@
"rule": "jQuery-wrap(",
"path": "ts/textsecure/SyncRequest.ts",
"line": " wrap(sender.sendRequestConfigurationSyncMessage(sendOptions));",
"lineNumber": 42,
"lineNumber": 51,
"reasonCategory": "falseMatch",
"updated": "2020-04-05T23:45:16.746Z"
},
@ -13291,7 +13290,7 @@
"rule": "jQuery-wrap(",
"path": "ts/textsecure/SyncRequest.ts",
"line": " wrap(sender.sendRequestBlockSyncMessage(sendOptions));",
"lineNumber": 45,
"lineNumber": 54,
"reasonCategory": "falseMatch",
"updated": "2020-04-05T23:45:16.746Z"
},
@ -13299,7 +13298,7 @@
"rule": "jQuery-wrap(",
"path": "ts/textsecure/SyncRequest.ts",
"line": " wrap(sender.sendRequestContactSyncMessage(sendOptions))",
"lineNumber": 48,
"lineNumber": 57,
"reasonCategory": "falseMatch",
"updated": "2020-04-05T23:45:16.746Z"
},
@ -13307,7 +13306,7 @@
"rule": "jQuery-wrap(",
"path": "ts/textsecure/SyncRequest.ts",
"line": " return wrap(sender.sendRequestGroupSyncMessage(sendOptions));",
"lineNumber": 51,
"lineNumber": 60,
"reasonCategory": "falseMatch",
"updated": "2020-04-05T23:45:16.746Z"
},
@ -13315,7 +13314,7 @@
"rule": "jQuery-wrap(",
"path": "ts/textsecure/WebAPI.js",
"line": " const byteBuffer = window.dcodeIO.ByteBuffer.wrap(quote, 'binary', window.dcodeIO.ByteBuffer.LITTLE_ENDIAN);",
"lineNumber": 1223,
"lineNumber": 1212,
"reasonCategory": "falseMatch",
"updated": "2020-09-08T23:07:22.682Z"
},
@ -13323,8 +13322,8 @@
"rule": "jQuery-wrap(",
"path": "ts/textsecure/WebAPI.ts",
"line": " const byteBuffer = window.dcodeIO.ByteBuffer.wrap(",
"lineNumber": 2076,
"lineNumber": 2068,
"reasonCategory": "falseMatch",
"updated": "2020-09-08T23:07:22.682Z"
}
]
]

View File

@ -192,9 +192,11 @@
"ts/scripts/**",
"ts/services/**",
"ts/shims/**",
"ts/sql/**",
"ts/state/**",
"ts/storybook/**",
"ts/test/**",
"ts/textsecure/**",
"ts/types/**",
"ts/updater/**",
"ts/util/**",