Standalone Protocol Buffers (#2347)
This change introduces a standalone module for our protocol buffers as CommonJS
module incl. TypeScript type definitions.
**Rationale:** In order to exclude voice messages from the media gallery,
I needed to get a reference of `AttachmentPointer.Flags.VOICE_MESSAGE`.
Currently, the only way is to use `textsecure.protobuf` which is only accessible
as a global.
* [x] Add `Attachment.isVoiceMessage` as a way to test standalone
Protocol Buffers.
* [x] Add latest version of `protobufjs`. Leave existing version in place to
keep this change less disruptive and since it’s been stable. Hopefully we
can move over to standalone protobufs over time to improve modularity and
maybe even startup performance.
* [x] Add `yarn build-protobuf` command to compile `SignalService.proto` into
standalone CommonJS module and accompanying TypeScript definitions.
~~Included compiled output for ease of use for other developers.
Can revisit if changes become more frequent.~~
Now built as part of `yarn grunt`.
* [x] Update style guide references and make sure they work!
* [x] ⚠️ Change type definition for `Attachment::file` to include `null` as
that’s apparently a valid value for legacy Android voice messages.
This commit is contained in:
commit
c7a502e2e1
22 changed files with 200 additions and 86 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -24,3 +24,4 @@ test/test.js
|
||||||
|
|
||||||
# React / TypeScript
|
# React / TypeScript
|
||||||
ts/**/*.js
|
ts/**/*.js
|
||||||
|
ts/protobuf/*.d.ts
|
||||||
|
|
|
@ -2,14 +2,19 @@
|
||||||
# supports `.gitignore`: https://github.com/prettier/prettier/issues/2294
|
# supports `.gitignore`: https://github.com/prettier/prettier/issues/2294
|
||||||
|
|
||||||
# Generated files
|
# Generated files
|
||||||
|
config/local-*.json
|
||||||
|
config/local.json
|
||||||
dist/**
|
dist/**
|
||||||
js/components.js
|
js/components.js
|
||||||
js/libsignal-protocol-worker.js
|
js/libsignal-protocol-worker.js
|
||||||
js/libtextsecure.js
|
js/libtextsecure.js
|
||||||
libtextsecure/components.js
|
libtextsecure/components.js
|
||||||
libtextsecure/test/test.js
|
libtextsecure/test/test.js
|
||||||
|
stylesheets/*.css
|
||||||
test/test.js
|
test/test.js
|
||||||
ts/**/*.js
|
ts/**/*.js
|
||||||
|
ts/protobuf/*.d.ts
|
||||||
|
ts/protobuf/*.js
|
||||||
|
|
||||||
# Third-party files
|
# Third-party files
|
||||||
components/**
|
components/**
|
||||||
|
|
|
@ -7,11 +7,10 @@ dist: trusty
|
||||||
install:
|
install:
|
||||||
- yarn install --frozen-lockfile
|
- yarn install --frozen-lockfile
|
||||||
script:
|
script:
|
||||||
- yarn transpile
|
- yarn generate
|
||||||
- yarn lint
|
- yarn lint
|
||||||
- yarn test-node
|
- yarn test-node
|
||||||
- yarn nsp check
|
- yarn nsp check
|
||||||
- yarn generate
|
|
||||||
- yarn prepare-beta-build
|
- yarn prepare-beta-build
|
||||||
- $(yarn bin)/build --config.extraMetadata.environment=$SIGNAL_ENV --config.mac.bundleVersion='$TRAVIS_BUILD_NUMBER' --publish=never
|
- $(yarn bin)/build --config.extraMetadata.environment=$SIGNAL_ENV --config.mac.bundleVersion='$TRAVIS_BUILD_NUMBER' --publish=never
|
||||||
- ./travis.sh
|
- ./travis.sh
|
||||||
|
|
24
Gruntfile.js
24
Gruntfile.js
|
@ -186,17 +186,21 @@ module.exports = function(grunt) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
sass: {
|
dist: {
|
||||||
files: ['./stylesheets/*.scss'],
|
files: ['<%= dist.src %>', '<%= dist.res %>'],
|
||||||
tasks: ['sass'],
|
tasks: ['copy_dist'],
|
||||||
},
|
},
|
||||||
libtextsecure: {
|
libtextsecure: {
|
||||||
files: ['./libtextsecure/*.js', './libtextsecure/storage/*.js'],
|
files: ['./libtextsecure/*.js', './libtextsecure/storage/*.js'],
|
||||||
tasks: ['concat:libtextsecure'],
|
tasks: ['concat:libtextsecure'],
|
||||||
},
|
},
|
||||||
dist: {
|
protobuf: {
|
||||||
files: ['<%= dist.src %>', '<%= dist.res %>'],
|
files: ['./protos/SignalService.proto'],
|
||||||
tasks: ['copy_dist'],
|
tasks: ['exec:build-protobuf'],
|
||||||
|
},
|
||||||
|
sass: {
|
||||||
|
files: ['./stylesheets/*.scss'],
|
||||||
|
tasks: ['sass'],
|
||||||
},
|
},
|
||||||
scripts: {
|
scripts: {
|
||||||
files: ['<%= jshint.files %>'],
|
files: ['<%= jshint.files %>'],
|
||||||
|
@ -216,7 +220,10 @@ module.exports = function(grunt) {
|
||||||
cmd: 'tx pull',
|
cmd: 'tx pull',
|
||||||
},
|
},
|
||||||
transpile: {
|
transpile: {
|
||||||
cmd: 'npm run transpile',
|
cmd: 'yarn transpile',
|
||||||
|
},
|
||||||
|
'build-protobuf': {
|
||||||
|
cmd: 'yarn build-protobuf',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'test-release': {
|
'test-release': {
|
||||||
|
@ -499,10 +506,11 @@ module.exports = function(grunt) {
|
||||||
grunt.registerTask('copy_dist', ['gitinfo', 'copy:res', 'copy:src']);
|
grunt.registerTask('copy_dist', ['gitinfo', 'copy:res', 'copy:src']);
|
||||||
grunt.registerTask('date', ['gitinfo', 'getExpireTime']);
|
grunt.registerTask('date', ['gitinfo', 'getExpireTime']);
|
||||||
grunt.registerTask('default', [
|
grunt.registerTask('default', [
|
||||||
|
'exec:build-protobuf',
|
||||||
|
'exec:transpile',
|
||||||
'concat',
|
'concat',
|
||||||
'copy:deps',
|
'copy:deps',
|
||||||
'sass',
|
'sass',
|
||||||
'date',
|
'date',
|
||||||
'exec:transpile',
|
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,11 +12,10 @@ install:
|
||||||
- yarn install --frozen-lockfile
|
- yarn install --frozen-lockfile
|
||||||
|
|
||||||
build_script:
|
build_script:
|
||||||
- yarn transpile
|
- yarn generate
|
||||||
- yarn lint-windows
|
- yarn lint-windows
|
||||||
- yarn test-node
|
- yarn test-node
|
||||||
- yarn nsp check
|
- yarn nsp check
|
||||||
- yarn generate
|
|
||||||
- node build\grunt.js
|
- node build\grunt.js
|
||||||
- type package.json | findstr /v certificateSubjectName > temp.json
|
- type package.json | findstr /v certificateSubjectName > temp.json
|
||||||
- move temp.json package.json
|
- move temp.json package.json
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
/* global _: false */
|
/* global _: false */
|
||||||
/* global Backbone: false */
|
/* global Backbone: false */
|
||||||
/* global Whisper: false */
|
|
||||||
/* global textsecure: false */
|
|
||||||
/* global ConversationController: false */
|
/* global ConversationController: false */
|
||||||
/* global i18n: false */
|
|
||||||
/* global getAccountManager: false */
|
/* global getAccountManager: false */
|
||||||
|
/* global i18n: false */
|
||||||
|
/* global Signal: false */
|
||||||
|
/* global textsecure: false */
|
||||||
|
/* global Whisper: false */
|
||||||
|
|
||||||
/* eslint-disable more/no-then */
|
/* eslint-disable more/no-then */
|
||||||
|
|
||||||
|
@ -14,8 +16,8 @@
|
||||||
|
|
||||||
window.Whisper = window.Whisper || {};
|
window.Whisper = window.Whisper || {};
|
||||||
|
|
||||||
const { Message: TypedMessage } = window.Signal.Types;
|
const { Message: TypedMessage } = Signal.Types;
|
||||||
const { deleteAttachmentData } = window.Signal.Migrations;
|
const { deleteAttachmentData } = Signal.Migrations;
|
||||||
|
|
||||||
window.Whisper.Message = Backbone.Model.extend({
|
window.Whisper.Message = Backbone.Model.extend({
|
||||||
database: Whisper.Database,
|
database: Whisper.Database,
|
||||||
|
@ -31,9 +33,6 @@
|
||||||
this.on('change:expireTimer', this.setToExpire);
|
this.on('change:expireTimer', this.setToExpire);
|
||||||
this.on('unload', this.unload);
|
this.on('unload', this.unload);
|
||||||
this.setToExpire();
|
this.setToExpire();
|
||||||
|
|
||||||
this.VOICE_FLAG =
|
|
||||||
textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE;
|
|
||||||
},
|
},
|
||||||
idForLogging() {
|
idForLogging() {
|
||||||
return `${this.get('source')}.${this.get('sourceDevice')} ${this.get(
|
return `${this.get('source')}.${this.get('sourceDevice')} ${this.get(
|
||||||
|
@ -246,8 +245,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
return Object.assign({}, attachment, {
|
return Object.assign({}, attachment, {
|
||||||
// eslint-disable-next-line no-bitwise
|
isVoiceMessage: Signal.Types.Attachment.isVoiceMessage(attachment),
|
||||||
isVoiceMessage: Boolean(attachment.flags & this.VOICE_FLAG),
|
|
||||||
thumbnail: thumbnailWithObjectUrl,
|
thumbnail: thumbnailWithObjectUrl,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -179,4 +179,5 @@ exports.deleteData = deleteAttachmentData => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.isVoiceMessage = AttachmentTS.isVoiceMessage;
|
||||||
exports.save = AttachmentTS.save;
|
exports.save = AttachmentTS.save;
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
|
|
||||||
/* global i18n: false */
|
/* global i18n: false */
|
||||||
/* global Signal: false */
|
/* global Signal: false */
|
||||||
/* global textsecure: false */
|
|
||||||
/* global Whisper: false */
|
/* global Whisper: false */
|
||||||
|
|
||||||
// eslint-disable-next-line func-names
|
// eslint-disable-next-line func-names
|
||||||
|
@ -119,20 +118,7 @@
|
||||||
Signal.Backbone.Views.Lightbox.show(this.lightboxView.el);
|
Signal.Backbone.Views.Lightbox.show(this.lightboxView.el);
|
||||||
},
|
},
|
||||||
isVoiceMessage() {
|
isVoiceMessage() {
|
||||||
if (
|
return Signal.Types.Attachment.isVoiceMessage(this.model);
|
||||||
// eslint-disable-next-line no-bitwise
|
|
||||||
this.model.flags &
|
|
||||||
textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Support for android legacy voice messages
|
|
||||||
if (this.isAudio() && this.model.fileName === null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
},
|
||||||
isAudio() {
|
isAudio() {
|
||||||
const { contentType } = this.model;
|
const { contentType } = this.model;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset='utf-8'>
|
<meta charset='utf-8'>
|
||||||
<title>libTextSecure test runner</title>
|
<title>libtextsecure test runner</title>
|
||||||
<link rel="stylesheet" href="../../components/mocha/mocha.css" />
|
<link rel="stylesheet" href="../../components/mocha/mocha.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -15,9 +15,13 @@
|
||||||
"start": "electron .",
|
"start": "electron .",
|
||||||
"grunt": "grunt",
|
"grunt": "grunt",
|
||||||
"icon-gen": "electron-icon-maker --input=images/icon_1024.png --output=./build",
|
"icon-gen": "electron-icon-maker --input=images/icon_1024.png --output=./build",
|
||||||
"generate": "npm run icon-gen && grunt",
|
"generate": "yarn icon-gen && yarn grunt",
|
||||||
"build": "build --config.extraMetadata.environment=$SIGNAL_ENV",
|
"build": "build --config.extraMetadata.environment=$SIGNAL_ENV",
|
||||||
"build-release": "SIGNAL_ENV=production npm run build -- --config.directories.output=release",
|
"build-release": "SIGNAL_ENV=production npm run build -- --config.directories.output=release",
|
||||||
|
"build-module-protobuf": "pbjs --target static-module --wrap commonjs --out ts/protobuf/compiled.js protos/*.proto && pbts --out ts/protobuf/compiled.d.ts ts/protobuf/compiled.js",
|
||||||
|
"clean-module-protobuf": "rm -f ts/protobuf/compiled.d.ts ts/protobuf/compiled.js",
|
||||||
|
"build-protobuf": "yarn build-module-protobuf",
|
||||||
|
"clean-protobuf": "yarn clean-module-protobuf",
|
||||||
"prepare-beta-build": "node prepare_beta_build.js",
|
"prepare-beta-build": "node prepare_beta_build.js",
|
||||||
"prepare-import-build": "node prepare_import_build.js",
|
"prepare-import-build": "node prepare_import_build.js",
|
||||||
"publish-to-apt": "NAME=$npm_package_name VERSION=$npm_package_version ./aptly.sh",
|
"publish-to-apt": "NAME=$npm_package_name VERSION=$npm_package_version ./aptly.sh",
|
||||||
|
@ -69,6 +73,7 @@
|
||||||
"node-fetch": "https://github.com/scottnonnenberg/node-fetch.git#3e5f51e08c647ee5f20c43b15cf2d352d61c36b4",
|
"node-fetch": "https://github.com/scottnonnenberg/node-fetch.git#3e5f51e08c647ee5f20c43b15cf2d352d61c36b4",
|
||||||
"os-locale": "^2.1.0",
|
"os-locale": "^2.1.0",
|
||||||
"pify": "^3.0.0",
|
"pify": "^3.0.0",
|
||||||
|
"protobufjs": "^6.8.6",
|
||||||
"proxy-agent": "^2.1.0",
|
"proxy-agent": "^2.1.0",
|
||||||
"react": "^16.2.0",
|
"react": "^16.2.0",
|
||||||
"react-dom": "^16.2.0",
|
"react-dom": "^16.2.0",
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
package signalservice;
|
|
||||||
|
|
||||||
option java_package = "org.whispersystems.libsignal.protocol";
|
|
||||||
option java_outer_classname = "WhisperProtos";
|
|
||||||
|
|
||||||
message WhisperMessage {
|
|
||||||
optional bytes ephemeralKey = 1;
|
|
||||||
optional uint32 counter = 2;
|
|
||||||
optional uint32 previousCounter = 3;
|
|
||||||
optional bytes ciphertext = 4; // PushMessageContent
|
|
||||||
}
|
|
||||||
|
|
||||||
message PreKeyWhisperMessage {
|
|
||||||
optional uint32 registrationId = 5;
|
|
||||||
optional uint32 preKeyId = 1;
|
|
||||||
optional uint32 signedPreKeyId = 6;
|
|
||||||
optional bytes baseKey = 2;
|
|
||||||
optional bytes identityKey = 3;
|
|
||||||
optional bytes message = 4; // WhisperMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
message KeyExchangeMessage {
|
|
||||||
optional uint32 id = 1;
|
|
||||||
optional bytes baseKey = 2;
|
|
||||||
optional bytes ephemeralKey = 3;
|
|
||||||
optional bytes identityKey = 4;
|
|
||||||
optional bytes baseKeySignature = 5;
|
|
||||||
}
|
|
|
@ -597,7 +597,7 @@ const outgoing = new Whisper.Message({
|
||||||
sent_at: Date.now() - 15000,
|
sent_at: Date.now() - 15000,
|
||||||
attachments: [
|
attachments: [
|
||||||
{
|
{
|
||||||
flags: textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE,
|
flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE,
|
||||||
data: util.mp3,
|
data: util.mp3,
|
||||||
fileName: 'agnus_dei.mp3',
|
fileName: 'agnus_dei.mp3',
|
||||||
contentType: 'audio/mp3',
|
contentType: 'audio/mp3',
|
||||||
|
|
|
@ -572,7 +572,7 @@ const outgoing = new Whisper.Message({
|
||||||
attachments: [
|
attachments: [
|
||||||
{
|
{
|
||||||
// proposed as of afternoon of 4/6 in Quoted Replies group
|
// proposed as of afternoon of 4/6 in Quoted Replies group
|
||||||
flags: textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE,
|
flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE,
|
||||||
contentType: 'audio/mp3',
|
contentType: 'audio/mp3',
|
||||||
fileName: 'agnus_dei.mp4',
|
fileName: 'agnus_dei.mp4',
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,7 @@ import moment from 'moment';
|
||||||
import formatFileSize from 'filesize';
|
import formatFileSize from 'filesize';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
fileName?: string;
|
fileName?: string | null;
|
||||||
fileSize?: number;
|
fileSize?: number;
|
||||||
i18n: (key: string, values?: Array<string>) => string;
|
i18n: (key: string, values?: Array<string>) => string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
|
|
3
ts/protobuf/README.md
Normal file
3
ts/protobuf/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Protocol Buffers
|
||||||
|
|
||||||
|
Placeholder directory for Protocol Buffers compiled to JavaScript / TypeScript.
|
3
ts/protobuf/index.ts
Normal file
3
ts/protobuf/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import { signalservice as SignalService } from './compiled';
|
||||||
|
|
||||||
|
export { SignalService };
|
|
@ -19,7 +19,9 @@ export { BackboneWrapper } from '../components/utility/BackboneWrapper';
|
||||||
import { Quote } from '../components/conversation/Quote';
|
import { Quote } from '../components/conversation/Quote';
|
||||||
import * as HTML from '../html';
|
import * as HTML from '../html';
|
||||||
|
|
||||||
|
import * as Attachment from '../../ts/types/Attachment';
|
||||||
import * as MIME from '../../ts/types/MIME';
|
import * as MIME from '../../ts/types/MIME';
|
||||||
|
import { SignalService } from '../../ts/protobuf';
|
||||||
|
|
||||||
// TypeScript wants two things when you import:
|
// TypeScript wants two things when you import:
|
||||||
// 1) a normal typescript file
|
// 1) a normal typescript file
|
||||||
|
@ -125,10 +127,12 @@ parent.ReactDOM = ReactDOM;
|
||||||
|
|
||||||
parent.Signal.HTML = HTML;
|
parent.Signal.HTML = HTML;
|
||||||
parent.Signal.Types.MIME = MIME;
|
parent.Signal.Types.MIME = MIME;
|
||||||
|
parent.Signal.Types.Attachment = Attachment;
|
||||||
parent.Signal.Components = {
|
parent.Signal.Components = {
|
||||||
Quote,
|
Quote,
|
||||||
};
|
};
|
||||||
parent.Signal.Util = Util;
|
parent.Signal.Util = Util;
|
||||||
|
parent.SignalService = SignalService;
|
||||||
parent.filesize = filesize;
|
parent.filesize = filesize;
|
||||||
|
|
||||||
parent.ConversationController._initialFetchComplete = true;
|
parent.ConversationController._initialFetchComplete = true;
|
||||||
|
|
|
@ -5,7 +5,8 @@ import 'mocha';
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
|
|
||||||
import * as Attachment from '../../types/Attachment';
|
import * as Attachment from '../../types/Attachment';
|
||||||
import { MIMEType } from '../../types/MIME';
|
import * as MIME from '../../types/MIME';
|
||||||
|
import { SignalService } from '../../protobuf';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { stringToArrayBuffer } from '../../../js/modules/string_to_array_buffer';
|
import { stringToArrayBuffer } from '../../../js/modules/string_to_array_buffer';
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ describe('Attachment', () => {
|
||||||
it('should return file extension from content type', () => {
|
it('should return file extension from content type', () => {
|
||||||
const input: Attachment.Attachment = {
|
const input: Attachment.Attachment = {
|
||||||
data: stringToArrayBuffer('foo'),
|
data: stringToArrayBuffer('foo'),
|
||||||
contentType: 'image/gif' as MIMEType,
|
contentType: MIME.IMAGE_GIF,
|
||||||
};
|
};
|
||||||
assert.strictEqual(Attachment.getFileExtension(input), 'gif');
|
assert.strictEqual(Attachment.getFileExtension(input), 'gif');
|
||||||
});
|
});
|
||||||
|
@ -22,7 +23,7 @@ describe('Attachment', () => {
|
||||||
it('should return file extension for QuickTime videos', () => {
|
it('should return file extension for QuickTime videos', () => {
|
||||||
const input: Attachment.Attachment = {
|
const input: Attachment.Attachment = {
|
||||||
data: stringToArrayBuffer('foo'),
|
data: stringToArrayBuffer('foo'),
|
||||||
contentType: 'video/quicktime' as MIMEType,
|
contentType: MIME.VIDEO_QUICKTIME,
|
||||||
};
|
};
|
||||||
assert.strictEqual(Attachment.getFileExtension(input), 'mov');
|
assert.strictEqual(Attachment.getFileExtension(input), 'mov');
|
||||||
});
|
});
|
||||||
|
@ -34,7 +35,7 @@ describe('Attachment', () => {
|
||||||
const attachment: Attachment.Attachment = {
|
const attachment: Attachment.Attachment = {
|
||||||
fileName: 'funny-cat.mov',
|
fileName: 'funny-cat.mov',
|
||||||
data: stringToArrayBuffer('foo'),
|
data: stringToArrayBuffer('foo'),
|
||||||
contentType: 'video/quicktime' as MIMEType,
|
contentType: MIME.VIDEO_QUICKTIME,
|
||||||
};
|
};
|
||||||
const actual = Attachment.getSuggestedFilename({ attachment });
|
const actual = Attachment.getSuggestedFilename({ attachment });
|
||||||
const expected = 'funny-cat.mov';
|
const expected = 'funny-cat.mov';
|
||||||
|
@ -45,7 +46,7 @@ describe('Attachment', () => {
|
||||||
it('should generate a filename based on timestamp', () => {
|
it('should generate a filename based on timestamp', () => {
|
||||||
const attachment: Attachment.Attachment = {
|
const attachment: Attachment.Attachment = {
|
||||||
data: stringToArrayBuffer('foo'),
|
data: stringToArrayBuffer('foo'),
|
||||||
contentType: 'video/quicktime' as MIMEType,
|
contentType: MIME.VIDEO_QUICKTIME,
|
||||||
};
|
};
|
||||||
const timestamp = new Date(new Date(0).getTimezoneOffset() * 60 * 1000);
|
const timestamp = new Date(new Date(0).getTimezoneOffset() * 60 * 1000);
|
||||||
const actual = Attachment.getSuggestedFilename({
|
const actual = Attachment.getSuggestedFilename({
|
||||||
|
@ -57,4 +58,34 @@ describe('Attachment', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('isVoiceMessage', () => {
|
||||||
|
it('should return true for voice message attachment', () => {
|
||||||
|
const attachment: Attachment.Attachment = {
|
||||||
|
fileName: 'Voice Message.aac',
|
||||||
|
flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE,
|
||||||
|
data: stringToArrayBuffer('voice message'),
|
||||||
|
contentType: MIME.AUDIO_AAC,
|
||||||
|
};
|
||||||
|
assert.isTrue(Attachment.isVoiceMessage(attachment));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true for legacy Android voice message attachment', () => {
|
||||||
|
const attachment: Attachment.Attachment = {
|
||||||
|
fileName: null,
|
||||||
|
data: stringToArrayBuffer('voice message'),
|
||||||
|
contentType: MIME.AUDIO_MP3,
|
||||||
|
};
|
||||||
|
assert.isTrue(Attachment.isVoiceMessage(attachment));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for other attachments', () => {
|
||||||
|
const attachment: Attachment.Attachment = {
|
||||||
|
fileName: 'foo.gif',
|
||||||
|
data: stringToArrayBuffer('foo'),
|
||||||
|
contentType: MIME.IMAGE_GIF,
|
||||||
|
};
|
||||||
|
assert.isFalse(Attachment.isVoiceMessage(attachment));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { assert } from 'chai';
|
||||||
|
|
||||||
import * as Message from '../../../../ts/types/message/initializeAttachmentMetadata';
|
import * as Message from '../../../../ts/types/message/initializeAttachmentMetadata';
|
||||||
import { IncomingMessage } from '../../../../ts/types/Message';
|
import { IncomingMessage } from '../../../../ts/types/Message';
|
||||||
import { MIMEType } from '../../../../ts/types/MIME';
|
import * as MIME from '../../../../ts/types/MIME';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { stringToArrayBuffer } from '../../../../js/modules/string_to_array_buffer';
|
import { stringToArrayBuffer } from '../../../../js/modules/string_to_array_buffer';
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ describe('Message', () => {
|
||||||
sent_at: 1523317140800,
|
sent_at: 1523317140800,
|
||||||
attachments: [
|
attachments: [
|
||||||
{
|
{
|
||||||
contentType: 'image/jpeg' as MIMEType,
|
contentType: MIME.IMAGE_JPEG,
|
||||||
data: stringToArrayBuffer('foo'),
|
data: stringToArrayBuffer('foo'),
|
||||||
fileName: 'foo.jpg',
|
fileName: 'foo.jpg',
|
||||||
size: 1111,
|
size: 1111,
|
||||||
|
@ -35,7 +35,7 @@ describe('Message', () => {
|
||||||
sent_at: 1523317140800,
|
sent_at: 1523317140800,
|
||||||
attachments: [
|
attachments: [
|
||||||
{
|
{
|
||||||
contentType: 'image/jpeg' as MIMEType,
|
contentType: MIME.IMAGE_JPEG,
|
||||||
data: stringToArrayBuffer('foo'),
|
data: stringToArrayBuffer('foo'),
|
||||||
fileName: 'foo.jpg',
|
fileName: 'foo.jpg',
|
||||||
size: 1111,
|
size: 1111,
|
||||||
|
|
|
@ -2,13 +2,15 @@ import is from '@sindresorhus/is';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
import * as GoogleChrome from '../util/GoogleChrome';
|
import * as GoogleChrome from '../util/GoogleChrome';
|
||||||
import { saveURLAsFile } from '../util/saveURLAsFile';
|
import * as MIME from './MIME';
|
||||||
import { arrayBufferToObjectURL } from '../util/arrayBufferToObjectURL';
|
import { arrayBufferToObjectURL } from '../util/arrayBufferToObjectURL';
|
||||||
import { MIMEType } from './MIME';
|
import { saveURLAsFile } from '../util/saveURLAsFile';
|
||||||
|
import { SignalService } from '../protobuf';
|
||||||
|
|
||||||
export type Attachment = {
|
export type Attachment = {
|
||||||
fileName?: string;
|
fileName?: string | null;
|
||||||
contentType?: MIMEType;
|
flags?: SignalService.AttachmentPointer.Flags;
|
||||||
|
contentType?: MIME.MIMEType;
|
||||||
size?: number;
|
size?: number;
|
||||||
data: ArrayBuffer;
|
data: ArrayBuffer;
|
||||||
|
|
||||||
|
@ -20,15 +22,12 @@ export type Attachment = {
|
||||||
// thumbnail?: ArrayBuffer;
|
// thumbnail?: ArrayBuffer;
|
||||||
// key?: ArrayBuffer;
|
// key?: ArrayBuffer;
|
||||||
// digest?: ArrayBuffer;
|
// digest?: ArrayBuffer;
|
||||||
// flags?: number;
|
|
||||||
} & Partial<AttachmentSchemaVersion3>;
|
} & Partial<AttachmentSchemaVersion3>;
|
||||||
|
|
||||||
interface AttachmentSchemaVersion3 {
|
interface AttachmentSchemaVersion3 {
|
||||||
path: string;
|
path: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SAVE_CONTENT_TYPE = 'application/octet-stream' as MIMEType;
|
|
||||||
|
|
||||||
export const isVisualMedia = (attachment: Attachment): boolean => {
|
export const isVisualMedia = (attachment: Attachment): boolean => {
|
||||||
const { contentType } = attachment;
|
const { contentType } = attachment;
|
||||||
|
|
||||||
|
@ -41,6 +40,26 @@ export const isVisualMedia = (attachment: Attachment): boolean => {
|
||||||
return isSupportedImageType || isSupportedVideoType;
|
return isSupportedImageType || isSupportedVideoType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isVoiceMessage = (attachment: Attachment): boolean => {
|
||||||
|
const flag = SignalService.AttachmentPointer.Flags.VOICE_MESSAGE;
|
||||||
|
const hasFlag =
|
||||||
|
// tslint:disable-next-line no-bitwise
|
||||||
|
!is.undefined(attachment.flags) && (attachment.flags & flag) === flag;
|
||||||
|
if (hasFlag) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLegacyAndroidVoiceMessage =
|
||||||
|
!is.undefined(attachment.contentType) &&
|
||||||
|
MIME.isAudio(attachment.contentType) &&
|
||||||
|
attachment.fileName === null;
|
||||||
|
if (isLegacyAndroidVoiceMessage) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
export const save = ({
|
export const save = ({
|
||||||
attachment,
|
attachment,
|
||||||
document,
|
document,
|
||||||
|
@ -57,7 +76,7 @@ export const save = ({
|
||||||
? getAbsolutePath(attachment.path)
|
? getAbsolutePath(attachment.path)
|
||||||
: arrayBufferToObjectURL({
|
: arrayBufferToObjectURL({
|
||||||
data: attachment.data,
|
data: attachment.data,
|
||||||
type: SAVE_CONTENT_TYPE,
|
type: MIME.APPLICATION_OCTET_STREAM,
|
||||||
});
|
});
|
||||||
const filename = getSuggestedFilename({ attachment, timestamp });
|
const filename = getSuggestedFilename({ attachment, timestamp });
|
||||||
saveURLAsFile({ url, filename, document });
|
saveURLAsFile({ url, filename, document });
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
export type MIMEType = string & { _mimeTypeBrand: any };
|
export type MIMEType = string & { _mimeTypeBrand: any };
|
||||||
|
|
||||||
|
export const APPLICATION_OCTET_STREAM = 'application/octet-stream' as MIMEType;
|
||||||
|
export const AUDIO_AAC = 'audio/aac' as MIMEType;
|
||||||
|
export const AUDIO_MP3 = 'audio/mp3' as MIMEType;
|
||||||
|
export const IMAGE_GIF = 'image/gif' as MIMEType;
|
||||||
|
export const IMAGE_JPEG = 'image/jpeg' as MIMEType;
|
||||||
|
export const VIDEO_QUICKTIME = 'video/quicktime' as MIMEType;
|
||||||
|
|
||||||
export const isJPEG = (value: MIMEType): boolean => value === 'image/jpeg';
|
export const isJPEG = (value: MIMEType): boolean => value === 'image/jpeg';
|
||||||
export const isImage = (value: MIMEType): boolean => value.startsWith('image/');
|
export const isImage = (value: MIMEType): boolean => value.startsWith('image/');
|
||||||
export const isVideo = (value: MIMEType): boolean => value.startsWith('video/');
|
export const isVideo = (value: MIMEType): boolean => value.startsWith('video/');
|
||||||
|
|
73
yarn.lock
73
yarn.lock
|
@ -22,6 +22,49 @@
|
||||||
"7zip-bin-mac" "~1.0.1"
|
"7zip-bin-mac" "~1.0.1"
|
||||||
"7zip-bin-win" "~2.2.0"
|
"7zip-bin-win" "~2.2.0"
|
||||||
|
|
||||||
|
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
|
||||||
|
|
||||||
|
"@protobufjs/base64@^1.1.2":
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735"
|
||||||
|
|
||||||
|
"@protobufjs/codegen@^2.0.4":
|
||||||
|
version "2.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb"
|
||||||
|
|
||||||
|
"@protobufjs/eventemitter@^1.1.0":
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70"
|
||||||
|
|
||||||
|
"@protobufjs/fetch@^1.1.0":
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45"
|
||||||
|
dependencies:
|
||||||
|
"@protobufjs/aspromise" "^1.1.1"
|
||||||
|
"@protobufjs/inquire" "^1.1.0"
|
||||||
|
|
||||||
|
"@protobufjs/float@^1.0.2":
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1"
|
||||||
|
|
||||||
|
"@protobufjs/inquire@^1.1.0":
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089"
|
||||||
|
|
||||||
|
"@protobufjs/path@^1.1.2":
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d"
|
||||||
|
|
||||||
|
"@protobufjs/pool@^1.1.0":
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54"
|
||||||
|
|
||||||
|
"@protobufjs/utf8@^1.1.0":
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
|
||||||
|
|
||||||
"@sindresorhus/is@^0.7.0":
|
"@sindresorhus/is@^0.7.0":
|
||||||
version "0.7.0"
|
version "0.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
|
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
|
||||||
|
@ -56,6 +99,10 @@
|
||||||
version "4.14.106"
|
version "4.14.106"
|
||||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.106.tgz#6093e9a02aa567ddecfe9afadca89e53e5dce4dd"
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.106.tgz#6093e9a02aa567ddecfe9afadca89e53e5dce4dd"
|
||||||
|
|
||||||
|
"@types/long@^3.0.32":
|
||||||
|
version "3.0.32"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/long/-/long-3.0.32.tgz#f4e5af31e9e9b196d8e5fca8a5e2e20aa3d60b69"
|
||||||
|
|
||||||
"@types/mocha@^5.0.0":
|
"@types/mocha@^5.0.0":
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.0.0.tgz#a3014921991066193f6c8e47290d4d598dfd19e6"
|
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.0.0.tgz#a3014921991066193f6c8e47290d4d598dfd19e6"
|
||||||
|
@ -68,6 +115,10 @@
|
||||||
version "8.9.4"
|
version "8.9.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.9.4.tgz#dfd327582a06c114eb6e0441fa3d6fab35edad48"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.9.4.tgz#dfd327582a06c114eb6e0441fa3d6fab35edad48"
|
||||||
|
|
||||||
|
"@types/node@^8.9.4":
|
||||||
|
version "8.10.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.12.tgz#dcb66f6de39074a296534bd1a256a3c6a1c8f5b5"
|
||||||
|
|
||||||
"@types/qs@^6.5.1":
|
"@types/qs@^6.5.1":
|
||||||
version "6.5.1"
|
version "6.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.5.1.tgz#a38f69c62528d56ba7bd1f91335a8004988d72f7"
|
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.5.1.tgz#a38f69c62528d56ba7bd1f91335a8004988d72f7"
|
||||||
|
@ -5326,6 +5377,10 @@ lolex@^2.2.0, lolex@^2.3.2:
|
||||||
version "2.3.2"
|
version "2.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.3.2.tgz#85f9450425103bf9e7a60668ea25dc43274ca807"
|
resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.3.2.tgz#85f9450425103bf9e7a60668ea25dc43274ca807"
|
||||||
|
|
||||||
|
long@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
|
||||||
|
|
||||||
longest-streak@^2.0.1:
|
longest-streak@^2.0.1:
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.2.tgz#2421b6ba939a443bb9ffebf596585a50b4c38e2e"
|
resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.2.tgz#2421b6ba939a443bb9ffebf596585a50b4c38e2e"
|
||||||
|
@ -6947,6 +7002,24 @@ prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.6.1:
|
||||||
loose-envify "^1.3.1"
|
loose-envify "^1.3.1"
|
||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
|
|
||||||
|
protobufjs@^6.8.6:
|
||||||
|
version "6.8.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.8.6.tgz#ce3cf4fff9625b62966c455fc4c15e4331a11ca2"
|
||||||
|
dependencies:
|
||||||
|
"@protobufjs/aspromise" "^1.1.2"
|
||||||
|
"@protobufjs/base64" "^1.1.2"
|
||||||
|
"@protobufjs/codegen" "^2.0.4"
|
||||||
|
"@protobufjs/eventemitter" "^1.1.0"
|
||||||
|
"@protobufjs/fetch" "^1.1.0"
|
||||||
|
"@protobufjs/float" "^1.0.2"
|
||||||
|
"@protobufjs/inquire" "^1.1.0"
|
||||||
|
"@protobufjs/path" "^1.1.2"
|
||||||
|
"@protobufjs/pool" "^1.1.0"
|
||||||
|
"@protobufjs/utf8" "^1.1.0"
|
||||||
|
"@types/long" "^3.0.32"
|
||||||
|
"@types/node" "^8.9.4"
|
||||||
|
long "^4.0.0"
|
||||||
|
|
||||||
proxy-addr@~2.0.3:
|
proxy-addr@~2.0.3:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341"
|
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue