Migrate base ts to eslint

This commit is contained in:
Chris Svenningsen 2020-09-11 12:37:01 -07:00 committed by Josh Perez
parent ad555ec8a9
commit 50378ed9bc
10 changed files with 475 additions and 409 deletions

View file

@ -30,7 +30,6 @@ webpack.config.ts
# Temporarily ignored during TSLint transition
# JIRA: DESKTOP-304
ts/*.ts
ts/components/*.ts
ts/components/*.tsx
ts/components/conversation/**

View file

@ -1,5 +1,3 @@
const MAX_MESSAGE_BODY_LENGTH = 64 * 1024;
import { debounce, reduce, uniq, without } from 'lodash';
import dataInterface from './sql/Client';
import {
@ -9,6 +7,8 @@ import {
} from './model-types.d';
import { SendOptionsType } from './textsecure/SendMessage';
const MAX_MESSAGE_BODY_LENGTH = 64 * 1024;
const {
getAllConversations,
getAllGroupsInvolvingId,
@ -22,7 +22,7 @@ const {
// We have to run this in background.js, after all backbone models and collections on
// Whisper.* have been created. Once those are in typescript we can use more reasonable
// require statements for referencing these things, giving us more flexibility here.
export function start() {
export function start(): void {
const conversations = new window.Whisper.ConversationCollection();
// This class is entirely designed to keep the app title, badge and tray icon updated.
@ -70,7 +70,9 @@ export function start() {
export class ConversationController {
_initialFetchComplete: boolean | undefined;
_initialPromise: Promise<void> = Promise.resolve();
_conversations: ConversationModelCollectionType;
constructor(conversations?: ConversationModelCollectionType) {
@ -91,14 +93,18 @@ export class ConversationController {
// This function takes null just fine. Backbone typings are too restrictive.
return this._conversations.get(id as string);
}
dangerouslyCreateAndAdd(attributes: Partial<ConversationModelType>) {
dangerouslyCreateAndAdd(
attributes: Partial<ConversationModelType>
): ConversationModelType {
return this._conversations.add(attributes);
}
getOrCreate(
identifier: string,
type: ConversationTypeType,
additionalInitialProps = {}
) {
): ConversationModelType {
if (typeof identifier !== 'string') {
throw new TypeError("'id' must be a string");
}
@ -186,24 +192,24 @@ export class ConversationController {
return conversation;
}
async getOrCreateAndWait(
id: string,
type: ConversationTypeType,
additionalInitialProps = {}
) {
return this._initialPromise.then(async () => {
const conversation = this.getOrCreate(id, type, additionalInitialProps);
): Promise<ConversationModelType> {
await this._initialPromise;
const conversation = this.getOrCreate(id, type, additionalInitialProps);
if (conversation) {
return conversation.initialPromise.then(() => conversation);
}
if (conversation) {
await conversation.initialPromise;
return conversation;
}
return Promise.reject(
new Error('getOrCreateAndWait: did not get conversation')
);
});
throw new Error('getOrCreateAndWait: did not get conversation');
}
getConversationId(address: string) {
getConversationId(address: string): string | null {
if (!address) {
return null;
}
@ -217,11 +223,13 @@ export class ConversationController {
return null;
}
getOurConversationId(): string | undefined {
const e164 = window.textsecure.storage.user.getNumber();
const uuid = window.textsecure.storage.user.getUuid();
return this.ensureContactIds({ e164, uuid, highTrust: true });
}
/**
* Given a UUID and/or an E164, resolves to a string representing the local
* database id of the given contact. In high trust mode, it may create new contacts,
@ -272,7 +280,8 @@ export class ConversationController {
return newConvo.get('id');
// 2. Handle match on only E164
} else if (convoE164 && !convoUuid) {
}
if (convoE164 && !convoUuid) {
const haveUuid = Boolean(normalizedUuid);
window.log.info(
`ensureContactIds: e164-only match found (have UUID: ${haveUuid})`
@ -315,7 +324,8 @@ export class ConversationController {
return newConvo.get('id');
// 3. Handle match on only UUID
} else if (!convoE164 && convoUuid) {
}
if (!convoE164 && convoUuid) {
window.log.info(
`ensureContactIds: UUID-only match found (have e164: ${Boolean(e164)})`
);
@ -363,6 +373,8 @@ export class ConversationController {
// Conflict: If e164 match has no UUID, we merge. We prefer the UUID match.
// Note: no await here, we want to keep this function synchronous
convoUuid.updateE164(e164);
// `then` is used to trigger async updates, not affecting return value
// eslint-disable-next-line more/no-then
this.combineContacts(convoUuid, convoE164)
.then(() => {
// If the old conversation was currently displayed, we load the new one
@ -381,7 +393,8 @@ export class ConversationController {
return convoUuid.get('id');
}
async checkForConflicts() {
async checkForConflicts(): Promise<void> {
window.log.info('checkForConflicts: starting...');
const byUuid = Object.create(null);
const byE164 = Object.create(null);
@ -465,10 +478,11 @@ export class ConversationController {
window.log.info('checkForConflicts: complete!');
}
async combineContacts(
current: ConversationModelType,
obsolete: ConversationModelType
) {
): Promise<void> {
const obsoleteId = obsolete.get('id');
const currentId = current.get('id');
window.log.warn('combineContacts: Combining two conversations', {
@ -541,6 +555,7 @@ export class ConversationController {
current: currentId,
});
}
/**
* Given a groupId and optional additional initialization properties,
* ensures the existence of a group conversation and returns a string
@ -549,16 +564,17 @@ export class ConversationController {
ensureGroup(groupId: string, additionalInitProps = {}): string {
return this.getOrCreate(groupId, 'group', additionalInitProps).get('id');
}
/**
* Given certain metadata about a message (an identifier of who wrote the
* message and the sent_at timestamp of the message) returns the
* conversation the message belongs to OR null if a conversation isn't
* found.
*/
async getConversationForTargetMessage(
static async getConversationForTargetMessage(
targetFromId: string,
targetTimestamp: number
) {
): Promise<boolean | ConversationModelType | null | undefined> {
const messages = await getMessagesBySentAt(targetTimestamp, {
MessageCollection: window.Whisper.MessageCollection,
});
@ -579,11 +595,12 @@ export class ConversationController {
return null;
}
prepareForSend(
prepareForSend<T>(
id: string,
options?: any
options?: unknown
): {
wrap: (promise: Promise<any>) => Promise<void>;
wrap: (promise: Promise<T>) => Promise<T>;
sendOptions: SendOptionsType | undefined;
} {
// id is any valid conversation identifier
@ -593,10 +610,11 @@ export class ConversationController {
: undefined;
const wrap = conversation
? conversation.wrapSend.bind(conversation)
: async (promise: Promise<any>) => promise;
: async (promise: Promise<T>) => promise;
return { wrap, sendOptions };
}
async getAllGroupsInvolvingId(
conversationId: string
): Promise<Array<ConversationModelType>> {
@ -605,18 +623,22 @@ export class ConversationController {
});
return groups.map(group => this._conversations.add(group));
}
async loadPromise() {
async loadPromise(): Promise<void> {
return this._initialPromise;
}
reset() {
reset(): void {
this._initialPromise = Promise.resolve();
this._initialFetchComplete = false;
this._conversations.reset([]);
}
isFetchComplete() {
isFetchComplete(): boolean | undefined {
return this._initialFetchComplete;
}
async load() {
async load(): Promise<void> {
window.log.info('ConversationController: starting initial fetch');
if (this._conversations.length) {

View file

@ -1,55 +1,47 @@
import pProps from 'p-props';
// Yep, we're doing some bitwise stuff in an encryption-related file
// tslint:disable no-bitwise
// We want some extra variables to make the decrption algorithm easier to understand
// tslint:disable no-unnecessary-local-variable
// Seems that tslint doesn't understand that crypto.subtle.importKey does return a Promise
// tslint:disable await-promise
export function typedArrayToArrayBuffer(typedArray: Uint8Array): ArrayBuffer {
const { buffer, byteOffset, byteLength } = typedArray;
// tslint:disable-next-line no-unnecessary-type-assertion
return buffer.slice(byteOffset, byteLength + byteOffset) as typeof typedArray;
}
export function arrayBufferToBase64(arrayBuffer: ArrayBuffer) {
export function arrayBufferToBase64(arrayBuffer: ArrayBuffer): string {
return window.dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('base64');
}
export function arrayBufferToHex(arrayBuffer: ArrayBuffer) {
export function arrayBufferToHex(arrayBuffer: ArrayBuffer): string {
return window.dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('hex');
}
export function base64ToArrayBuffer(base64string: string) {
export function base64ToArrayBuffer(base64string: string): ArrayBuffer {
return window.dcodeIO.ByteBuffer.wrap(base64string, 'base64').toArrayBuffer();
}
export function hexToArrayBuffer(hexString: string) {
export function hexToArrayBuffer(hexString: string): ArrayBuffer {
return window.dcodeIO.ByteBuffer.wrap(hexString, 'hex').toArrayBuffer();
}
export function fromEncodedBinaryToArrayBuffer(key: string) {
export function fromEncodedBinaryToArrayBuffer(key: string): ArrayBuffer {
return window.dcodeIO.ByteBuffer.wrap(key, 'binary').toArrayBuffer();
}
export function bytesFromString(string: string) {
export function bytesFromString(string: string): ArrayBuffer {
return window.dcodeIO.ByteBuffer.wrap(string, 'utf8').toArrayBuffer();
}
export function stringFromBytes(buffer: ArrayBuffer) {
export function stringFromBytes(buffer: ArrayBuffer): string {
return window.dcodeIO.ByteBuffer.wrap(buffer).toString('utf8');
}
export function hexFromBytes(buffer: ArrayBuffer) {
export function hexFromBytes(buffer: ArrayBuffer): string {
return window.dcodeIO.ByteBuffer.wrap(buffer).toString('hex');
}
export function bytesFromHexString(string: string) {
export function bytesFromHexString(string: string): ArrayBuffer {
return window.dcodeIO.ByteBuffer.wrap(string, 'hex').toArrayBuffer();
}
export async function deriveStickerPackKey(packKey: ArrayBuffer) {
export async function deriveStickerPackKey(
packKey: ArrayBuffer
): Promise<ArrayBuffer> {
const salt = getZeroes(32);
const info = bytesFromString('Sticker Pack');
@ -72,7 +64,7 @@ export async function computeHash(data: ArrayBuffer): Promise<string> {
export async function encryptDeviceName(
deviceName: string,
identityPublic: ArrayBuffer
) {
): Promise<Record<string, ArrayBuffer>> {
const plaintext = bytesFromString(deviceName);
const ephemeralKeyPair = await window.libsignal.KeyHelper.generateIdentityKeyPair();
const masterSecret = await window.libsignal.Curve.async.calculateAgreement(
@ -107,7 +99,7 @@ export async function decryptDeviceName(
ciphertext: ArrayBuffer;
},
identityPrivate: ArrayBuffer
) {
): Promise<string> {
const masterSecret = await window.libsignal.Curve.async.calculateAgreement(
ephemeralPublic,
identityPrivate
@ -130,7 +122,7 @@ export async function decryptDeviceName(
}
// Path structure: 'fa/facdf99c22945b1c9393345599a276f4b36ad7ccdc8c2467f5441b742c2d11fa'
export function getAttachmentLabel(path: string) {
export function getAttachmentLabel(path: string): ArrayBuffer {
const filename = path.slice(3);
return base64ToArrayBuffer(filename);
@ -141,7 +133,7 @@ export async function encryptAttachment(
staticPublicKey: ArrayBuffer,
path: string,
plaintext: ArrayBuffer
) {
): Promise<ArrayBuffer> {
const uniqueId = getAttachmentLabel(path);
return encryptFile(staticPublicKey, uniqueId, plaintext);
@ -151,7 +143,7 @@ export async function decryptAttachment(
staticPrivateKey: ArrayBuffer,
path: string,
data: ArrayBuffer
) {
): Promise<ArrayBuffer> {
const uniqueId = getAttachmentLabel(path);
return decryptFile(staticPrivateKey, uniqueId, data);
@ -161,7 +153,7 @@ export async function encryptFile(
staticPublicKey: ArrayBuffer,
uniqueId: ArrayBuffer,
plaintext: ArrayBuffer
) {
): Promise<ArrayBuffer> {
const ephemeralKeyPair = await window.libsignal.KeyHelper.generateIdentityKeyPair();
const agreement = await window.libsignal.Curve.async.calculateAgreement(
staticPublicKey,
@ -178,7 +170,7 @@ export async function decryptFile(
staticPrivateKey: ArrayBuffer,
uniqueId: ArrayBuffer,
data: ArrayBuffer
) {
): Promise<ArrayBuffer> {
const ephemeralPublicKey = getFirstBytes(data, PUB_KEY_LENGTH);
const ciphertext = getBytes(data, PUB_KEY_LENGTH, data.byteLength);
const agreement = await window.libsignal.Curve.async.calculateAgreement(
@ -194,18 +186,20 @@ export async function decryptFile(
export async function deriveStorageManifestKey(
storageServiceKey: ArrayBuffer,
version: number
) {
): Promise<ArrayBuffer> {
return hmacSha256(storageServiceKey, bytesFromString(`Manifest_${version}`));
}
export async function deriveStorageItemKey(
storageServiceKey: ArrayBuffer,
itemID: string
) {
): Promise<ArrayBuffer> {
return hmacSha256(storageServiceKey, bytesFromString(`Item_${itemID}`));
}
export async function deriveAccessKey(profileKey: ArrayBuffer) {
export async function deriveAccessKey(
profileKey: ArrayBuffer
): Promise<ArrayBuffer> {
const iv = getZeroes(12);
const plaintext = getZeroes(16);
const accessKey = await encryptAesGcm(profileKey, iv, plaintext);
@ -213,7 +207,9 @@ export async function deriveAccessKey(profileKey: ArrayBuffer) {
return getFirstBytes(accessKey, 16);
}
export async function getAccessKeyVerifier(accessKey: ArrayBuffer) {
export async function getAccessKeyVerifier(
accessKey: ArrayBuffer
): Promise<ArrayBuffer> {
const plaintext = getZeroes(32);
return hmacSha256(accessKey, plaintext);
@ -222,7 +218,7 @@ export async function getAccessKeyVerifier(accessKey: ArrayBuffer) {
export async function verifyAccessKey(
accessKey: ArrayBuffer,
theirVerifier: ArrayBuffer
) {
): Promise<boolean> {
const ourVerifier = await getAccessKeyVerifier(accessKey);
if (constantTimeEqual(ourVerifier, theirVerifier)) {
@ -239,14 +235,14 @@ const NONCE_LENGTH = 16;
export async function encryptSymmetric(
key: ArrayBuffer,
plaintext: ArrayBuffer
) {
): Promise<ArrayBuffer> {
const iv = getZeroes(IV_LENGTH);
const nonce = getRandomBytes(NONCE_LENGTH);
const cipherKey = await hmacSha256(key, nonce);
const macKey = await hmacSha256(key, cipherKey);
const cipherText = await _encrypt_aes256_CBC_PKCSPadding(
const cipherText = await _encryptAes256CbcPkcsPadding(
cipherKey,
iv,
plaintext
@ -256,7 +252,10 @@ export async function encryptSymmetric(
return concatenateBytes(nonce, cipherText, mac);
}
export async function decryptSymmetric(key: ArrayBuffer, data: ArrayBuffer) {
export async function decryptSymmetric(
key: ArrayBuffer,
data: ArrayBuffer
): Promise<ArrayBuffer> {
const iv = getZeroes(IV_LENGTH);
const nonce = getFirstBytes(data, NONCE_LENGTH);
@ -280,10 +279,13 @@ export async function decryptSymmetric(key: ArrayBuffer, data: ArrayBuffer) {
);
}
return _decrypt_aes256_CBC_PKCSPadding(cipherKey, iv, cipherText);
return _decryptAes256CbcPkcsPadding(cipherKey, iv, cipherText);
}
export function constantTimeEqual(left: ArrayBuffer, right: ArrayBuffer) {
export function constantTimeEqual(
left: ArrayBuffer,
right: ArrayBuffer
): boolean {
if (left.byteLength !== right.byteLength) {
return false;
}
@ -301,8 +303,11 @@ export function constantTimeEqual(left: ArrayBuffer, right: ArrayBuffer) {
// Encryption
export async function hmacSha256(key: ArrayBuffer, plaintext: ArrayBuffer) {
const algorithm = {
export async function hmacSha256(
key: ArrayBuffer,
plaintext: ArrayBuffer
): Promise<ArrayBuffer> {
const algorithm: HmacImportParams = {
name: 'HMAC',
hash: 'SHA-256',
};
@ -311,7 +316,7 @@ export async function hmacSha256(key: ArrayBuffer, plaintext: ArrayBuffer) {
const cryptoKey = await window.crypto.subtle.importKey(
'raw',
key,
algorithm as any,
algorithm,
extractable,
['sign']
);
@ -319,11 +324,11 @@ export async function hmacSha256(key: ArrayBuffer, plaintext: ArrayBuffer) {
return window.crypto.subtle.sign(algorithm, cryptoKey, plaintext);
}
export async function _encrypt_aes256_CBC_PKCSPadding(
export async function _encryptAes256CbcPkcsPadding(
key: ArrayBuffer,
iv: ArrayBuffer,
plaintext: ArrayBuffer
) {
): Promise<ArrayBuffer> {
const algorithm = {
name: 'AES-CBC',
iv,
@ -333,6 +338,10 @@ export async function _encrypt_aes256_CBC_PKCSPadding(
const cryptoKey = await window.crypto.subtle.importKey(
'raw',
key,
// `algorithm` appears to be an instance of AesCbcParams,
// which is not in the param's types, so we need to pass as `any`.
// TODO: just pass the string "AES-CBC", per the docs?
// eslint-disable-next-line @typescript-eslint/no-explicit-any
algorithm as any,
extractable,
['encrypt']
@ -341,11 +350,11 @@ export async function _encrypt_aes256_CBC_PKCSPadding(
return window.crypto.subtle.encrypt(algorithm, cryptoKey, plaintext);
}
export async function _decrypt_aes256_CBC_PKCSPadding(
export async function _decryptAes256CbcPkcsPadding(
key: ArrayBuffer,
iv: ArrayBuffer,
plaintext: ArrayBuffer
) {
): Promise<ArrayBuffer> {
const algorithm = {
name: 'AES-CBC',
iv,
@ -355,6 +364,10 @@ export async function _decrypt_aes256_CBC_PKCSPadding(
const cryptoKey = await window.crypto.subtle.importKey(
'raw',
key,
// `algorithm` appears to be an instance of AesCbcParams,
// which is not in the param's types, so we need to pass as `any`.
// TODO: just pass the string "AES-CBC", per the docs?
// eslint-disable-next-line @typescript-eslint/no-explicit-any
algorithm as any,
extractable,
['decrypt']
@ -367,7 +380,7 @@ export async function encryptAesCtr(
key: ArrayBuffer,
plaintext: ArrayBuffer,
counter: ArrayBuffer
) {
): Promise<ArrayBuffer> {
const extractable = false;
const algorithm = {
name: 'AES-CTR',
@ -378,6 +391,10 @@ export async function encryptAesCtr(
const cryptoKey = await crypto.subtle.importKey(
'raw',
key,
// `algorithm` appears to be an instance of AesCtrParams,
// which is not in the param's types, so we need to pass as `any`.
// TODO: just pass the string "AES-CTR", per the docs?
// eslint-disable-next-line @typescript-eslint/no-explicit-any
algorithm as any,
extractable,
['encrypt']
@ -396,7 +413,7 @@ export async function decryptAesCtr(
key: ArrayBuffer,
ciphertext: ArrayBuffer,
counter: ArrayBuffer
) {
): Promise<ArrayBuffer> {
const extractable = false;
const algorithm = {
name: 'AES-CTR',
@ -407,6 +424,10 @@ export async function decryptAesCtr(
const cryptoKey = await crypto.subtle.importKey(
'raw',
key,
// `algorithm` appears to be an instance of AesCtrParams,
// which is not in the param's types, so we need to pass as `any`.
// TODO: just pass the string "AES-CTR", per the docs?
// eslint-disable-next-line @typescript-eslint/no-explicit-any
algorithm as any,
extractable,
['decrypt']
@ -425,7 +446,7 @@ export async function encryptAesGcm(
iv: ArrayBuffer,
plaintext: ArrayBuffer,
additionalData?: ArrayBuffer
) {
): Promise<ArrayBuffer> {
const algorithm = {
name: 'AES-GCM',
iv,
@ -437,6 +458,10 @@ export async function encryptAesGcm(
const cryptoKey = await crypto.subtle.importKey(
'raw',
key,
// `algorithm` appears to be an instance of AesGcmParams,
// which is not in the param's types, so we need to pass as `any`.
// TODO: just pass the string "AES-GCM", per the docs?
// eslint-disable-next-line @typescript-eslint/no-explicit-any
algorithm as any,
extractable,
['encrypt']
@ -450,7 +475,7 @@ export async function decryptAesGcm(
iv: ArrayBuffer,
ciphertext: ArrayBuffer,
additionalData?: ArrayBuffer
) {
): Promise<ArrayBuffer> {
const algorithm = {
name: 'AES-GCM',
iv,
@ -462,6 +487,10 @@ export async function decryptAesGcm(
const cryptoKey = await crypto.subtle.importKey(
'raw',
key,
// `algorithm` appears to be an instance of AesGcmParams,
// which is not in the param's types, so we need to pass as `any`.
// TODO: just pass the string "AES-GCM", per the docs?
// eslint-disable-next-line @typescript-eslint/no-explicit-any
algorithm as any,
extractable,
['decrypt']
@ -472,13 +501,13 @@ export async function decryptAesGcm(
// Hashing
export async function sha256(data: ArrayBuffer) {
export async function sha256(data: ArrayBuffer): Promise<ArrayBuffer> {
return crypto.subtle.digest('SHA-256', data);
}
// Utility
export function getRandomBytes(n: number) {
export function getRandomBytes(n: number): ArrayBuffer {
const bytes = new Uint8Array(n);
window.crypto.getRandomValues(bytes);
@ -496,7 +525,7 @@ export function getRandomValue(low: number, high: number): number {
return (bytes[0] % mod) + low;
}
export function getZeroes(n: number) {
export function getZeroes(n: number): ArrayBuffer {
const result = new Uint8Array(n);
const value = 0;
@ -508,6 +537,7 @@ export function getZeroes(n: number) {
}
export function highBitsToInt(byte: number): number {
// eslint-disable-next-line no-bitwise
return (byte & 0xff) >> 4;
}
@ -515,10 +545,11 @@ export function intsToByteHighAndLow(
highValue: number,
lowValue: number
): number {
// eslint-disable-next-line no-bitwise
return ((highValue << 4) | lowValue) & 0xff;
}
export function trimBytes(buffer: ArrayBuffer, length: number) {
export function trimBytes(buffer: ArrayBuffer, length: number): ArrayBuffer {
return getFirstBytes(buffer, length);
}
@ -526,14 +557,16 @@ export function getViewOfArrayBuffer(
buffer: ArrayBuffer,
start: number,
finish: number
) {
): ArrayBuffer | SharedArrayBuffer {
const source = new Uint8Array(buffer);
const result = source.slice(start, finish);
return result.buffer;
}
export function concatenateBytes(...elements: Array<ArrayBuffer | Uint8Array>) {
export function concatenateBytes(
...elements: Array<ArrayBuffer | Uint8Array>
): ArrayBuffer {
const length = elements.reduce(
(total, element) => total + element.byteLength,
0
@ -585,7 +618,7 @@ export function splitBytes(
return results;
}
export function getFirstBytes(data: ArrayBuffer, n: number) {
export function getFirstBytes(data: ArrayBuffer, n: number): ArrayBuffer {
const source = new Uint8Array(data);
return typedArrayToArrayBuffer(source.subarray(0, n));
@ -595,7 +628,7 @@ export function getBytes(
data: ArrayBuffer | Uint8Array,
start: number,
n: number
) {
): ArrayBuffer {
const source = new Uint8Array(data);
return typedArrayToArrayBuffer(source.subarray(start, start + n));
@ -614,7 +647,7 @@ export async function encryptCdsDiscoveryRequest(
[key: string]: { clientKey: ArrayBuffer; requestId: ArrayBuffer };
},
phoneNumbers: ReadonlyArray<string>
) {
): Promise<Record<string, unknown>> {
const nonce = getRandomBytes(32);
const numbersArray = new window.dcodeIO.ByteBuffer(
phoneNumbers.length * 8,
@ -669,7 +702,7 @@ export async function encryptCdsDiscoveryRequest(
};
}
export function splitUuids(arrayBuffer: ArrayBuffer) {
export function splitUuids(arrayBuffer: ArrayBuffer): Array<string | null> {
const uuids = [];
for (let i = 0; i < arrayBuffer.byteLength; i += 16) {
const bytes = getBytes(arrayBuffer, i, 16);

View file

@ -2,9 +2,9 @@ import is from '@sindresorhus/is';
import os from 'os';
import semver from 'semver';
export const isMacOS = () => process.platform === 'darwin';
export const isLinux = () => process.platform === 'linux';
export const isWindows = (minVersion?: string) => {
export const isMacOS = (): boolean => process.platform === 'darwin';
export const isLinux = (): boolean => process.platform === 'linux';
export const isWindows = (minVersion?: string): boolean => {
const osRelease = os.release();
if (process.platform !== 'win32') {

View file

@ -34,12 +34,15 @@ function getServer(): WebAPIType {
let config: ConfigMapType = {};
const listeners: ConfigListenersMapType = {};
export async function initRemoteConfig() {
export async function initRemoteConfig(): Promise<void> {
config = window.storage.get('remoteConfig') || {};
await maybeRefreshRemoteConfig();
}
export function onChange(key: ConfigKeyType, fn: ConfigListenerType) {
export function onChange(
key: ConfigKeyType,
fn: ConfigListenerType
): () => void {
const keyListeners: Array<ConfigListenerType> = get(listeners, key, []);
keyListeners.push(fn);
listeners[key] = keyListeners;
@ -49,7 +52,7 @@ export function onChange(key: ConfigKeyType, fn: ConfigListenerType) {
};
}
export const refreshRemoteConfig = async () => {
export const refreshRemoteConfig = async (): Promise<void> => {
const now = Date.now();
const server = getServer();
const newConfig = await server.getConfig();
@ -61,7 +64,8 @@ export const refreshRemoteConfig = async () => {
config = newConfig.reduce((acc, { name, enabled, value }) => {
const previouslyEnabled: boolean = get(oldConfig, [name, 'enabled'], false);
const previousValue: unknown = get(oldConfig, [name, 'value'], undefined);
// If a flag was previously not enabled and is now enabled, record the time it was enabled
// If a flag was previously not enabled and is now enabled,
// record the time it was enabled
const enabledAt: number | undefined =
previouslyEnabled && enabled ? now : get(oldConfig, [name, 'enabledAt']);

View file

@ -26,7 +26,7 @@ export type RenderOptionsType = {
export function renderChange(
change: GroupV2ChangeType,
options: RenderOptionsType
) {
): Array<FullJSXType> {
const { details, from } = change;
return details.map((detail: GroupV2ChangeDetailType) =>
@ -37,7 +37,6 @@ export function renderChange(
);
}
// tslint:disable-next-line cyclomatic-complexity max-func-body-length
export function renderChangeDetail(
detail: GroupV2ChangeDetailType,
options: RenderOptionsType
@ -59,108 +58,103 @@ export function renderChangeDetail(
if (newTitle) {
if (fromYou) {
return renderString('GroupV2--title--change--you', i18n, [newTitle]);
} else if (from) {
}
if (from) {
return renderString('GroupV2--title--change--other', i18n, {
memberName: renderContact(from),
newTitle,
});
} else {
return renderString('GroupV2--title--change--unknown', i18n, [
newTitle,
]);
}
} else {
if (fromYou) {
return renderString('GroupV2--title--remove--you', i18n);
} else if (from) {
return renderString('GroupV2--title--remove--other', i18n, [
renderContact(from),
]);
} else {
return renderString('GroupV2--title--remove--unknown', i18n);
}
return renderString('GroupV2--title--change--unknown', i18n, [newTitle]);
}
} else if (detail.type === 'avatar') {
if (fromYou) {
return renderString('GroupV2--title--remove--you', i18n);
}
if (from) {
return renderString('GroupV2--title--remove--other', i18n, [
renderContact(from),
]);
}
return renderString('GroupV2--title--remove--unknown', i18n);
}
if (detail.type === 'avatar') {
if (detail.removed) {
if (fromYou) {
return renderString('GroupV2--avatar--remove--you', i18n);
} else if (from) {
}
if (from) {
return renderString('GroupV2--avatar--remove--other', i18n, [
renderContact(from),
]);
} else {
return renderString('GroupV2--avatar--remove--unknown', i18n);
}
} else {
if (fromYou) {
return renderString('GroupV2--avatar--change--you', i18n);
} else if (from) {
return renderString('GroupV2--avatar--change--other', i18n, [
renderContact(from),
]);
} else {
return renderString('GroupV2--avatar--change--unknown', i18n);
}
return renderString('GroupV2--avatar--remove--unknown', i18n);
}
} else if (detail.type === 'access-attributes') {
if (fromYou) {
return renderString('GroupV2--avatar--change--you', i18n);
}
if (from) {
return renderString('GroupV2--avatar--change--other', i18n, [
renderContact(from),
]);
}
return renderString('GroupV2--avatar--change--unknown', i18n);
}
if (detail.type === 'access-attributes') {
const { newPrivilege } = detail;
if (newPrivilege === AccessControlEnum.ADMINISTRATOR) {
if (fromYou) {
return renderString('GroupV2--access-attributes--admins--you', i18n);
} else if (from) {
}
if (from) {
return renderString('GroupV2--access-attributes--admins--other', i18n, [
renderContact(from),
]);
} else {
return renderString(
'GroupV2--access-attributes--admins--unknown',
i18n
);
}
} else if (newPrivilege === AccessControlEnum.MEMBER) {
return renderString('GroupV2--access-attributes--admins--unknown', i18n);
}
if (newPrivilege === AccessControlEnum.MEMBER) {
if (fromYou) {
return renderString('GroupV2--access-attributes--all--you', i18n);
} else if (from) {
}
if (from) {
return renderString('GroupV2--access-attributes--all--other', i18n, [
renderContact(from),
]);
} else {
return renderString('GroupV2--access-attributes--all--unknown', i18n);
}
} else {
throw new Error(
`access-attributes change type, privilege ${newPrivilege} is unknown`
);
return renderString('GroupV2--access-attributes--all--unknown', i18n);
}
throw new Error(
`access-attributes change type, privilege ${newPrivilege} is unknown`
);
} else if (detail.type === 'access-members') {
const { newPrivilege } = detail;
if (newPrivilege === AccessControlEnum.ADMINISTRATOR) {
if (fromYou) {
return renderString('GroupV2--access-members--admins--you', i18n);
} else if (from) {
}
if (from) {
return renderString('GroupV2--access-members--admins--other', i18n, [
renderContact(from),
]);
} else {
return renderString('GroupV2--access-members--admins--unknown', i18n);
}
} else if (newPrivilege === AccessControlEnum.MEMBER) {
return renderString('GroupV2--access-members--admins--unknown', i18n);
}
if (newPrivilege === AccessControlEnum.MEMBER) {
if (fromYou) {
return renderString('GroupV2--access-members--all--you', i18n);
} else if (from) {
}
if (from) {
return renderString('GroupV2--access-members--all--other', i18n, [
renderContact(from),
]);
} else {
return renderString('GroupV2--access-members--all--unknown', i18n);
}
} else {
throw new Error(
`access-members change type, privilege ${newPrivilege} is unknown`
);
return renderString('GroupV2--access-members--all--unknown', i18n);
}
throw new Error(
`access-members change type, privilege ${newPrivilege} is unknown`
);
} else if (detail.type === 'member-add') {
const { conversationId } = detail;
const weAreJoiner = conversationId === ourConversationId;
@ -168,29 +162,28 @@ export function renderChangeDetail(
if (weAreJoiner) {
if (fromYou) {
return renderString('GroupV2--member-add--you--you', i18n);
} else if (from) {
}
if (from) {
return renderString('GroupV2--member-add--you--other', i18n, [
renderContact(from),
]);
} else {
return renderString('GroupV2--member-add--you--unknown', i18n);
}
} else {
if (fromYou) {
return renderString('GroupV2--member-add--other--you', i18n, [
renderContact(conversationId),
]);
} else if (from) {
return renderString('GroupV2--member-add--other--other', i18n, {
adderName: renderContact(from),
addeeName: renderContact(conversationId),
});
} else {
return renderString('GroupV2--member-add--other--unknown', i18n, [
renderContact(conversationId),
]);
}
return renderString('GroupV2--member-add--you--unknown', i18n);
}
if (fromYou) {
return renderString('GroupV2--member-add--other--you', i18n, [
renderContact(conversationId),
]);
}
if (from) {
return renderString('GroupV2--member-add--other--other', i18n, {
adderName: renderContact(from),
addeeName: renderContact(conversationId),
});
}
return renderString('GroupV2--member-add--other--unknown', i18n, [
renderContact(conversationId),
]);
} else if (detail.type === 'member-add-from-invite') {
const { conversationId, inviter } = detail;
const weAreJoiner = conversationId === ourConversationId;
@ -200,16 +193,16 @@ export function renderChangeDetail(
return renderString('GroupV2--member-add--from-invite--you', i18n, [
renderContact(inviter),
]);
} else if (weAreInviter) {
}
if (weAreInviter) {
return renderString('GroupV2--member-add--from-invite--from-you', i18n, [
renderContact(conversationId),
]);
} else {
return renderString('GroupV2--member-add--from-invite--other', i18n, {
inviteeName: renderContact(conversationId),
inviterName: renderContact(inviter),
});
}
return renderString('GroupV2--member-add--from-invite--other', i18n, {
inviteeName: renderContact(conversationId),
inviterName: renderContact(inviter),
});
} else if (detail.type === 'member-remove') {
const { conversationId } = detail;
const weAreLeaver = conversationId === ourConversationId;
@ -217,33 +210,34 @@ export function renderChangeDetail(
if (weAreLeaver) {
if (fromYou) {
return renderString('GroupV2--member-remove--you--you', i18n);
} else if (from) {
}
if (from) {
return renderString('GroupV2--member-remove--you--other', i18n, [
renderContact(from),
]);
} else {
return renderString('GroupV2--member-remove--you--unknown', i18n);
}
} else {
if (fromYou) {
return renderString('GroupV2--member-remove--other--you', i18n, [
renderContact(conversationId),
]);
} else if (from && from === conversationId) {
return renderString('GroupV2--member-remove--other--self', i18n, [
renderContact(from),
]);
} else if (from) {
return renderString('GroupV2--member-remove--other--other', i18n, {
adminName: renderContact(from),
memberName: renderContact(conversationId),
});
} else {
return renderString('GroupV2--member-remove--other--unknown', i18n, [
renderContact(conversationId),
]);
}
return renderString('GroupV2--member-remove--you--unknown', i18n);
}
if (fromYou) {
return renderString('GroupV2--member-remove--other--you', i18n, [
renderContact(conversationId),
]);
}
if (from && from === conversationId) {
return renderString('GroupV2--member-remove--other--self', i18n, [
renderContact(from),
]);
}
if (from) {
return renderString('GroupV2--member-remove--other--other', i18n, {
adminName: renderContact(from),
memberName: renderContact(conversationId),
});
}
return renderString('GroupV2--member-remove--other--unknown', i18n, [
renderContact(conversationId),
]);
} else if (detail.type === 'member-privilege') {
const { conversationId, newPrivilege } = detail;
const weAreMember = conversationId === ourConversationId;
@ -256,37 +250,38 @@ export function renderChangeDetail(
i18n,
[renderContact(from)]
);
} else {
return renderString(
'GroupV2--member-privilege--promote--you--unknown',
i18n
);
}
} else {
if (fromYou) {
return renderString(
'GroupV2--member-privilege--promote--other--you',
i18n,
[renderContact(conversationId)]
);
} else if (from) {
return renderString(
'GroupV2--member-privilege--promote--other--other',
i18n,
{
adminName: renderContact(from),
memberName: renderContact(conversationId),
}
);
} else {
return renderString(
'GroupV2--member-privilege--promote--other--unknown',
i18n,
[renderContact(conversationId)]
);
}
return renderString(
'GroupV2--member-privilege--promote--you--unknown',
i18n
);
}
} else if (newPrivilege === RoleEnum.DEFAULT) {
if (fromYou) {
return renderString(
'GroupV2--member-privilege--promote--other--you',
i18n,
[renderContact(conversationId)]
);
}
if (from) {
return renderString(
'GroupV2--member-privilege--promote--other--other',
i18n,
{
adminName: renderContact(from),
memberName: renderContact(conversationId),
}
);
}
return renderString(
'GroupV2--member-privilege--promote--other--unknown',
i18n,
[renderContact(conversationId)]
);
}
if (newPrivilege === RoleEnum.DEFAULT) {
if (weAreMember) {
if (from) {
return renderString(
@ -294,41 +289,39 @@ export function renderChangeDetail(
i18n,
[renderContact(from)]
);
} else {
return renderString(
'GroupV2--member-privilege--demote--you--unknown',
i18n
);
}
} else {
if (fromYou) {
return renderString(
'GroupV2--member-privilege--demote--other--you',
i18n,
[renderContact(conversationId)]
);
} else if (from) {
return renderString(
'GroupV2--member-privilege--demote--other--other',
i18n,
{
adminName: renderContact(from),
memberName: renderContact(conversationId),
}
);
} else {
return renderString(
'GroupV2--member-privilege--demote--other--unknown',
i18n,
[renderContact(conversationId)]
);
}
return renderString(
'GroupV2--member-privilege--demote--you--unknown',
i18n
);
}
} else {
throw new Error(
`member-privilege change type, privilege ${newPrivilege} is unknown`
if (fromYou) {
return renderString(
'GroupV2--member-privilege--demote--other--you',
i18n,
[renderContact(conversationId)]
);
}
if (from) {
return renderString(
'GroupV2--member-privilege--demote--other--other',
i18n,
{
adminName: renderContact(from),
memberName: renderContact(conversationId),
}
);
}
return renderString(
'GroupV2--member-privilege--demote--other--unknown',
i18n,
[renderContact(conversationId)]
);
}
throw new Error(
`member-privilege change type, privilege ${newPrivilege} is unknown`
);
} else if (detail.type === 'pending-add-one') {
const { conversationId } = detail;
const weAreInvited = conversationId === ourConversationId;
@ -337,22 +330,20 @@ export function renderChangeDetail(
return renderString('GroupV2--pending-add--one--you--other', i18n, [
renderContact(from),
]);
} else {
return renderString('GroupV2--pending-add--one--you--unknown', i18n);
}
} else {
if (fromYou) {
return renderString('GroupV2--pending-add--one--other--you', i18n, [
renderContact(conversationId),
]);
} else if (from) {
return renderString('GroupV2--pending-add--one--other--other', i18n, [
renderContact(from),
]);
} else {
return renderString('GroupV2--pending-add--one--other--unknown', i18n);
}
return renderString('GroupV2--pending-add--one--you--unknown', i18n);
}
if (fromYou) {
return renderString('GroupV2--pending-add--one--other--you', i18n, [
renderContact(conversationId),
]);
}
if (from) {
return renderString('GroupV2--pending-add--one--other--other', i18n, [
renderContact(from),
]);
}
return renderString('GroupV2--pending-add--one--other--unknown', i18n);
} else if (detail.type === 'pending-add-many') {
const { count } = detail;
@ -360,16 +351,16 @@ export function renderChangeDetail(
return renderString('GroupV2--pending-add--many--you', i18n, [
count.toString(),
]);
} else if (from) {
}
if (from) {
return renderString('GroupV2--pending-add--many--other', i18n, {
memberName: renderContact(from),
count: count.toString(),
});
} else {
return renderString('GroupV2--pending-add--many--unknown', i18n, [
count.toString(),
]);
}
return renderString('GroupV2--pending-add--many--unknown', i18n, [
count.toString(),
]);
} else if (detail.type === 'pending-remove-one') {
const { inviter, conversationId } = detail;
const weAreInviter = Boolean(inviter && inviter === ourConversationId);
@ -380,13 +371,15 @@ export function renderChangeDetail(
return renderString('GroupV2--pending-remove--decline--you', i18n, [
renderContact(conversationId),
]);
} else if (fromYou) {
}
if (fromYou) {
return renderString(
'GroupV2--pending-remove--revoke-invite-from-you--one--you',
i18n,
[renderContact(conversationId)]
);
} else if (from) {
}
if (from) {
return renderString(
'GroupV2--pending-remove--revoke-invite-from-you--one--other',
i18n,
@ -395,29 +388,30 @@ export function renderChangeDetail(
inviteeName: renderContact(conversationId),
}
);
} else {
return renderString(
'GroupV2--pending-remove--revoke-invite-from-you--one--unknown',
i18n,
[renderContact(conversationId)]
);
}
} else if (sentByInvited) {
return renderString(
'GroupV2--pending-remove--revoke-invite-from-you--one--unknown',
i18n,
[renderContact(conversationId)]
);
}
if (sentByInvited) {
if (inviter) {
return renderString('GroupV2--pending-remove--decline--other', i18n, [
renderContact(inviter),
]);
} else {
return renderString('GroupV2--pending-remove--decline--unknown', i18n);
}
} else if (inviter) {
return renderString('GroupV2--pending-remove--decline--unknown', i18n);
}
if (inviter) {
if (fromYou) {
return renderString(
'GroupV2--pending-remove--revoke-invite-from--one--you',
i18n,
[renderContact(inviter)]
);
} else if (from) {
}
if (from) {
return renderString(
'GroupV2--pending-remove--revoke-invite-from--one--other',
i18n,
@ -426,29 +420,22 @@ export function renderChangeDetail(
memberName: renderContact(inviter),
}
);
} else {
return renderString(
'GroupV2--pending-remove--revoke-invite-from--one--unknown',
i18n,
[renderContact(inviter)]
);
}
} else {
if (fromYou) {
return renderString('GroupV2--pending-remove--revoke--one--you', i18n);
} else if (from) {
return renderString(
'GroupV2--pending-remove--revoke--one--other',
i18n,
[renderContact(from)]
);
} else {
return renderString(
'GroupV2--pending-remove--revoke--one--unknown',
i18n
);
}
return renderString(
'GroupV2--pending-remove--revoke-invite-from--one--unknown',
i18n,
[renderContact(inviter)]
);
}
if (fromYou) {
return renderString('GroupV2--pending-remove--revoke--one--you', i18n);
}
if (from) {
return renderString('GroupV2--pending-remove--revoke--one--other', i18n, [
renderContact(from),
]);
}
return renderString('GroupV2--pending-remove--revoke--one--unknown', i18n);
} else if (detail.type === 'pending-remove-many') {
const { count, inviter } = detail;
const weAreInviter = Boolean(inviter && inviter === ourConversationId);
@ -460,7 +447,8 @@ export function renderChangeDetail(
i18n,
[count.toString()]
);
} else if (from) {
}
if (from) {
return renderString(
'GroupV2--pending-remove--revoke-invite-from-you--many--other',
i18n,
@ -469,14 +457,14 @@ export function renderChangeDetail(
count: count.toString(),
}
);
} else {
return renderString(
'GroupV2--pending-remove--revoke-invite-from-you--many--unknown',
i18n,
[count.toString()]
);
}
} else if (inviter) {
return renderString(
'GroupV2--pending-remove--revoke-invite-from-you--many--unknown',
i18n,
[count.toString()]
);
}
if (inviter) {
if (fromYou) {
return renderString(
'GroupV2--pending-remove--revoke-invite-from--many--you',
@ -486,7 +474,8 @@ export function renderChangeDetail(
memberName: renderContact(inviter),
}
);
} else if (from) {
}
if (from) {
return renderString(
'GroupV2--pending-remove--revoke-invite-from--many--other',
i18n,
@ -496,40 +485,36 @@ export function renderChangeDetail(
memberName: renderContact(inviter),
}
);
} else {
return renderString(
'GroupV2--pending-remove--revoke-invite-from--many--unknown',
i18n,
{
count: count.toString(),
memberName: renderContact(inviter),
}
);
}
} else {
if (fromYou) {
return renderString(
'GroupV2--pending-remove--revoke--many--you',
i18n,
[count.toString()]
);
} else if (from) {
return renderString(
'GroupV2--pending-remove--revoke--many--other',
i18n,
{
memberName: renderContact(from),
count: count.toString(),
}
);
} else {
return renderString(
'GroupV2--pending-remove--revoke--many--unknown',
i18n,
[count.toString()]
);
}
return renderString(
'GroupV2--pending-remove--revoke-invite-from--many--unknown',
i18n,
{
count: count.toString(),
memberName: renderContact(inviter),
}
);
}
if (fromYou) {
return renderString('GroupV2--pending-remove--revoke--many--you', i18n, [
count.toString(),
]);
}
if (from) {
return renderString(
'GroupV2--pending-remove--revoke--many--other',
i18n,
{
memberName: renderContact(from),
count: count.toString(),
}
);
}
return renderString(
'GroupV2--pending-remove--revoke--many--unknown',
i18n,
[count.toString()]
);
} else {
throw missingCaseError(detail);
}

View file

@ -1,5 +1,3 @@
/* tslint:disable no-dynamic-delete no-unnecessary-local-variable */
import {
compact,
Dictionary,
@ -8,6 +6,8 @@ import {
isNumber,
values,
} from 'lodash';
import { ClientZkGroupCipher } from 'zkgroup';
import { v4 as getGuid } from 'uuid';
import {
getCredentialsForToday,
GROUP_CREDENTIALS_KEY,
@ -33,7 +33,6 @@ import {
getClientZkAuthOperations,
getClientZkGroupCipher,
} from './util/zkgroup';
import { ClientZkGroupCipher } from 'zkgroup';
import {
arrayBufferToBase64,
arrayBufferToHex,
@ -50,7 +49,6 @@ import {
ProtoBinaryType,
} from './textsecure.d';
import { GroupCredentialsType } from './textsecure/WebAPI';
import { v4 as getGuid } from 'uuid';
import { CURRENT_SCHEMA_VERSION as MAX_MESSAGE_SCHEMA } from '../js/modules/types/message';
export type GroupV2AccessAttributesChangeType = {
@ -250,7 +248,9 @@ export async function uploadGroupChange({
// Utility
export function deriveGroupFields(masterKey: ArrayBuffer) {
export function deriveGroupFields(
masterKey: ArrayBuffer
): Record<string, ArrayBuffer> {
const secretParams = deriveGroupSecretParams(masterKey);
const publicParams = deriveGroupPublicParams(secretParams);
const id = deriveGroupID(secretParams);
@ -273,7 +273,9 @@ type MaybeUpdatePropsType = {
dropInitialJoinMessage?: boolean;
};
export async function waitThenMaybeUpdateGroup(options: MaybeUpdatePropsType) {
export async function waitThenMaybeUpdateGroup(
options: MaybeUpdatePropsType
): Promise<void> {
// First wait to process all incoming messages on the websocket
await window.waitForEmptyEventQueue();
@ -321,8 +323,9 @@ export async function maybeUpdateGroup({
conversation.set(newAttributes);
// Ensure that all generated message are ordered properly. Before the provided timestamp
// so update messages appear before the initiating message, or after now().
// Ensure that all generated messages are ordered properly.
// Before the provided timestamp so update messages appear before the
// initiating message, or after now().
let syntheticTimestamp = receivedAt
? receivedAt - (groupChangeMessages.length + 1)
: Date.now();
@ -355,7 +358,6 @@ export async function maybeUpdateGroup({
const contact = window.ConversationController.get(member.uuid);
if (member.profileKey && contact && !contact.get('profileKey')) {
// tslint:disable-next-line no-floating-promises
contact.setProfileKey(member.profileKey);
}
});
@ -487,8 +489,8 @@ async function updateGroupViaState({
authCredentialBase64: groupCredentials.tomorrow.credential,
});
return result;
} catch (error) {
if (error.code === GROUP_ACCESS_DENIED_CODE) {
} catch (subError) {
if (subError.code === GROUP_ACCESS_DENIED_CODE) {
return generateLeftGroupChanges(group);
}
}
@ -537,9 +539,8 @@ async function updateGroupViaLogs({
...deltaOptions,
authCredentialBase64: groupCredentials.tomorrow.credential,
});
} else {
throw error;
}
throw error;
}
}
@ -654,6 +655,7 @@ async function getGroupDelta({
let response;
const changes: Array<GroupChangesClass> = [];
do {
// eslint-disable-next-line no-await-in-loop
response = await sender.getGroupLog(revisionToFetch, options);
changes.push(response.changes);
if (response.end) {
@ -689,6 +691,7 @@ async function integrateGroupChanges({
const { groupChanges } = changes[i];
if (!groupChanges) {
// eslint-disable-next-line no-continue
continue;
}
@ -699,6 +702,7 @@ async function integrateGroupChanges({
const { groupChange } = changeState;
if (!groupChange) {
// eslint-disable-next-line no-continue
continue;
}
@ -707,6 +711,7 @@ async function integrateGroupChanges({
newAttributes,
groupChangeMessages,
members,
// eslint-disable-next-line no-await-in-loop
} = await integrateGroupChange({
group: attributes,
newRevision,
@ -868,7 +873,6 @@ export async function getCurrentGroupState({
};
}
// tslint:disable-next-line max-func-body-length cyclomatic-complexity
function extractDiffs({
current,
dropInitialJoinMessage,
@ -1126,7 +1130,6 @@ type GroupChangeResultType = {
newProfileKeys: Array<GroupChangeMemberType>;
};
// tslint:disable-next-line cyclomatic-complexity max-func-body-length
async function applyGroupChange({
group,
actions,
@ -1158,7 +1161,7 @@ async function applyGroupChange({
result.revision = version;
// addMembers?: Array<GroupChangeClass.Actions.AddMemberAction>;
(actions.addMembers || []).map(addMember => {
(actions.addMembers || []).forEach(addMember => {
const { added } = addMember;
if (!added) {
throw new Error('applyGroupChange: addMember.added is missing');
@ -1249,7 +1252,8 @@ async function applyGroupChange({
}
});
// modifyMemberProfileKeys?: Array<GroupChangeClass.Actions.ModifyMemberProfileKeyAction>;
// modifyMemberProfileKeys?:
// Array<GroupChangeClass.Actions.ModifyMemberProfileKeyAction>;
(actions.modifyMemberProfileKeys || []).forEach(modifyMemberProfileKey => {
const { profileKey, uuid } = modifyMemberProfileKey;
if (!profileKey || !uuid) {
@ -1374,8 +1378,7 @@ async function applyGroupChange({
// modifyTitle?: GroupChangeClass.Actions.ModifyTitleAction;
if (actions.modifyTitle) {
const title: GroupAttributeBlobClass | undefined =
actions.modifyTitle.title;
const { title } = actions.modifyTitle;
if (title && title.content === 'title') {
result.name = title.title;
} else {
@ -1388,11 +1391,12 @@ async function applyGroupChange({
// modifyAvatar?: GroupChangeClass.Actions.ModifyAvatarAction;
if (actions.modifyAvatar) {
const avatar = actions.modifyAvatar.avatar;
const { avatar } = actions.modifyAvatar;
await applyNewAvatar(avatar, result, logId);
}
// modifyDisappearingMessagesTimer?: GroupChangeClass.Actions.ModifyDisappearingMessagesTimerAction;
// modifyDisappearingMessagesTimer?:
// GroupChangeClass.Actions.ModifyDisappearingMessagesTimerAction;
if (actions.modifyDisappearingMessagesTimer) {
const disappearingMessagesTimer: GroupAttributeBlobClass | undefined =
actions.modifyDisappearingMessagesTimer.timer;
@ -1415,7 +1419,8 @@ async function applyGroupChange({
attributes: ACCESS_ENUM.MEMBER,
};
// modifyAttributesAccess?: GroupChangeClass.Actions.ModifyAttributesAccessControlAction;
// modifyAttributesAccess?:
// GroupChangeClass.Actions.ModifyAttributesAccessControlAction;
if (actions.modifyAttributesAccess) {
result.accessControl = {
...result.accessControl,
@ -1447,6 +1452,8 @@ async function applyGroupChange({
};
}
// Ovewriting result.avatar as part of functionality
/* eslint-disable no-param-reassign */
async function applyNewAvatar(
newAvatar: string | undefined,
result: ConversationAttributesType,
@ -1514,8 +1521,8 @@ async function applyNewAvatar(
result.avatar = undefined;
}
}
/* eslint-enable no-param-reassign */
// tslint:disable-next-line cyclomatic-complexity max-func-body-length
async function applyGroupState(
group: ConversationAttributesType,
groupState: GroupClass
@ -1532,7 +1539,7 @@ async function applyGroupState(
// title
// Note: During decryption, title becomes a GroupAttributeBlob
const title: GroupAttributeBlobClass | undefined = groupState.title;
const { title } = groupState;
if (title && title.content === 'title') {
result.name = title.title;
} else {
@ -1544,8 +1551,7 @@ async function applyGroupState(
// disappearingMessagesTimer
// Note: during decryption, disappearingMessageTimer becomes a GroupAttributeBlob
const disappearingMessagesTimer: GroupAttributeBlobClass | undefined =
groupState.disappearingMessagesTimer;
const { disappearingMessagesTimer } = groupState;
if (
disappearingMessagesTimer &&
disappearingMessagesTimer.content === 'disappearingMessagesDuration'
@ -1665,13 +1671,13 @@ function hasData(data: ProtoBinaryType): boolean {
return data && data.limit > 0;
}
// tslint:disable-next-line max-func-body-length cyclomatic-complexity
function decryptGroupChange(
actions: GroupChangeClass.Actions,
_actions: GroupChangeClass.Actions,
groupSecretParams: string,
logId: string
): GroupChangeClass.Actions {
const clientZkGroupCipher = getClientZkGroupCipher(groupSecretParams);
const actions = _actions;
if (hasData(actions.sourceUuid)) {
try {
@ -1701,7 +1707,9 @@ function decryptGroupChange(
// addMembers?: Array<GroupChangeClass.Actions.AddMemberAction>;
actions.addMembers = compact(
(actions.addMembers || []).map(addMember => {
(actions.addMembers || []).map(_addMember => {
const addMember = _addMember;
if (addMember.added) {
const decrypted = decryptMember(
clientZkGroupCipher,
@ -1714,17 +1722,16 @@ function decryptGroupChange(
addMember.added = decrypted;
return addMember;
} else {
throw new Error(
'decryptGroupChange: AddMember was missing added field!'
);
}
throw new Error('decryptGroupChange: AddMember was missing added field!');
})
);
// deleteMembers?: Array<GroupChangeClass.Actions.DeleteMemberAction>;
actions.deleteMembers = compact(
(actions.deleteMembers || []).map(deleteMember => {
(actions.deleteMembers || []).map(_deleteMember => {
const deleteMember = _deleteMember;
if (hasData(deleteMember.deletedUserId)) {
try {
deleteMember.deletedUserId = decryptUuid(
@ -1764,7 +1771,9 @@ function decryptGroupChange(
// modifyMemberRoles?: Array<GroupChangeClass.Actions.ModifyMemberRoleAction>;
actions.modifyMemberRoles = compact(
(actions.modifyMemberRoles || []).map(modifyMember => {
(actions.modifyMemberRoles || []).map(_modifyMember => {
const modifyMember = _modifyMember;
if (hasData(modifyMember.userId)) {
try {
modifyMember.userId = decryptUuid(
@ -1808,9 +1817,12 @@ function decryptGroupChange(
})
);
// modifyMemberProfileKeys?: Array<GroupChangeClass.Actions.ModifyMemberProfileKeyAction>;
// modifyMemberProfileKeys?:
// Array<GroupChangeClass.Actions.ModifyMemberProfileKeyAction>;
actions.modifyMemberProfileKeys = compact(
(actions.modifyMemberProfileKeys || []).map(modifyMemberProfileKey => {
(actions.modifyMemberProfileKeys || []).map(_modifyMemberProfileKey => {
const modifyMemberProfileKey = _modifyMemberProfileKey;
if (hasData(modifyMemberProfileKey.presentation)) {
const { profileKey, uuid } = decryptProfileKeyCredentialPresentation(
clientZkGroupCipher,
@ -1854,7 +1866,9 @@ function decryptGroupChange(
// addPendingMembers?: Array<GroupChangeClass.Actions.AddPendingMemberAction>;
actions.addPendingMembers = compact(
(actions.addPendingMembers || []).map(addPendingMember => {
(actions.addPendingMembers || []).map(_addPendingMember => {
const addPendingMember = _addPendingMember;
if (addPendingMember.added) {
const decrypted = decryptPendingMember(
clientZkGroupCipher,
@ -1867,17 +1881,18 @@ function decryptGroupChange(
addPendingMember.added = decrypted;
return addPendingMember;
} else {
throw new Error(
'decryptGroupChange: addPendingMember was missing added field!'
);
}
throw new Error(
'decryptGroupChange: addPendingMember was missing added field!'
);
})
);
// deletePendingMembers?: Array<GroupChangeClass.Actions.DeletePendingMemberAction>;
actions.deletePendingMembers = compact(
(actions.deletePendingMembers || []).map(deletePendingMember => {
(actions.deletePendingMembers || []).map(_deletePendingMember => {
const deletePendingMember = _deletePendingMember;
if (hasData(deletePendingMember.deletedUserId)) {
try {
deletePendingMember.deletedUserId = decryptUuid(
@ -1917,7 +1932,9 @@ function decryptGroupChange(
// promotePendingMembers?: Array<GroupChangeClass.Actions.PromotePendingMemberAction>;
actions.promotePendingMembers = compact(
(actions.promotePendingMembers || []).map(promotePendingMember => {
(actions.promotePendingMembers || []).map(_promotePendingMember => {
const promotePendingMember = _promotePendingMember;
if (hasData(promotePendingMember.presentation)) {
const { profileKey, uuid } = decryptProfileKeyCredentialPresentation(
clientZkGroupCipher,
@ -1979,7 +1996,8 @@ function decryptGroupChange(
// modifyAvatar?: GroupChangeClass.Actions.ModifyAvatarAction;
// Note: decryption happens during application of the change, on download of the avatar
// modifyDisappearingMessagesTimer?: GroupChangeClass.Actions.ModifyDisappearingMessagesTimerAction;
// modifyDisappearingMessagesTimer?:
// GroupChangeClass.Actions.ModifyDisappearingMessagesTimerAction;
if (
actions.modifyDisappearingMessagesTimer &&
hasData(actions.modifyDisappearingMessagesTimer.timer)
@ -2002,7 +2020,8 @@ function decryptGroupChange(
actions.modifyDisappearingMessagesTimer.timer = undefined;
}
// modifyAttributesAccess?: GroupChangeClass.Actions.ModifyAttributesAccessControlAction;
// modifyAttributesAccess?:
// GroupChangeClass.Actions.ModifyAttributesAccessControlAction;
if (
actions.modifyAttributesAccess &&
!isValidAccess(actions.modifyAttributesAccess.attributesAccess)
@ -2025,13 +2044,13 @@ function decryptGroupChange(
return actions;
}
// tslint:disable-next-line max-func-body-length
function decryptGroupState(
groupState: GroupClass,
_groupState: GroupClass,
groupSecretParams: string,
logId: string
): GroupClass {
const clientZkGroupCipher = getClientZkGroupCipher(groupSecretParams);
const groupState = _groupState;
// title
if (hasData(groupState.title)) {
@ -2121,9 +2140,11 @@ function decryptGroupState(
function decryptMember(
clientZkGroupCipher: ClientZkGroupCipher,
member: MemberClass,
_member: MemberClass,
logId: string
) {
const member = _member;
// userId
if (hasData(member.userId)) {
try {
@ -2175,12 +2196,13 @@ function decryptMember(
return member;
}
// tslint:disable-next-line max-func-body-length cyclomatic-complexity
function decryptPendingMember(
clientZkGroupCipher: ClientZkGroupCipher,
member: PendingMemberClass,
_member: PendingMemberClass,
logId: string
) {
const member = _member;
// addedByUserId
if (hasData(member.addedByUserId)) {
try {

8
ts/model-types.d.ts vendored
View file

@ -25,7 +25,7 @@ declare class DeletesModelType extends Backbone.Model<DeletesAttributesType> {
type TaskResultType = any;
type MessageAttributesType = {
export type MessageAttributesType = {
id: string;
type?: string;
@ -63,9 +63,9 @@ declare class MessageModelType extends Backbone.Model<MessageAttributesType> {
): Promise<void>;
}
type ConversationTypeType = 'private' | 'group';
export type ConversationTypeType = 'private' | 'group';
type ConversationAttributesType = {
export type ConversationAttributesType = {
id: string;
type: ConversationTypeType;
timestamp: number;
@ -201,7 +201,7 @@ export declare class ConversationModelType extends Backbone.Model<
wrapSend: (sendPromise: Promise<any>) => Promise<any>;
}
declare class ConversationModelCollectionType extends Backbone.Collection<
export declare class ConversationModelCollectionType extends Backbone.Collection<
ConversationModelType
> {
resetLookups(): void;

2
ts/textsecure.d.ts vendored
View file

@ -209,7 +209,7 @@ type ProtobufCollectionType = DeviceMessagesProtobufTypes &
// Note: there are a lot of places in the code that overwrite a field like this
// with a type that the app can use. Being more rigorous with these
// types would require code changes, out of scope for now.
type ProtoBinaryType = any;
export type ProtoBinaryType = any;
type ProtoBigNumberType = any;
// Groups.proto

View file

@ -177,6 +177,7 @@
"rulesDirectory": ["node_modules/tslint-microsoft-contrib"],
"linterOptions": {
"exclude": [
"ts/*.ts",
"ts/components/emoji/**",
"ts/backbone/**",
"ts/build/**",