From 352a718631469b6e3beafc34690bb57dd73806b9 Mon Sep 17 00:00:00 2001 From: Gerzon Z Date: Wed, 16 Feb 2022 17:14:28 -0400 Subject: [PATCH] Chore: Migrate lib/encryption folder to TypeScript (#3639) * Initial commit * add types/bytebuffer, add type definitions to params and update interfaces * add more types and type assertions * update types * change bang operator by type assertion and update class variables definitions * add types for deferred class * minor tweaks on types definitions * add ts-ignore * Update encryption.ts * update deferred and encryption * update encryption.ts * Update room.ts * update toDecrypt type * initialize sessionKeyExportedString * remove return types --- app/definitions/IMessage.ts | 10 +- app/definitions/ISubscription.ts | 1 + app/definitions/IThread.ts | 6 +- app/definitions/IThreadMessage.ts | 6 +- app/definitions/IUser.ts | 2 +- .../encryption/{constants.js => constants.ts} | 0 .../{encryption.js => encryption.ts} | 93 ++++++++++++------- app/lib/encryption/{index.js => index.ts} | 0 app/lib/encryption/{room.js => room.ts} | 53 +++++++---- app/lib/encryption/{utils.js => utils.ts} | 26 +++--- app/utils/base64-js/index.ts | 6 +- app/utils/deferred.js | 14 --- app/utils/deferred.ts | 41 ++++++++ package.json | 1 + yarn.lock | 13 +++ 15 files changed, 178 insertions(+), 94 deletions(-) rename app/lib/encryption/{constants.js => constants.ts} (100%) rename app/lib/encryption/{encryption.js => encryption.ts} (81%) rename app/lib/encryption/{index.js => index.ts} (100%) rename app/lib/encryption/{room.js => room.ts} (80%) rename app/lib/encryption/{utils.js => utils.ts} (65%) delete mode 100644 app/utils/deferred.js create mode 100644 app/utils/deferred.ts diff --git a/app/definitions/IMessage.ts b/app/definitions/IMessage.ts index 4b7183d10..d86492cde 100644 --- a/app/definitions/IMessage.ts +++ b/app/definitions/IMessage.ts @@ -3,7 +3,8 @@ import { MarkdownAST } from '@rocket.chat/message-parser'; import { IAttachment } from './IAttachment'; import { IReaction } from './IReaction'; -import { SubscriptionType } from './ISubscription'; + +export type MessageType = 'jitsi_call_started' | 'discussion-created' | 'e2e' | 'load_more' | 'rm' | 'uj'; export interface IUserMessage { _id: string; @@ -34,12 +35,16 @@ export interface ITranslations { value: string; } +export type E2EType = 'pending' | 'done'; + export interface ILastMessage { _id: string; rid: string; tshow: boolean; + t: MessageType; tmid: string; msg: string; + e2e: E2EType; ts: Date; u: IUserMessage; _updatedAt: Date; @@ -55,8 +60,9 @@ export interface ILastMessage { export interface IMessage { _id: string; + rid: string; msg?: string; - t?: SubscriptionType; + t?: MessageType; ts: Date; u: IUserMessage; alias: string; diff --git a/app/definitions/ISubscription.ts b/app/definitions/ISubscription.ts index a2d28ed44..3e523656b 100644 --- a/app/definitions/ISubscription.ts +++ b/app/definitions/ISubscription.ts @@ -12,6 +12,7 @@ export enum SubscriptionType { DIRECT = 'd', CHANNEL = 'c', OMNICHANNEL = 'l', + E2E = 'e2e', THREAD = 'thread' // FIXME: this is not a type of subscription } diff --git a/app/definitions/IThread.ts b/app/definitions/IThread.ts index 3116a502f..2095d903f 100644 --- a/app/definitions/IThread.ts +++ b/app/definitions/IThread.ts @@ -2,9 +2,8 @@ import Model from '@nozbe/watermelondb/Model'; import { MarkdownAST } from '@rocket.chat/message-parser'; import { IAttachment } from './IAttachment'; -import { IEditedBy, IUserChannel, IUserMention, IUserMessage } from './IMessage'; +import { IEditedBy, IUserChannel, IUserMention, IUserMessage, MessageType } from './IMessage'; import { IReaction } from './IReaction'; -import { SubscriptionType } from './ISubscription'; import { IUrl } from './IUrl'; interface IFileThread { @@ -35,8 +34,9 @@ export interface IThreadResult { export interface IThread { id: string; + tmsg?: string; msg?: string; - t?: SubscriptionType; + t?: MessageType; rid: string; _updatedAt?: Date; ts?: Date; diff --git a/app/definitions/IThreadMessage.ts b/app/definitions/IThreadMessage.ts index d4ab3fca0..9792a9504 100644 --- a/app/definitions/IThreadMessage.ts +++ b/app/definitions/IThreadMessage.ts @@ -1,13 +1,13 @@ import Model from '@nozbe/watermelondb/Model'; import { IAttachment } from './IAttachment'; -import { IEditedBy, ITranslations, IUserChannel, IUserMention, IUserMessage } from './IMessage'; +import { IEditedBy, ITranslations, IUserChannel, IUserMention, IUserMessage, MessageType } from './IMessage'; import { IReaction } from './IReaction'; -import { SubscriptionType } from './ISubscription'; export interface IThreadMessage { + tmsg?: string; msg?: string; - t?: SubscriptionType; + t?: MessageType; rid: string; ts: Date; u: IUserMessage; diff --git a/app/definitions/IUser.ts b/app/definitions/IUser.ts index 1b5d244c8..06aaffa1f 100644 --- a/app/definitions/IUser.ts +++ b/app/definitions/IUser.ts @@ -99,8 +99,8 @@ export interface IUser extends IRocketChatRecord { roles: string[]; type: string; active: boolean; - username?: string; name?: string; + username: string; services?: IUserServices; emails?: IUserEmail[]; status?: UserStatus; diff --git a/app/lib/encryption/constants.js b/app/lib/encryption/constants.ts similarity index 100% rename from app/lib/encryption/constants.js rename to app/lib/encryption/constants.ts diff --git a/app/lib/encryption/encryption.js b/app/lib/encryption/encryption.ts similarity index 81% rename from app/lib/encryption/encryption.js rename to app/lib/encryption/encryption.ts index 106a27ffe..0f5c17249 100644 --- a/app/lib/encryption/encryption.js +++ b/app/lib/encryption/encryption.ts @@ -1,7 +1,7 @@ import EJSON from 'ejson'; import SimpleCrypto from 'react-native-simple-crypto'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; -import { Q } from '@nozbe/watermelondb'; +import { Q, Model } from '@nozbe/watermelondb'; import RocketChat from '../rocketchat'; import UserPreferences from '../userPreferences'; @@ -20,9 +20,25 @@ import { } from './constants'; import { joinVectorData, randomPassword, splitVectorData, toString, utf8ToBuffer } from './utils'; import { EncryptionRoom } from './index'; +import { IMessage, ISubscription, TMessageModel, TSubscriptionModel, TThreadMessageModel, TThreadModel } from '../../definitions'; class Encryption { + ready: boolean; + privateKey: string | null; + readyPromise: Deferred; + userId: string | null; + roomInstances: { + [rid: string]: { + ready: boolean; + provideKeyToUser: Function; + handshake: Function; + decrypt: Function; + encrypt: Function; + }; + }; + constructor() { + this.userId = ''; this.ready = false; this.privateKey = null; this.roomInstances = {}; @@ -37,7 +53,7 @@ class Encryption { } // Initialize Encryption client - initialize = userId => { + initialize = (userId: string) => { this.userId = userId; this.roomInstances = {}; @@ -82,7 +98,7 @@ class Encryption { }; // When a new participant join and request a new room encryption key - provideRoomKeyToUser = async (keyId, rid) => { + provideRoomKeyToUser = async (keyId: string, rid: string) => { // If the client is not ready if (!this.ready) { try { @@ -100,14 +116,14 @@ class Encryption { }; // Persist keys on UserPreferences - persistKeys = async (server, publicKey, privateKey) => { + persistKeys = async (server: string, publicKey: string, privateKey: string) => { this.privateKey = await SimpleCrypto.RSA.importKey(EJSON.parse(privateKey)); await UserPreferences.setStringAsync(`${server}-${E2E_PUBLIC_KEY}`, EJSON.stringify(publicKey)); await UserPreferences.setStringAsync(`${server}-${E2E_PRIVATE_KEY}`, privateKey); }; // Could not obtain public-private keypair from server. - createKeys = async (userId, server) => { + createKeys = async (userId: string, server: string) => { // Generate new keys const key = await SimpleCrypto.RSA.generateKeys(2048); @@ -132,7 +148,7 @@ class Encryption { }; // Encode a private key before send it to the server - encodePrivateKey = async (privateKey, password, userId) => { + encodePrivateKey = async (privateKey: string, password: string, userId: string) => { const masterKey = await this.generateMasterKey(password, userId); const vector = await SimpleCrypto.utils.randomBytes(16); @@ -142,7 +158,7 @@ class Encryption { }; // Decode a private key fetched from server - decodePrivateKey = async (privateKey, password, userId) => { + decodePrivateKey = async (privateKey: string, password: string, userId: string) => { const masterKey = await this.generateMasterKey(password, userId); const [vector, cipherText] = splitVectorData(EJSON.parse(privateKey)); @@ -152,7 +168,7 @@ class Encryption { }; // Generate a user master key, this is based on userId and a password - generateMasterKey = async (password, userId) => { + generateMasterKey = async (password: string, userId: string) => { const iterations = 1000; const hash = 'SHA256'; const keyLen = 32; @@ -166,18 +182,18 @@ class Encryption { }; // Create a random password to local created keys - createRandomPassword = async server => { + createRandomPassword = async (server: string) => { const password = randomPassword(); await UserPreferences.setStringAsync(`${server}-${E2E_RANDOM_PASSWORD_KEY}`, password); return password; }; - changePassword = async (server, password) => { + changePassword = async (server: string, password: string) => { // Cast key to the format server is expecting - const privateKey = await SimpleCrypto.RSA.exportKey(this.privateKey); + const privateKey = await SimpleCrypto.RSA.exportKey(this.privateKey as string); // Encode the private key - const encodedPrivateKey = await this.encodePrivateKey(EJSON.stringify(privateKey), password, this.userId); + const encodedPrivateKey = await this.encodePrivateKey(EJSON.stringify(privateKey), password, this.userId as string); const publicKey = await UserPreferences.getStringAsync(`${server}-${E2E_PUBLIC_KEY}`); // Send the new keys to the server @@ -185,7 +201,7 @@ class Encryption { }; // get a encryption room instance - getRoomInstance = async rid => { + getRoomInstance = async (rid: string) => { // Prevent handshake again if (this.roomInstances[rid]?.ready) { return this.roomInstances[rid]; @@ -193,7 +209,7 @@ class Encryption { // If doesn't have a instance of this room if (!this.roomInstances[rid]) { - this.roomInstances[rid] = new EncryptionRoom(rid, this.userId); + this.roomInstances[rid] = new EncryptionRoom(rid, this.userId as string); } const roomE2E = this.roomInstances[rid]; @@ -206,7 +222,7 @@ class Encryption { // Logic to decrypt all pending messages/threads/threadMessages // after initialize the encryption client - decryptPendingMessages = async roomId => { + decryptPendingMessages = async (roomId?: string) => { const db = database.active; const messagesCollection = db.get('messages'); @@ -228,8 +244,12 @@ class Encryption { const threadMessagesToDecrypt = await threadMessagesCollection.query(...whereClause).fetch(); // Concat messages/threads/threadMessages - let toDecrypt = [...messagesToDecrypt, ...threadsToDecrypt, ...threadMessagesToDecrypt]; - toDecrypt = await Promise.all( + let toDecrypt: (TThreadModel | TThreadMessageModel)[] = [ + ...messagesToDecrypt, + ...threadsToDecrypt, + ...threadMessagesToDecrypt + ]; + toDecrypt = (await Promise.all( toDecrypt.map(async message => { const { t, msg, tmsg } = message; const { id: rid } = message.subscription; @@ -240,9 +260,10 @@ class Encryption { msg, tmsg }); + try { return message.prepareUpdate( - protectedFunction(m => { + protectedFunction((m: TMessageModel) => { Object.assign(m, newMessage); }) ); @@ -250,9 +271,9 @@ class Encryption { return null; } }) - ); + )) as (TThreadModel | TThreadMessageModel)[]; - await db.action(async () => { + await db.write(async () => { await db.batch(...toDecrypt); }); } catch (e) { @@ -271,19 +292,19 @@ class Encryption { const subsEncrypted = await subCollection.query(Q.where('e2e_key_id', Q.notEq(null))).fetch(); // We can't do this on database level since lastMessage is not a database object const subsToDecrypt = subsEncrypted.filter( - sub => + (sub: ISubscription) => // Encrypted message sub?.lastMessage?.t === E2E_MESSAGE_TYPE && // Message pending decrypt sub?.lastMessage?.e2e === E2E_STATUS.PENDING ); await Promise.all( - subsToDecrypt.map(async sub => { + subsToDecrypt.map(async (sub: TSubscriptionModel) => { const { rid, lastMessage } = sub; const newSub = await this.decryptSubscription({ rid, lastMessage }); try { return sub.prepareUpdate( - protectedFunction(m => { + protectedFunction((m: TSubscriptionModel) => { Object.assign(m, newSub); }) ); @@ -293,7 +314,7 @@ class Encryption { }) ); - await db.action(async () => { + await db.write(async () => { await db.batch(...subsToDecrypt); }); } catch (e) { @@ -302,7 +323,7 @@ class Encryption { }; // Decrypt a subscription lastMessage - decryptSubscription = async subscription => { + decryptSubscription = async (subscription: Partial) => { // If the subscription doesn't have a lastMessage just return if (!subscription?.lastMessage) { return subscription; @@ -334,18 +355,18 @@ class Encryption { let subRecord; try { - subRecord = await subCollection.find(rid); + subRecord = await subCollection.find(rid as string); } catch { // Do nothing } try { - const batch = []; + const batch: (Model | null | void | false | Promise)[] = []; // If the subscription doesn't exists yet if (!subRecord) { // Let's create the subscription with the data received batch.push( - subCollection.prepareCreate(s => { + subCollection.prepareCreate((s: TSubscriptionModel) => { s._raw = sanitizedRaw({ id: rid }, subCollection.schema); Object.assign(s, subscription); }) @@ -355,7 +376,7 @@ class Encryption { try { // Let's update the subscription with the received E2EKey batch.push( - subRecord.prepareUpdate(s => { + subRecord.prepareUpdate((s: TSubscriptionModel) => { s.E2EKey = subscription.E2EKey; }) ); @@ -366,7 +387,7 @@ class Encryption { // If batch has some operation if (batch.length) { - await db.action(async () => { + await db.write(async () => { await db.batch(...batch); }); } @@ -377,7 +398,7 @@ class Encryption { } // Get a instance using the subscription - const roomE2E = await this.getRoomInstance(rid); + const roomE2E = await this.getRoomInstance(rid as string); const decryptedMessage = await roomE2E.decrypt(lastMessage); return { ...subscription, @@ -386,7 +407,7 @@ class Encryption { }; // Encrypt a message - encryptMessage = async message => { + encryptMessage = async (message: IMessage) => { const { rid } = message; const db = database.active; const subCollection = db.get('subscriptions'); @@ -419,7 +440,7 @@ class Encryption { }; // Decrypt a message - decryptMessage = async message => { + decryptMessage = async (message: Partial) => { const { t, e2e } = message; // Prevent create a new instance if this room was encrypted sometime ago @@ -440,15 +461,15 @@ class Encryption { } const { rid } = message; - const roomE2E = await this.getRoomInstance(rid); + const roomE2E = await this.getRoomInstance(rid as string); return roomE2E.decrypt(message); }; // Decrypt multiple messages - decryptMessages = messages => Promise.all(messages.map(m => this.decryptMessage(m))); + decryptMessages = (messages: IMessage[]) => Promise.all(messages.map((m: IMessage) => this.decryptMessage(m))); // Decrypt multiple subscriptions - decryptSubscriptions = subscriptions => Promise.all(subscriptions.map(s => this.decryptSubscription(s))); + decryptSubscriptions = (subscriptions: ISubscription[]) => Promise.all(subscriptions.map(s => this.decryptSubscription(s))); } const encryption = new Encryption(); diff --git a/app/lib/encryption/index.js b/app/lib/encryption/index.ts similarity index 100% rename from app/lib/encryption/index.js rename to app/lib/encryption/index.ts diff --git a/app/lib/encryption/room.js b/app/lib/encryption/room.ts similarity index 80% rename from app/lib/encryption/room.js rename to app/lib/encryption/room.ts index d61c7d75a..a68ccc49f 100644 --- a/app/lib/encryption/room.js +++ b/app/lib/encryption/room.ts @@ -1,7 +1,9 @@ import EJSON from 'ejson'; import { Base64 } from 'js-base64'; import SimpleCrypto from 'react-native-simple-crypto'; +import ByteBuffer from 'bytebuffer'; +import { IMessage } from '../../definitions'; import RocketChat from '../rocketchat'; import Deferred from '../../utils/deferred'; import debounce from '../../utils/debounce'; @@ -19,13 +21,26 @@ import { utf8ToBuffer } from './utils'; import { Encryption } from './index'; +import { IUser } from '../../definitions/IUser'; export default class EncryptionRoom { - constructor(roomId, userId) { + ready: boolean; + roomId: string; + userId: string; + establishing: boolean; + readyPromise: Deferred; + sessionKeyExportedString: string | ByteBuffer; + keyID: string; + roomKey: ArrayBuffer; + + constructor(roomId: string, userId: string) { this.ready = false; this.roomId = roomId; this.userId = userId; this.establishing = false; + this.keyID = ''; + this.sessionKeyExportedString = ''; + this.roomKey = new ArrayBuffer(0); this.readyPromise = new Deferred(); this.readyPromise.then(() => { // Mark as ready @@ -57,7 +72,7 @@ export default class EncryptionRoom { const { E2EKey, e2eKeyId } = subscription; // If this room has a E2EKey, we import it - if (E2EKey) { + if (E2EKey && Encryption.privateKey) { // We're establishing a new room encryption client this.establishing = true; await this.importRoomKey(E2EKey, Encryption.privateKey); @@ -82,25 +97,25 @@ export default class EncryptionRoom { }; // Import roomKey as an AES Decrypt key - importRoomKey = async (E2EKey, privateKey) => { + importRoomKey = async (E2EKey: string, privateKey: string) => { const roomE2EKey = E2EKey.slice(12); const decryptedKey = await SimpleCrypto.RSA.decrypt(roomE2EKey, privateKey); this.sessionKeyExportedString = toString(decryptedKey); - this.keyID = Base64.encode(this.sessionKeyExportedString).slice(0, 12); + this.keyID = Base64.encode(this.sessionKeyExportedString as string).slice(0, 12); // Extract K from Web Crypto Secret Key // K is a base64URL encoded array of bytes // Web Crypto API uses this as a private key to decrypt/encrypt things // Reference: https://www.javadoc.io/doc/com.nimbusds/nimbus-jose-jwt/5.1/com/nimbusds/jose/jwk/OctetSequenceKey.html - const { k } = EJSON.parse(this.sessionKeyExportedString); + const { k } = EJSON.parse(this.sessionKeyExportedString as string); this.roomKey = b64ToBuffer(k); }; // Create a key to a room createRoomKey = async () => { - const key = await SimpleCrypto.utils.randomBytes(16); + const key = (await SimpleCrypto.utils.randomBytes(16)) as Uint8Array; this.roomKey = key; // Web Crypto format of a Secret Key @@ -131,7 +146,7 @@ export default class EncryptionRoom { // Each time you see a encrypted message of a room that you don't have a key // this will be called again and run once in 5 seconds requestRoomKey = debounce( - async e2eKeyId => { + async (e2eKeyId: string) => { await RocketChat.e2eRequestRoomKey(this.roomId, e2eKeyId); }, 5000, @@ -143,22 +158,22 @@ export default class EncryptionRoom { const result = await RocketChat.e2eGetUsersOfRoomWithoutKey(this.roomId); if (result.success) { const { users } = result; - await Promise.all(users.map(user => this.encryptRoomKeyForUser(user))); + await Promise.all(users.map((user: IUser) => this.encryptRoomKeyForUser(user))); } }; // Encrypt the room key to each user in - encryptRoomKeyForUser = async user => { + encryptRoomKeyForUser = async (user: IUser) => { if (user?.e2e?.public_key) { const { public_key: publicKey } = user.e2e; const userKey = await SimpleCrypto.RSA.importKey(EJSON.parse(publicKey)); - const encryptedUserKey = await SimpleCrypto.RSA.encrypt(this.sessionKeyExportedString, userKey); + const encryptedUserKey = await SimpleCrypto.RSA.encrypt(this.sessionKeyExportedString as string, userKey); await RocketChat.e2eUpdateGroupKey(user?._id, this.roomId, this.keyID + encryptedUserKey); } }; // Provide this room key to a user - provideKeyToUser = async keyId => { + provideKeyToUser = async (keyId: string) => { // Don't provide a key if the keyId received // is different than the current one if (this.keyID !== keyId) { @@ -169,16 +184,16 @@ export default class EncryptionRoom { }; // Encrypt text - encryptText = async text => { - text = utf8ToBuffer(text); + encryptText = async (text: string | ArrayBuffer) => { + text = utf8ToBuffer(text as string); const vector = await SimpleCrypto.utils.randomBytes(16); - const data = await SimpleCrypto.AES.encrypt(text, this.roomKey, vector); + const data = await SimpleCrypto.AES.encrypt(text, this.roomKey as ArrayBuffer, vector); return this.keyID + bufferToB64(joinVectorData(vector, data)); }; // Encrypt messages - encrypt = async message => { + encrypt = async (message: IMessage) => { if (!this.ready) { return message; } @@ -207,8 +222,8 @@ export default class EncryptionRoom { }; // Decrypt text - decryptText = async msg => { - msg = b64ToBuffer(msg.slice(12)); + decryptText = async (msg: string | ArrayBuffer) => { + msg = b64ToBuffer(msg.slice(12) as string); const [vector, cipherText] = splitVectorData(msg); const decrypted = await SimpleCrypto.AES.decrypt(cipherText, this.roomKey, vector); @@ -219,7 +234,7 @@ export default class EncryptionRoom { }; // Decrypt messages - decrypt = async message => { + decrypt = async (message: IMessage) => { if (!this.ready) { return message; } @@ -231,7 +246,7 @@ export default class EncryptionRoom { if (t === E2E_MESSAGE_TYPE && e2e !== E2E_STATUS.DONE) { let { msg, tmsg } = message; // Decrypt msg - msg = await this.decryptText(msg); + msg = await this.decryptText(msg as string); // Decrypt tmsg if (tmsg) { diff --git a/app/lib/encryption/utils.js b/app/lib/encryption/utils.ts similarity index 65% rename from app/lib/encryption/utils.js rename to app/lib/encryption/utils.ts index e82e11cf3..97ba23f07 100644 --- a/app/lib/encryption/utils.js +++ b/app/lib/encryption/utils.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-bitwise */ import ByteBuffer from 'bytebuffer'; import SimpleCrypto from 'react-native-simple-crypto'; @@ -7,12 +6,13 @@ import { fromByteArray, toByteArray } from '../../utils/base64-js'; const BASE64URI = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; -export const b64ToBuffer = base64 => toByteArray(base64).buffer; +// @ts-ignore +export const b64ToBuffer = (base64: string): ArrayBuffer => toByteArray(base64).buffer; export const utf8ToBuffer = SimpleCrypto.utils.convertUtf8ToArrayBuffer; -export const bufferToB64 = arrayBuffer => fromByteArray(new Uint8Array(arrayBuffer)); +export const bufferToB64 = (arrayBuffer: ArrayBuffer): string => fromByteArray(new Uint8Array(arrayBuffer)); // ArrayBuffer -> Base64 URI Safe // https://github.com/herrjemand/Base64URL-ArrayBuffer/blob/master/lib/base64url-arraybuffer.js -export const bufferToB64URI = buffer => { +export const bufferToB64URI = (buffer: ArrayBuffer): string => { const uintArray = new Uint8Array(buffer); const len = uintArray.length; let base64 = ''; @@ -33,28 +33,28 @@ export const bufferToB64URI = buffer => { return base64; }; // SimpleCrypto.utils.convertArrayBufferToUtf8 is not working with unicode emoji -export const bufferToUtf8 = buffer => { - const uintArray = new Uint8Array(buffer); +export const bufferToUtf8 = (buffer: ArrayBuffer): string => { + const uintArray = new Uint8Array(buffer) as number[] & Uint8Array; const encodedString = String.fromCharCode.apply(null, uintArray); - const decodedString = decodeURIComponent(escape(encodedString)); - return decodedString; + return decodeURIComponent(escape(encodedString)); }; -export const splitVectorData = text => { +export const splitVectorData = (text: ArrayBuffer): ArrayBuffer[] => { const vector = text.slice(0, 16); const data = text.slice(16); return [vector, data]; }; -export const joinVectorData = (vector, data) => { + +export const joinVectorData = (vector: ArrayBuffer, data: ArrayBuffer): ArrayBufferLike => { const output = new Uint8Array(vector.byteLength + data.byteLength); output.set(new Uint8Array(vector), 0); output.set(new Uint8Array(data), vector.byteLength); return output.buffer; }; -export const toString = thing => { +export const toString = (thing: string | ByteBuffer | Buffer | ArrayBuffer | Uint8Array): string | ByteBuffer => { if (typeof thing === 'string') { return thing; } - // eslint-disable-next-line new-cap + // @ts-ignore return new ByteBuffer.wrap(thing).toString('binary'); }; -export const randomPassword = () => `${random(3)}-${random(3)}-${random(3)}`.toLowerCase(); +export const randomPassword = (): string => `${random(3)}-${random(3)}-${random(3)}`.toLowerCase(); diff --git a/app/utils/base64-js/index.ts b/app/utils/base64-js/index.ts index 71fac91ce..7a77af0f2 100644 --- a/app/utils/base64-js/index.ts +++ b/app/utils/base64-js/index.ts @@ -37,7 +37,7 @@ const getLens = (b64: string) => { }; // base64 is 4/3 + up to two characters of the original data -export const byteLength = (b64: string) => { +export const byteLength = (b64: string): number => { const lens = getLens(b64); const validLen = lens[0]; const placeHoldersLen = lens[1]; @@ -47,7 +47,7 @@ export const byteLength = (b64: string) => { const _byteLength = (b64: string, validLen: number, placeHoldersLen: number) => ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen; -export const toByteArray = (b64: string) => { +export const toByteArray = (b64: string): any[] | Uint8Array => { let tmp; const lens = getLens(b64); const validLen = lens[0]; @@ -106,7 +106,7 @@ const encodeChunk = (uint8: number[] | Uint8Array, start: number, end: number) = return output.join(''); }; -export const fromByteArray = (uint8: number[] | Uint8Array) => { +export const fromByteArray = (uint8: number[] | Uint8Array): string => { let tmp; const len = uint8.length; const extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes diff --git a/app/utils/deferred.js b/app/utils/deferred.js deleted file mode 100644 index 0c0046e55..000000000 --- a/app/utils/deferred.js +++ /dev/null @@ -1,14 +0,0 @@ -// https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred -export default class Deferred { - constructor() { - const promise = new Promise((resolve, reject) => { - this.resolve = resolve; - this.reject = reject; - }); - - promise.resolve = this.resolve; - promise.reject = this.reject; - - return promise; - } -} diff --git a/app/utils/deferred.ts b/app/utils/deferred.ts new file mode 100644 index 000000000..28e4f29c4 --- /dev/null +++ b/app/utils/deferred.ts @@ -0,0 +1,41 @@ +export default class Deferred { + [Symbol.toStringTag]: 'Promise'; + + private promise: Promise; + private _resolve: (value?: unknown) => void; + private _reject: (reason?: any) => void; + + constructor() { + this._resolve = () => {}; + this._reject = () => {}; + this.promise = new Promise((resolve, reject) => { + this._resolve = resolve as (value?: unknown) => void; + this._reject = reject; + }); + } + + public then( + onfulfilled?: ((value: unknown) => TResult1 | PromiseLike) | undefined | null, + onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null + ): Promise { + return this.promise.then(onfulfilled, onrejected); + } + + public catch( + onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null + ): Promise { + return this.promise.catch(onrejected); + } + + public finally(onfinally?: (() => void) | null | undefined): Promise { + return this.promise.finally(onfinally); + } + + public resolve(value?: unknown): void { + this._resolve(value); + } + + public reject(reason?: any): void { + this._reject(reason); + } +} diff --git a/package.json b/package.json index 2bd3831eb..3dae21612 100644 --- a/package.json +++ b/package.json @@ -143,6 +143,7 @@ "@rocket.chat/eslint-config": "^0.4.0", "@storybook/addon-storyshots": "5.3.21", "@storybook/react-native": "5.3.25", + "@types/bytebuffer": "^5.0.43", "@testing-library/react-native": "^9.0.0", "@types/ejson": "^2.1.3", "@types/jest": "^26.0.24", diff --git a/yarn.lock b/yarn.lock index dff2e5e10..69ec7d91c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4200,6 +4200,14 @@ dependencies: "@babel/types" "^7.3.0" +"@types/bytebuffer@^5.0.43": + version "5.0.43" + resolved "https://registry.yarnpkg.com/@types/bytebuffer/-/bytebuffer-5.0.43.tgz#b5259fca1412106bcee0cabfbf7c104846d06738" + integrity sha512-vQnTYvy4LpSojHjKdmg4nXFI1BAiYPvZ/k3ouczZAQnbDprk1xqxJiFmFHyy8y6MuUq3slz5erNMtn6n87uVKw== + dependencies: + "@types/long" "*" + "@types/node" "*" + "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" @@ -4341,6 +4349,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.172.tgz#aad774c28e7bfd7a67de25408e03ee5a8c3d028a" integrity sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw== +"@types/long@*": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" + integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== + "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"