Migrate base ts to eslint
This commit is contained in:
parent
ad555ec8a9
commit
50378ed9bc
10 changed files with 475 additions and 409 deletions
|
@ -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/**
|
||||
|
|
|
@ -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) {
|
||||
|
|
149
ts/Crypto.ts
149
ts/Crypto.ts
|
@ -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);
|
||||
|
|
6
ts/OS.ts
6
ts/OS.ts
|
@ -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') {
|
||||
|
|
|
@ -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']);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
124
ts/groups.ts
124
ts/groups.ts
|
@ -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
8
ts/model-types.d.ts
vendored
|
@ -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
2
ts/textsecure.d.ts
vendored
|
@ -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
|
||||
|
|
|
@ -177,6 +177,7 @@
|
|||
"rulesDirectory": ["node_modules/tslint-microsoft-contrib"],
|
||||
"linterOptions": {
|
||||
"exclude": [
|
||||
"ts/*.ts",
|
||||
"ts/components/emoji/**",
|
||||
"ts/backbone/**",
|
||||
"ts/build/**",
|
||||
|
|
Loading…
Reference in a new issue