From ccf8a31ae30b40d69716579f8c048bd7e90a0947 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 14 Apr 2021 10:34:48 +1000 Subject: [PATCH] move Attachments and Attachments_test to typescript --- app/attachment_channel.js | 2 +- main.js | 2 +- package.json | 1 + preload.js | 2 +- test/views/last_seen_indicator_view_test.js | 32 -------- .../attachments/attachments.ts | 68 +++++++++-------- ts/components/LightboxGallery.tsx | 1 + ts/components/conversation/Message.tsx | 1 - ts/session/utils/String.ts | 8 ++ .../unit/attachments/attachments_test.ts | 74 +++++++++++++------ ts/types/Attachment.ts | 9 +-- yarn.lock | 5 ++ 12 files changed, 113 insertions(+), 92 deletions(-) delete mode 100644 test/views/last_seen_indicator_view_test.js rename app/attachments.js => ts/attachments/attachments.ts (74%) rename test/app/attachments_test.js => ts/test/session/unit/attachments/attachments_test.ts (76%) diff --git a/app/attachment_channel.js b/app/attachment_channel.js index c5e229751..520162b0f 100644 --- a/app/attachment_channel.js +++ b/app/attachment_channel.js @@ -1,5 +1,5 @@ const electron = require('electron'); -const Attachments = require('./attachments'); +const Attachments = require('../ts/attachments/attachments'); const rimraf = require('rimraf'); const { ipcMain } = electron; diff --git a/main.js b/main.js index 71b551c2b..3de171b70 100644 --- a/main.js +++ b/main.js @@ -64,7 +64,7 @@ const appInstance = config.util.getEnv('NODE_APP_INSTANCE') || 0; // We generally want to pull in our own modules after this point, after the user // data directory has been set. -const attachments = require('./app/attachments'); +const attachments = require('./ts/attachments/attachments'); const attachmentChannel = require('./app/attachment_channel'); const updater = require('./ts/updater/index'); diff --git a/package.json b/package.json index 5ca5adce4..e0839b277 100644 --- a/package.json +++ b/package.json @@ -165,6 +165,7 @@ "@types/semver": "5.5.0", "@types/sinon": "9.0.4", "@types/styled-components": "^5.1.4", + "@types/tmp": "^0.2.0", "@types/uuid": "3.4.4", "arraybuffer-loader": "1.0.3", "asar": "0.14.0", diff --git a/preload.js b/preload.js index 5322257b0..0358ec58b 100644 --- a/preload.js +++ b/preload.js @@ -332,7 +332,7 @@ window.nodeSetImmediate = setImmediate; const Signal = require('./js/modules/signal'); const i18n = require('./js/modules/i18n'); -const Attachments = require('./app/attachments'); +const Attachments = require('./ts/attachments/attachments'); window.Signal = Signal.setup({ Attachments, diff --git a/test/views/last_seen_indicator_view_test.js b/test/views/last_seen_indicator_view_test.js deleted file mode 100644 index c24d1d80d..000000000 --- a/test/views/last_seen_indicator_view_test.js +++ /dev/null @@ -1,32 +0,0 @@ -/* global Whisper */ - -describe('LastSeenIndicatorView', () => { - it('renders provided count', () => { - const view = new Whisper.LastSeenIndicatorView({ count: 10 }); - assert.equal(view.count, 10); - - view.render(); - assert.match(view.$el.html(), /10 Unread Messages/); - }); - - it('renders count of 1', () => { - const view = new Whisper.LastSeenIndicatorView({ count: 1 }); - assert.equal(view.count, 1); - - view.render(); - assert.match(view.$el.html(), /1 Unread Message/); - }); - - it('increments count', () => { - const view = new Whisper.LastSeenIndicatorView({ count: 4 }); - - assert.equal(view.count, 4); - view.render(); - assert.match(view.$el.html(), /4 Unread Messages/); - - view.increment(3); - assert.equal(view.count, 7); - view.render(); - assert.match(view.$el.html(), /7 Unread Messages/); - }); -}); diff --git a/app/attachments.js b/ts/attachments/attachments.ts similarity index 74% rename from app/attachments.js rename to ts/attachments/attachments.ts index a1303e2ea..59b54659c 100644 --- a/app/attachments.js +++ b/ts/attachments/attachments.ts @@ -1,16 +1,19 @@ -const crypto = require('crypto'); -const path = require('path'); - -const pify = require('pify'); -const glob = require('glob'); -const fse = require('fs-extra'); -const toArrayBuffer = require('to-arraybuffer'); -const { map, isArrayBuffer, isString } = require('lodash'); -const AttachmentTS = require('../ts/types/Attachment'); +import crypto from 'crypto'; +import path from 'path'; + +import pify from 'pify'; +import { default as glob } from 'glob'; +import fse from 'fs-extra'; +import toArrayBuffer from 'to-arraybuffer'; +import { isArrayBuffer, isString, map } from 'lodash'; +import { + decryptAttachmentBuffer, + encryptAttachmentBuffer, +} from '../../ts/types/Attachment'; const PATH = 'attachments.noindex'; -exports.getAllAttachments = async userDataPath => { +export const getAllAttachments = async (userDataPath: string) => { const dir = exports.getPath(userDataPath); const pattern = path.join(dir, '**', '*'); @@ -19,7 +22,7 @@ exports.getAllAttachments = async userDataPath => { }; // getPath :: AbsolutePath -> AbsolutePath -exports.getPath = userDataPath => { +export const getPath = (userDataPath: string) => { if (!isString(userDataPath)) { throw new TypeError("'userDataPath' must be a string"); } @@ -27,7 +30,7 @@ exports.getPath = userDataPath => { }; // ensureDirectory :: AbsolutePath -> IO Unit -exports.ensureDirectory = async userDataPath => { +export const ensureDirectory = async (userDataPath: string) => { if (!isString(userDataPath)) { throw new TypeError("'userDataPath' must be a string"); } @@ -37,12 +40,12 @@ exports.ensureDirectory = async userDataPath => { // createReader :: AttachmentsPath -> // RelativePath -> // IO (Promise ArrayBuffer) -exports.createReader = root => { +export const createReader = (root: string) => { if (!isString(root)) { throw new TypeError("'root' must be a path"); } - return async relativePath => { + return async (relativePath: string) => { if (!isString(relativePath)) { throw new TypeError("'relativePath' must be a string"); } @@ -52,9 +55,8 @@ exports.createReader = root => { throw new Error('Invalid relative path'); } const buffer = await fse.readFile(normalized); - const decryptedData = await AttachmentTS.decryptAttachmentBuffer( - toArrayBuffer(buffer) - ); + + const decryptedData = await decryptAttachmentBuffer(toArrayBuffer(buffer)); return decryptedData.buffer; }; @@ -63,12 +65,12 @@ exports.createReader = root => { // createWriterForNew :: AttachmentsPath -> // ArrayBuffer -> // IO (Promise RelativePath) -exports.createWriterForNew = root => { +export const createWriterForNew = (root: string) => { if (!isString(root)) { throw new TypeError("'root' must be a path"); } - return async arrayBuffer => { + return async (arrayBuffer: ArrayBuffer) => { if (!isArrayBuffer(arrayBuffer)) { throw new TypeError("'arrayBuffer' must be an array buffer"); } @@ -85,12 +87,15 @@ exports.createWriterForNew = root => { // createWriter :: AttachmentsPath -> // { data: ArrayBuffer, path: RelativePath } -> // IO (Promise RelativePath) -exports.createWriterForExisting = root => { +export const createWriterForExisting = (root: any) => { if (!isString(root)) { throw new TypeError("'root' must be a path"); } - return async ({ data: arrayBuffer, path: relativePath } = {}) => { + return async ({ + data: arrayBuffer, + path: relativePath, + }: { data?: ArrayBuffer; path?: string } = {}) => { if (!isString(relativePath)) { throw new TypeError("'relativePath' must be a path"); } @@ -106,9 +111,9 @@ exports.createWriterForExisting = root => { } await fse.ensureFile(normalized); - const { - encryptedBufferWithHeader, - } = await AttachmentTS.encryptAttachmentBuffer(arrayBuffer); + const { encryptedBufferWithHeader } = await encryptAttachmentBuffer( + arrayBuffer + ); const buffer = Buffer.from(encryptedBufferWithHeader.buffer); await fse.writeFile(normalized, buffer); @@ -120,12 +125,12 @@ exports.createWriterForExisting = root => { // createDeleter :: AttachmentsPath -> // RelativePath -> // IO Unit -exports.createDeleter = root => { +export const createDeleter = (root: any) => { if (!isString(root)) { throw new TypeError("'root' must be a path"); } - return async relativePath => { + return async (relativePath: any) => { if (!isString(relativePath)) { throw new TypeError("'relativePath' must be a string"); } @@ -139,9 +144,10 @@ exports.createDeleter = root => { }; }; -exports.deleteAll = async ({ userDataPath, attachments }) => { +export const deleteAll = async ({ userDataPath, attachments }: any) => { const deleteFromDisk = exports.createDeleter(exports.getPath(userDataPath)); + // tslint:disable-next-line: one-variable-per-declaration for (let index = 0, max = attachments.length; index < max; index += 1) { const file = attachments[index]; // eslint-disable-next-line no-await-in-loop @@ -152,13 +158,13 @@ exports.deleteAll = async ({ userDataPath, attachments }) => { }; // createName :: Unit -> IO String -exports.createName = () => { +export const createName = () => { const buffer = crypto.randomBytes(32); return buffer.toString('hex'); }; // getRelativePath :: String -> Path -exports.getRelativePath = name => { +export const getRelativePath = (name: any) => { if (!isString(name)) { throw new TypeError("'name' must be a string"); } @@ -168,7 +174,9 @@ exports.getRelativePath = name => { }; // createAbsolutePathGetter :: RootPath -> RelativePath -> AbsolutePath -exports.createAbsolutePathGetter = rootPath => relativePath => { +export const createAbsolutePathGetter = (rootPath: string) => ( + relativePath: string +) => { const absolutePath = path.join(rootPath, relativePath); const normalized = path.normalize(absolutePath); if (!normalized.startsWith(rootPath)) { diff --git a/ts/components/LightboxGallery.tsx b/ts/components/LightboxGallery.tsx index f4f8b80bd..80fe42416 100644 --- a/ts/components/LightboxGallery.tsx +++ b/ts/components/LightboxGallery.tsx @@ -8,6 +8,7 @@ import { Lightbox } from './Lightbox'; import { Message } from './conversation/media-gallery/types/Message'; import { AttachmentType } from '../types/Attachment'; +// tslint:disable-next-line: no-submodule-imports import useKey from 'react-use/lib/useKey'; export interface MediaItemType { diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index 0194513ee..045134a17 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -77,7 +77,6 @@ import { ToastUtils, UserUtils } from '../../session/utils'; import { ConversationController } from '../../session/conversations'; import { MessageRegularProps } from '../../models/messageType'; import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch'; -import src from 'redux-promise-middleware'; // Same as MIN_WIDTH in ImageGrid.tsx const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200; diff --git a/ts/session/utils/String.ts b/ts/session/utils/String.ts index 5cdf4c1fa..9b8b30140 100644 --- a/ts/session/utils/String.ts +++ b/ts/session/utils/String.ts @@ -41,3 +41,11 @@ export const fromBase64ToArray = (d: string) => export const fromArrayBufferToBase64 = (d: BufferType) => decode(d, 'base64'); export const fromUInt8ArrayToBase64 = (d: Uint8Array) => decode(d, 'base64'); + +export const stringToArrayBuffer = (str: string): ArrayBuffer => { + if (typeof str !== 'string') { + throw new TypeError("'string' must be a string"); + } + + return encode(str, 'binary'); +}; diff --git a/test/app/attachments_test.js b/ts/test/session/unit/attachments/attachments_test.ts similarity index 76% rename from test/app/attachments_test.js rename to ts/test/session/unit/attachments/attachments_test.ts index 0901e63f4..c3861068c 100644 --- a/test/app/attachments_test.js +++ b/ts/test/session/unit/attachments/attachments_test.ts @@ -1,27 +1,38 @@ -const fse = require('fs-extra'); -const path = require('path'); -const tmp = require('tmp'); -const { assert } = require('chai'); - -const Attachments = require('../../app/attachments'); -const { - stringToArrayBuffer, -} = require('../../js/modules/string_to_array_buffer'); +import fse from 'fs-extra'; +import path from 'path'; +import tmp from 'tmp'; +import { assert } from 'chai'; + +import * as Attachments from '../../../../attachments/attachments'; +import { stringToArrayBuffer } from '../../../../session/utils/String'; +import { + decryptAttachmentBuffer, + encryptAttachmentBuffer, +} from '../../../../types/Attachment'; +import { TestUtils } from '../../../test-utils'; const PREFIX_LENGTH = 2; const NUM_SEPARATORS = 1; const NAME_LENGTH = 64; const PATH_LENGTH = PREFIX_LENGTH + NUM_SEPARATORS + NAME_LENGTH; +// tslint:disable-next-line: max-func-body-length describe('Attachments', () => { describe('createWriterForNew', () => { - let tempRootDirectory = null; + let tempRootDirectory: any = null; before(() => { tempRootDirectory = tmp.dirSync().name; + TestUtils.stubWindow('textsecure', { + storage: { + get: () => + '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + }, + }); }); after(async () => { await fse.remove(tempRootDirectory); + TestUtils.restoreStubs(); }); it('should write file to disk and return path', async () => { @@ -37,19 +48,30 @@ describe('Attachments', () => { assert.lengthOf(outputPath, PATH_LENGTH); + const outputDecrypted = Buffer.from( + await decryptAttachmentBuffer(output.buffer) + ); + const inputBuffer = Buffer.from(input); - assert.deepEqual(inputBuffer, output); + assert.deepEqual(inputBuffer, outputDecrypted); }); }); describe('createWriterForExisting', () => { - let tempRootDirectory = null; + let tempRootDirectory: any = null; before(() => { tempRootDirectory = tmp.dirSync().name; + TestUtils.stubWindow('textsecure', { + storage: { + get: () => + '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + }, + }); }); after(async () => { await fse.remove(tempRootDirectory); + TestUtils.restoreStubs(); }); it('should write file to disk on given path and return path', async () => { @@ -72,9 +94,11 @@ describe('Attachments', () => { const output = await fse.readFile(path.join(tempDirectory, outputPath)); assert.equal(outputPath, relativePath); - + const outputDecrypted = Buffer.from( + await decryptAttachmentBuffer(output.buffer) + ); const inputBuffer = Buffer.from(input); - assert.deepEqual(inputBuffer, output); + assert.deepEqual(inputBuffer, outputDecrypted); }); it('throws if relative path goes higher than root', async () => { @@ -101,13 +125,20 @@ describe('Attachments', () => { }); describe('createReader', () => { - let tempRootDirectory = null; + let tempRootDirectory: any = null; before(() => { tempRootDirectory = tmp.dirSync().name; + TestUtils.stubWindow('textsecure', { + storage: { + get: () => + '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + }, + }); }); after(async () => { await fse.remove(tempRootDirectory); + TestUtils.restoreStubs(); }); it('should read file from disk', async () => { @@ -122,14 +153,15 @@ describe('Attachments', () => { const fullPath = path.join(tempDirectory, relativePath); const input = stringToArrayBuffer('test string'); - const inputBuffer = Buffer.from(input); + const encryptedInput = await encryptAttachmentBuffer(input); + + const inputBuffer = Buffer.from(encryptedInput.encryptedBufferWithHeader); await fse.ensureFile(fullPath); await fse.writeFile(fullPath, inputBuffer); - const output = await Attachments.createReader(tempDirectory)( + const outputDecrypted = await Attachments.createReader(tempDirectory)( relativePath ); - - assert.deepEqual(input, output); + assert.deepEqual(new Uint8Array(input), new Uint8Array(outputDecrypted)); }); it('throws if relative path goes higher than root', async () => { @@ -152,7 +184,7 @@ describe('Attachments', () => { }); describe('createDeleter', () => { - let tempRootDirectory = null; + let tempRootDirectory: any = null; before(() => { tempRootDirectory = tmp.dirSync().name; }); @@ -178,7 +210,7 @@ describe('Attachments', () => { await fse.writeFile(fullPath, inputBuffer); await Attachments.createDeleter(tempDirectory)(relativePath); - const existsFile = await fse.exists(fullPath); + const existsFile = fse.existsSync(fullPath); assert.isFalse(existsFile); }); diff --git a/ts/types/Attachment.ts b/ts/types/Attachment.ts index 0f02ad541..5f1c11ecf 100644 --- a/ts/types/Attachment.ts +++ b/ts/types/Attachment.ts @@ -10,9 +10,8 @@ import { isVideoTypeSupported, } from '../util/GoogleChrome'; import { LocalizerType } from './Util'; -import { fromHexToArray, toHex } from '../session/utils/String'; +import { fromHexToArray } from '../session/utils/String'; import { getSodium } from '../session/crypto'; -import { fromHex } from 'bytebuffer'; const MAX_WIDTH = 300; const MAX_HEIGHT = MAX_WIDTH * 1.5; @@ -455,8 +454,7 @@ export const encryptAttachmentBuffer = async (bufferIn: ArrayBuffer) => { }; export const decryptAttachmentBuffer = async ( - bufferIn: ArrayBuffer, - key: string = '0c5f7147b6d3239cbb5a418814cee1bfca2df5c94bffddf22ee37eea3ede972b' + bufferIn: ArrayBuffer ): Promise => { if (!isArrayBuffer(bufferIn)) { throw new TypeError("'bufferIn' must be an array buffer"); @@ -469,6 +467,7 @@ export const decryptAttachmentBuffer = async ( const header = new Uint8Array( bufferIn.slice(0, sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES) ); + const encryptedBuffer = new Uint8Array( bufferIn.slice(sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES) ); @@ -492,7 +491,7 @@ export const decryptAttachmentBuffer = async ( return messageTag.message; } } catch (e) { - window.log.warn('Failed to load the file as an encrypted one', e); + window?.log?.warn('Failed to load the file as an encrypted one', e); } return new Uint8Array(); }; diff --git a/yarn.lock b/yarn.lock index 683d41150..0c81c8dd0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -754,6 +754,11 @@ "@types/react-native" "*" csstype "^3.0.2" +"@types/tmp@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.0.tgz#e3f52b4d7397eaa9193592ef3fdd44dc0af4298c" + integrity sha512-flgpHJjntpBAdJD43ShRosQvNC0ME97DCfGvZEDlAThQmnerRXrLbX6YgzRBQCZTthET9eAWFAMaYP0m0Y4HzQ== + "@types/trusted-types@*": version "1.0.4" resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-1.0.4.tgz#922d092c84a776a59acb0bd6785fd82b59b9bad5"