From 6a63e427c8ac0305198cac5fa95fe3ebe9d4f7fc Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 9 Apr 2018 18:38:44 -0400 Subject: [PATCH 001/122] Use `is` instead of Lodash `is*` --- js/modules/types/attachment.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/js/modules/types/attachment.js b/js/modules/types/attachment.js index 06298b284e..25e58bb953 100644 --- a/js/modules/types/attachment.js +++ b/js/modules/types/attachment.js @@ -1,4 +1,4 @@ -const { isFunction, isString } = require('lodash'); +const is = require('@sindresorhus/is'); const MIME = require('./mime'); const { arrayBufferToBlob, blobToArrayBuffer, dataURLToBlob } = require('blob-util'); @@ -76,7 +76,7 @@ const INVALID_CHARACTERS_PATTERN = new RegExp( // which currently doesn’t support async testing: // https://github.com/leebyron/testcheck-js/issues/45 exports._replaceUnicodeOrderOverridesSync = (attachment) => { - if (!isString(attachment.fileName)) { + if (!is.string(attachment.fileName)) { return attachment; } @@ -115,7 +115,7 @@ exports.hasData = attachment => // Attachment -> // IO (Promise Attachment) exports.loadData = (readAttachmentData) => { - if (!isFunction(readAttachmentData)) { + if (!is.function(readAttachmentData)) { throw new TypeError("'readAttachmentData' must be a function"); } @@ -129,7 +129,7 @@ exports.loadData = (readAttachmentData) => { return attachment; } - if (!isString(attachment.path)) { + if (!is.string(attachment.path)) { throw new TypeError("'attachment.path' is required"); } @@ -142,7 +142,7 @@ exports.loadData = (readAttachmentData) => { // Attachment -> // IO Unit exports.deleteData = (deleteAttachmentData) => { - if (!isFunction(deleteAttachmentData)) { + if (!is.function(deleteAttachmentData)) { throw new TypeError("'deleteAttachmentData' must be a function"); } @@ -156,7 +156,7 @@ exports.deleteData = (deleteAttachmentData) => { return; } - if (!isString(attachment.path)) { + if (!is.string(attachment.path)) { throw new TypeError("'attachment.path' is required"); } From df2e6e78640fa11d15f7b9514b04a39e27ed68a3 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 9 Apr 2018 19:24:24 -0400 Subject: [PATCH 002/122] Port `MIME` module to TypeScript --- js/modules/types/attachment.js | 2 +- js/modules/types/mime.js | 10 ---------- preload.js | 2 +- test/modules/types/mime_test.js | 2 +- ts/components/conversation/Quote.tsx | 17 ++++++++--------- ts/styleguide/StyleGuideUtil.ts | 3 +-- ts/types/MIME.ts | 13 +++++++++++++ 7 files changed, 25 insertions(+), 24 deletions(-) delete mode 100644 js/modules/types/mime.js diff --git a/js/modules/types/attachment.js b/js/modules/types/attachment.js index 25e58bb953..cb67b7f1da 100644 --- a/js/modules/types/attachment.js +++ b/js/modules/types/attachment.js @@ -1,6 +1,6 @@ const is = require('@sindresorhus/is'); -const MIME = require('./mime'); +const MIME = require('../../../ts/types/MIME'); const { arrayBufferToBlob, blobToArrayBuffer, dataURLToBlob } = require('blob-util'); const { autoOrientImage } = require('../auto_orient_image'); const { migrateDataToFileSystem } = require('./attachment/migrate_data_to_file_system'); diff --git a/js/modules/types/mime.js b/js/modules/types/mime.js deleted file mode 100644 index b149aead43..0000000000 --- a/js/modules/types/mime.js +++ /dev/null @@ -1,10 +0,0 @@ -exports.isJPEG = mimeType => - mimeType === 'image/jpeg'; - -exports.isVideo = mimeType => - mimeType.startsWith('video/') && mimeType !== 'video/wmv'; - -exports.isImage = mimeType => - mimeType.startsWith('image/') && mimeType !== 'image/tiff'; - -exports.isAudio = mimeType => mimeType.startsWith('audio/'); diff --git a/preload.js b/preload.js index 24eada4524..d35da5a885 100644 --- a/preload.js +++ b/preload.js @@ -191,7 +191,7 @@ window.Signal.Types.Conversation = require('./ts/types/Conversation'); window.Signal.Types.Errors = require('./js/modules/types/errors'); window.Signal.Types.Message = Message; -window.Signal.Types.MIME = require('./js/modules/types/mime'); +window.Signal.Types.MIME = require('./ts/types/MIME'); window.Signal.Types.Settings = require('./js/modules/types/settings'); window.Signal.Views = {}; diff --git a/test/modules/types/mime_test.js b/test/modules/types/mime_test.js index b56f831c46..ab62bc3433 100644 --- a/test/modules/types/mime_test.js +++ b/test/modules/types/mime_test.js @@ -1,6 +1,6 @@ const { assert } = require('chai'); -const MIME = require('../../../js/modules/types/mime'); +const MIME = require('../../../ts/types/MIME'); describe('MIME', () => { diff --git a/ts/components/conversation/Quote.tsx b/ts/components/conversation/Quote.tsx index 3e541f7ce4..417448dd4c 100644 --- a/ts/components/conversation/Quote.tsx +++ b/ts/components/conversation/Quote.tsx @@ -1,8 +1,7 @@ import React from 'react'; import classnames from 'classnames'; -// @ts-ignore -import Mime from '../../../js/modules/types/mime'; +import * as MIME from '../../../ts/types/MIME'; interface Props { @@ -92,17 +91,17 @@ export class Quote extends React.Component { const { contentType, thumbnail } = first; const objectUrl = getObjectUrl(thumbnail); - if (Mime.isVideo(contentType)) { + if (MIME.isVideo(contentType)) { return objectUrl ? this.renderImage(objectUrl, 'play') : this.renderIcon('movie'); } - if (Mime.isImage(contentType)) { + if (MIME.isImage(contentType)) { return objectUrl ? this.renderImage(objectUrl) : this.renderIcon('image'); } - if (Mime.isAudio(contentType)) { + if (MIME.isAudio(contentType)) { return this.renderIcon('microphone'); } @@ -123,16 +122,16 @@ export class Quote extends React.Component { const first = attachments[0]; const { contentType, fileName, isVoiceMessage } = first; - if (Mime.isVideo(contentType)) { + if (MIME.isVideo(contentType)) { return
{i18n('video')}
; } - if (Mime.isImage(contentType)) { + if (MIME.isImage(contentType)) { return
{i18n('photo')}
; } - if (Mime.isAudio(contentType) && isVoiceMessage) { + if (MIME.isAudio(contentType) && isVoiceMessage) { return
{i18n('voiceMessage')}
; } - if (Mime.isAudio(contentType)) { + if (MIME.isAudio(contentType)) { return
{i18n('audio')}
; } diff --git a/ts/styleguide/StyleGuideUtil.ts b/ts/styleguide/StyleGuideUtil.ts index 35149e1964..0d61d39fe0 100644 --- a/ts/styleguide/StyleGuideUtil.ts +++ b/ts/styleguide/StyleGuideUtil.ts @@ -20,8 +20,7 @@ export { BackboneWrapper } from '../components/utility/BackboneWrapper'; import { Quote } from '../components/conversation/Quote'; import * as HTML from '../html'; -// @ts-ignore -import MIME from '../../js/modules/types/mime'; +import * as MIME from '../../ts/types/MIME'; // TypeScript wants two things when you import: // 1) a normal typescript file diff --git a/ts/types/MIME.ts b/ts/types/MIME.ts index ade12d7d50..ed1e3b5fde 100644 --- a/ts/types/MIME.ts +++ b/ts/types/MIME.ts @@ -1 +1,14 @@ export type MIMEType = string & { _mimeTypeBrand: any }; + + +export const isVideo = (value: MIMEType): boolean => + value.startsWith('video/') && value !== 'video/wmv'; + +export const isImage = (value: MIMEType): boolean => + value.startsWith('image/') && value !== 'image/tiff'; + +export const isAudio = (value: MIMEType): boolean => + value.startsWith('audio/'); + +export const isJPEG = (value: MIMEType): boolean => + value === 'image/jpeg'; From 6ff82adf0a8a9d8a2d5281233aec80652a355c95 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 9 Apr 2018 19:24:47 -0400 Subject: [PATCH 003/122] Add `MIME.isImage` and `MIME.isVideo` --- ts/types/MIME.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ts/types/MIME.ts b/ts/types/MIME.ts index ed1e3b5fde..3ac85dbfb9 100644 --- a/ts/types/MIME.ts +++ b/ts/types/MIME.ts @@ -1,14 +1,14 @@ export type MIMEType = string & { _mimeTypeBrand: any }; -export const isVideo = (value: MIMEType): boolean => - value.startsWith('video/') && value !== 'video/wmv'; +export const isJPEG = (value: MIMEType): boolean => + value === 'image/jpeg'; export const isImage = (value: MIMEType): boolean => - value.startsWith('image/') && value !== 'image/tiff'; + value.startsWith('image/'); + +export const isVideo = (value: MIMEType): boolean => + value.startsWith('video/'); export const isAudio = (value: MIMEType): boolean => value.startsWith('audio/'); - -export const isJPEG = (value: MIMEType): boolean => - value === 'image/jpeg'; From 9533c09707d6f03764145b59934cbfb5414db850 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 9 Apr 2018 19:25:48 -0400 Subject: [PATCH 004/122] Add `@types/lodash` --- package.json | 1 + yarn.lock | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/package.json b/package.json index 7c99afda60..36b2eb7546 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ }, "dependencies": { "@sindresorhus/is": "^0.8.0", + "@types/lodash": "^4.14.106", "archiver": "^2.1.1", "blob-util": "^1.3.0", "blueimp-canvas-to-blob": "^3.14.0", diff --git a/yarn.lock b/yarn.lock index 3748ca6bba..ed9d1b1fd8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -36,6 +36,10 @@ dependencies: samsam "1.3.0" +"@types/lodash@^4.14.106": + version "4.14.106" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.106.tgz#6093e9a02aa567ddecfe9afadca89e53e5dce4dd" + "@types/chai@^4.1.2": version "4.1.2" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.2.tgz#f1af664769cfb50af805431c407425ed619daa21" From 47cc701e723a8cc14628cdd4b42a8bc257b598c6 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 9 Apr 2018 19:27:40 -0400 Subject: [PATCH 005/122] Add `GoogleChrome` module Helps us determine which media we can natively display / play back in Electron. --- ts/GoogleChrome.ts | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 ts/GoogleChrome.ts diff --git a/ts/GoogleChrome.ts b/ts/GoogleChrome.ts new file mode 100644 index 0000000000..9d9429ca42 --- /dev/null +++ b/ts/GoogleChrome.ts @@ -0,0 +1,37 @@ +import * as MIME from './types/MIME'; + + +interface MIMETypeSupportMap { + [key: string]: boolean; +} + +// See: https://en.wikipedia.org/wiki/Comparison_of_web_browsers#Image_format_support +const SUPPORTED_IMAGE_MIME_TYPES: MIMETypeSupportMap = { + 'image/bmp': true, + 'image/gif': true, + 'image/jpeg': true, + 'image/svg+xml': true, + 'image/webp': true, + 'image/x-xbitmap': true, + // ICO + 'image/vnd.microsoft.icon': true, + 'image/ico': true, + 'image/icon': true, + 'image/x-icon': true, + // PNG + 'image/apng': true, + 'image/png': true, +}; + +export const isImageTypeSupported = (mimeType: MIME.MIMEType): boolean => + SUPPORTED_IMAGE_MIME_TYPES[mimeType] === true; + +const SUPPORTED_VIDEO_MIME_TYPES: MIMETypeSupportMap = { + 'video/mp4': true, + 'video/ogg': true, + 'video/webm': true, +}; + +// See: https://www.chromium.org/audio-video +export const isVideoTypeSupported = (mimeType: MIME.MIMEType): boolean => + SUPPORTED_VIDEO_MIME_TYPES[mimeType] === true; From fc12353bb838789a014f42cee70458c63dfe8955 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 9 Apr 2018 19:29:38 -0400 Subject: [PATCH 006/122] Add `Attachment.isVisualMedia` --- ts/types/Attachment.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/ts/types/Attachment.ts b/ts/types/Attachment.ts index 1e3d14e65f..4f384d1c3a 100644 --- a/ts/types/Attachment.ts +++ b/ts/types/Attachment.ts @@ -1,3 +1,6 @@ +import is from '@sindresorhus/is'; + +import * as GoogleChrome from '../GoogleChrome'; import { MIMEType } from './MIME'; @@ -16,4 +19,16 @@ export interface Attachment { // key?: ArrayBuffer; // digest?: ArrayBuffer; // flags?: number; -} +}; + +export const isVisualMedia = (attachment: Attachment): boolean => { + const { contentType } = attachment; + + if (is.undefined(contentType)) { + return false; + } + + const isSupportedImageType = GoogleChrome.isImageTypeSupported(contentType); + const isSupportedVideoType = GoogleChrome.isVideoTypeSupported(contentType); + return isSupportedImageType || isSupportedVideoType; +}; From c5352cf26c0c9da956af086e2ab47b72fcadf1c8 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 9 Apr 2018 20:02:07 -0400 Subject: [PATCH 007/122] Separate required from optional `Message` keys --- ts/types/Message.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/ts/types/Message.ts b/ts/types/Message.ts index f539ed1a8d..4afe2dc84f 100644 --- a/ts/types/Message.ts +++ b/ts/types/Message.ts @@ -8,32 +8,39 @@ export type Message export type IncomingMessage = Readonly<{ type: 'incoming'; + // Required attachments: Array; + id: string; + received_at: number; + + // Optional body?: string; decrypted_at?: number; errors?: Array; flags?: number; - id: string; - received_at: number; source?: string; sourceDevice?: number; } & SharedMessageProperties & Message4 & ExpirationTimerUpdate>; export type OutgoingMessage = Readonly<{ type: 'outgoing'; + + // Required attachments: Array; - body?: string; delivered: number; delivered_to: Array; destination: string; // PhoneNumber expirationStartTimestamp: number; - expires_at?: number; - expireTimer?: number; id: string; received_at: number; - recipients?: Array; // Array sent: boolean; sent_to: Array; // Array + + // Optional + body?: string; + expires_at?: number; + expireTimer?: number; + recipients?: Array; // Array synced: boolean; } & SharedMessageProperties & Message4 & ExpirationTimerUpdate>; From 867bece952ca2c30091eacdd219a1620c3f5de70 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 9 Apr 2018 20:05:36 -0400 Subject: [PATCH 008/122] Add `Message.initializeAttachmentMetadata` --- .../initializeAttachmentMetadata_test.ts | 50 +++++++++++++++++++ test/modules/types/message_test.js | 3 ++ .../message/initializeAttachmentMetadata.ts | 20 ++++++++ 3 files changed, 73 insertions(+) create mode 100644 test/modules/types/message/initializeAttachmentMetadata_test.ts create mode 100644 ts/types/message/initializeAttachmentMetadata.ts diff --git a/test/modules/types/message/initializeAttachmentMetadata_test.ts b/test/modules/types/message/initializeAttachmentMetadata_test.ts new file mode 100644 index 0000000000..c6b69d1987 --- /dev/null +++ b/test/modules/types/message/initializeAttachmentMetadata_test.ts @@ -0,0 +1,50 @@ +import 'mocha'; +import { assert } from 'chai'; + +import * as Message from '../../../../ts/types/message/initializeAttachmentMetadata'; +import { IncomingMessage } from '../../../../ts/types/Message'; +import { MIMEType } from '../../../../ts/types/MIME'; +// @ts-ignore +import { stringToArrayBuffer } from '../../../../js/modules/string_to_array_buffer'; + + +describe('Message', () => { + describe('initializeAttachmentMetadata', () => { + it('should handle visual media attachments', async () => { + const input: IncomingMessage = { + type: 'incoming', + conversationId: 'foo', + id: '11111111-1111-1111-1111-111111111111', + timestamp: 1523317140899, + received_at: 1523317140899, + sent_at: 1523317140800, + attachments: [{ + contentType: 'image/jpeg' as MIMEType, + data: stringToArrayBuffer('foo'), + fileName: 'foo.jpg', + size: 1111, + }], + }; + const expected: IncomingMessage = { + type: 'incoming', + conversationId: 'foo', + id: '11111111-1111-1111-1111-111111111111', + timestamp: 1523317140899, + received_at: 1523317140899, + sent_at: 1523317140800, + attachments: [{ + contentType: 'image/jpeg' as MIMEType, + data: stringToArrayBuffer('foo'), + fileName: 'foo.jpg', + size: 1111, + }], + numAttachments: 1, + numVisualMediaAttachments: 1, + numFileAttachments: 0, + }; + + const actual = await Message.initializeAttachmentMetadata(input); + assert.deepEqual(actual, expected); + }); + }); +}); diff --git a/test/modules/types/message_test.js b/test/modules/types/message_test.js index 271ffe825b..42e8c4c6c0 100644 --- a/test/modules/types/message_test.js +++ b/test/modules/types/message_test.js @@ -181,6 +181,9 @@ describe('Message', () => { fileName: 'test\uFFFDfig.exe', size: 1111, }], + numAttachments: 1, + numVisualMediaAttachments: 0, + numFileAttachments: 1, schemaVersion: Message.CURRENT_SCHEMA_VERSION, }; diff --git a/ts/types/message/initializeAttachmentMetadata.ts b/ts/types/message/initializeAttachmentMetadata.ts new file mode 100644 index 0000000000..49d0f35d62 --- /dev/null +++ b/ts/types/message/initializeAttachmentMetadata.ts @@ -0,0 +1,20 @@ +import { partition } from 'lodash'; + +import * as Attachment from '../Attachment'; +import { Message } from '../message'; + + +export const initializeAttachmentMetadata = + async (message: Message): Promise => { + const numAttachments = message.attachments.length; + const [numVisualMediaAttachments, numFileAttachments] = + partition(message.attachments, Attachment.isVisualMedia) + .map((attachments) => attachments.length); + + return { + ...message, + numAttachments, + numVisualMediaAttachments, + numFileAttachments, + }; + }; From 7d11efc50ba6fad77f95632facc97387d1557694 Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Mon, 26 Mar 2018 11:45:25 -0700 Subject: [PATCH 009/122] ConversationView: Add 'View All Media' menu item --- _locales/en/messages.json | 3 +++ background.html | 1 + 2 files changed, 4 insertions(+) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 85251a2b68..983de15252 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -522,6 +522,9 @@ "showSafetyNumber": { "message": "Show safety number" }, + "viewAllMedia": { + "message": "View all media" + }, "verifyHelp": { "message": "If you wish to verify the security of your end-to-end encryption with $name$, compare the numbers above with the numbers on their device.", "placeholders": { diff --git a/background.html b/background.html index f061468a17..af336a2411 100644 --- a/background.html +++ b/background.html @@ -159,6 +159,7 @@