From 8a4f062120baffc9e7ac718886da69fd49549bba Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 11:19:58 -0400 Subject: [PATCH 01/23] Add `MIME.APPLICATION_OCTET_STREAM` --- ts/types/Attachment.ts | 8 +++----- ts/types/MIME.ts | 2 ++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ts/types/Attachment.ts b/ts/types/Attachment.ts index d37b2f3e34..fccc9962d4 100644 --- a/ts/types/Attachment.ts +++ b/ts/types/Attachment.ts @@ -4,11 +4,11 @@ import moment from 'moment'; import * as GoogleChrome from '../util/GoogleChrome'; import { saveURLAsFile } from '../util/saveURLAsFile'; import { arrayBufferToObjectURL } from '../util/arrayBufferToObjectURL'; -import { MIMEType } from './MIME'; +import * as MIME from './MIME'; export type Attachment = { fileName?: string; - contentType?: MIMEType; + contentType?: MIME.MIMEType; size?: number; data: ArrayBuffer; @@ -27,8 +27,6 @@ interface AttachmentSchemaVersion3 { path: string; } -const SAVE_CONTENT_TYPE = 'application/octet-stream' as MIMEType; - export const isVisualMedia = (attachment: Attachment): boolean => { const { contentType } = attachment; @@ -57,7 +55,7 @@ export const save = ({ ? getAbsolutePath(attachment.path) : arrayBufferToObjectURL({ data: attachment.data, - type: SAVE_CONTENT_TYPE, + type: MIME.APPLICATION_OCTET_STREAM, }); const filename = getSuggestedFilename({ attachment, timestamp }); saveURLAsFile({ url, filename, document }); diff --git a/ts/types/MIME.ts b/ts/types/MIME.ts index 620df41c96..43ed14efb0 100644 --- a/ts/types/MIME.ts +++ b/ts/types/MIME.ts @@ -4,3 +4,5 @@ export const isJPEG = (value: MIMEType): boolean => value === 'image/jpeg'; export const isImage = (value: MIMEType): boolean => value.startsWith('image/'); export const isVideo = (value: MIMEType): boolean => value.startsWith('video/'); export const isAudio = (value: MIMEType): boolean => value.startsWith('audio/'); + +export const APPLICATION_OCTET_STREAM = 'application/octet-stream' as MIMEType; From fa4c3fda2b9a80fe1cd84d6ba8589ea79e1c4a89 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 11:24:40 -0400 Subject: [PATCH 02/23] Extract common MIME types --- ts/test/types/Attachment_test.ts | 10 +++++----- .../types/message/initializeAttachmentMetadata_test.ts | 6 +++--- ts/types/MIME.ts | 6 +++++- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/ts/test/types/Attachment_test.ts b/ts/test/types/Attachment_test.ts index 343373d4ba..cdb671eed6 100644 --- a/ts/test/types/Attachment_test.ts +++ b/ts/test/types/Attachment_test.ts @@ -5,7 +5,7 @@ import 'mocha'; import { assert } from 'chai'; import * as Attachment from '../../types/Attachment'; -import { MIMEType } from '../../types/MIME'; +import * as MIME from '../../types/MIME'; // @ts-ignore import { stringToArrayBuffer } from '../../../js/modules/string_to_array_buffer'; @@ -14,7 +14,7 @@ describe('Attachment', () => { it('should return file extension from content type', () => { const input: Attachment.Attachment = { data: stringToArrayBuffer('foo'), - contentType: 'image/gif' as MIMEType, + contentType: MIME.IMAGE_GIF, }; assert.strictEqual(Attachment.getFileExtension(input), 'gif'); }); @@ -22,7 +22,7 @@ describe('Attachment', () => { it('should return file extension for QuickTime videos', () => { const input: Attachment.Attachment = { data: stringToArrayBuffer('foo'), - contentType: 'video/quicktime' as MIMEType, + contentType: MIME.VIDEO_QUICKTIME, }; assert.strictEqual(Attachment.getFileExtension(input), 'mov'); }); @@ -34,7 +34,7 @@ describe('Attachment', () => { const attachment: Attachment.Attachment = { fileName: 'funny-cat.mov', data: stringToArrayBuffer('foo'), - contentType: 'video/quicktime' as MIMEType, + contentType: MIME.VIDEO_QUICKTIME, }; const actual = Attachment.getSuggestedFilename({ attachment }); const expected = 'funny-cat.mov'; @@ -45,7 +45,7 @@ describe('Attachment', () => { it('should generate a filename based on timestamp', () => { const attachment: Attachment.Attachment = { data: stringToArrayBuffer('foo'), - contentType: 'video/quicktime' as MIMEType, + contentType: MIME.VIDEO_QUICKTIME, }; const timestamp = new Date(new Date(0).getTimezoneOffset() * 60 * 1000); const actual = Attachment.getSuggestedFilename({ diff --git a/ts/test/types/message/initializeAttachmentMetadata_test.ts b/ts/test/types/message/initializeAttachmentMetadata_test.ts index d76618f280..9c87215f3b 100644 --- a/ts/test/types/message/initializeAttachmentMetadata_test.ts +++ b/ts/test/types/message/initializeAttachmentMetadata_test.ts @@ -3,7 +3,7 @@ import { assert } from 'chai'; import * as Message from '../../../../ts/types/message/initializeAttachmentMetadata'; import { IncomingMessage } from '../../../../ts/types/Message'; -import { MIMEType } from '../../../../ts/types/MIME'; +import * as MIME from '../../../../ts/types/MIME'; // @ts-ignore import { stringToArrayBuffer } from '../../../../js/modules/string_to_array_buffer'; @@ -19,7 +19,7 @@ describe('Message', () => { sent_at: 1523317140800, attachments: [ { - contentType: 'image/jpeg' as MIMEType, + contentType: MIME.IMAGE_JPEG, data: stringToArrayBuffer('foo'), fileName: 'foo.jpg', size: 1111, @@ -35,7 +35,7 @@ describe('Message', () => { sent_at: 1523317140800, attachments: [ { - contentType: 'image/jpeg' as MIMEType, + contentType: MIME.IMAGE_JPEG, data: stringToArrayBuffer('foo'), fileName: 'foo.jpg', size: 1111, diff --git a/ts/types/MIME.ts b/ts/types/MIME.ts index 43ed14efb0..60dd011558 100644 --- a/ts/types/MIME.ts +++ b/ts/types/MIME.ts @@ -1,8 +1,12 @@ export type MIMEType = string & { _mimeTypeBrand: any }; +export const APPLICATION_OCTET_STREAM = 'application/octet-stream' 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 isImage = (value: MIMEType): boolean => value.startsWith('image/'); export const isVideo = (value: MIMEType): boolean => value.startsWith('video/'); export const isAudio = (value: MIMEType): boolean => value.startsWith('audio/'); -export const APPLICATION_OCTET_STREAM = 'application/octet-stream' as MIMEType; From 58399549c31f8a670afaba2ea1fb34839eff101f Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 11:58:04 -0400 Subject: [PATCH 03/23] Add `protobufjs` dependency --- package.json | 1 + yarn.lock | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/package.json b/package.json index 583113b7cf..f1be43651a 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "node-fetch": "https://github.com/scottnonnenberg/node-fetch.git#3e5f51e08c647ee5f20c43b15cf2d352d61c36b4", "os-locale": "^2.1.0", "pify": "^3.0.0", + "protobufjs": "^6.8.6", "proxy-agent": "^2.1.0", "react": "^16.2.0", "react-dom": "^16.2.0", diff --git a/yarn.lock b/yarn.lock index 47540d5bbb..86b435defe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22,6 +22,49 @@ "7zip-bin-mac" "~1.0.1" "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": version "0.7.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" @@ -56,6 +99,10 @@ version "4.14.106" 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": version "5.0.0" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.0.0.tgz#a3014921991066193f6c8e47290d4d598dfd19e6" @@ -68,6 +115,10 @@ version "8.9.4" 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": version "6.5.1" 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" 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: version "2.0.2" 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" 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: version "2.0.3" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341" From 2d95745d56b003d2968d74dd162e7d87d4d5ab29 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 11:59:04 -0400 Subject: [PATCH 04/23] Add `yarn build-protobuf` command --- .prettierignore | 1 + package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/.prettierignore b/.prettierignore index ed8fd952d7..548f196286 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,6 +10,7 @@ libtextsecure/components.js libtextsecure/test/test.js test/test.js ts/**/*.js +ts/protobuf/** # Third-party files components/** diff --git a/package.json b/package.json index f1be43651a..1af087b47d 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "generate": "npm run icon-gen && grunt", "build": "build --config.extraMetadata.environment=$SIGNAL_ENV", "build-release": "SIGNAL_ENV=production npm run build -- --config.directories.output=release", + "build-protobuf": "pbjs --target static-module --wrap commonjs --out ts/protobuf/SignalService.js protos/SignalService.proto && pbts --out ts/protobuf/SignalService.d.ts ts/protobuf/SignalService.js", "prepare-beta-build": "node prepare_beta_build.js", "prepare-import-build": "node prepare_import_build.js", "publish-to-apt": "NAME=$npm_package_name VERSION=$npm_package_version ./aptly.sh", From 5789b3eb75017b0578c78f9095acbbfa0f982393 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 12:40:59 -0400 Subject: [PATCH 05/23] Add `Attachment.isVoiceMessage` --- .../media-gallery/DocumentListItem.tsx | 2 +- ts/test/types/Attachment_test.ts | 31 +++++++++++++++++++ ts/types/Attachment.ts | 25 +++++++++++++-- ts/types/MIME.ts | 3 +- 4 files changed, 57 insertions(+), 4 deletions(-) diff --git a/ts/components/conversation/media-gallery/DocumentListItem.tsx b/ts/components/conversation/media-gallery/DocumentListItem.tsx index b5d6394038..b8e83bcec9 100644 --- a/ts/components/conversation/media-gallery/DocumentListItem.tsx +++ b/ts/components/conversation/media-gallery/DocumentListItem.tsx @@ -4,7 +4,7 @@ import moment from 'moment'; import formatFileSize from 'filesize'; interface Props { - fileName?: string; + fileName?: string | null; fileSize?: number; i18n: (key: string, values?: Array) => string; onClick?: () => void; diff --git a/ts/test/types/Attachment_test.ts b/ts/test/types/Attachment_test.ts index cdb671eed6..49e1621e3a 100644 --- a/ts/test/types/Attachment_test.ts +++ b/ts/test/types/Attachment_test.ts @@ -6,6 +6,7 @@ import { assert } from 'chai'; import * as Attachment from '../../types/Attachment'; import * as MIME from '../../types/MIME'; +import { signalservice as SignalService } from '../../protobuf/SignalService'; // @ts-ignore import { stringToArrayBuffer } from '../../../js/modules/string_to_array_buffer'; @@ -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)); + }); + }); }); diff --git a/ts/types/Attachment.ts b/ts/types/Attachment.ts index fccc9962d4..6ed7f54234 100644 --- a/ts/types/Attachment.ts +++ b/ts/types/Attachment.ts @@ -5,9 +5,11 @@ import * as GoogleChrome from '../util/GoogleChrome'; import { saveURLAsFile } from '../util/saveURLAsFile'; import { arrayBufferToObjectURL } from '../util/arrayBufferToObjectURL'; import * as MIME from './MIME'; +import { signalservice as SignalService } from '../protobuf/SignalService'; export type Attachment = { - fileName?: string; + fileName?: string | null; + flags?: SignalService.AttachmentPointer.Flags; contentType?: MIME.MIMEType; size?: number; data: ArrayBuffer; @@ -20,7 +22,6 @@ export type Attachment = { // thumbnail?: ArrayBuffer; // key?: ArrayBuffer; // digest?: ArrayBuffer; - // flags?: number; } & Partial; interface AttachmentSchemaVersion3 { @@ -39,6 +40,26 @@ export const isVisualMedia = (attachment: Attachment): boolean => { 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 = ({ attachment, document, diff --git a/ts/types/MIME.ts b/ts/types/MIME.ts index 60dd011558..c2cc66ac58 100644 --- a/ts/types/MIME.ts +++ b/ts/types/MIME.ts @@ -1,6 +1,8 @@ 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; @@ -9,4 +11,3 @@ export const isJPEG = (value: MIMEType): boolean => value === 'image/jpeg'; export const isImage = (value: MIMEType): boolean => value.startsWith('image/'); export const isVideo = (value: MIMEType): boolean => value.startsWith('video/'); export const isAudio = (value: MIMEType): boolean => value.startsWith('audio/'); - From a86056630f0303c400680a96f705832443345241 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 12:41:15 -0400 Subject: [PATCH 06/23] Use `Attachment.isVoiceMessage` --- js/modules/types/attachment.js | 1 + js/views/attachment_view.js | 16 +--------------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/js/modules/types/attachment.js b/js/modules/types/attachment.js index 5010f87fc4..1420152654 100644 --- a/js/modules/types/attachment.js +++ b/js/modules/types/attachment.js @@ -179,4 +179,5 @@ exports.deleteData = deleteAttachmentData => { }; }; +exports.isVoiceMessage = AttachmentTS.isVoiceMessage; exports.save = AttachmentTS.save; diff --git a/js/views/attachment_view.js b/js/views/attachment_view.js index 798039e7c4..15654e82bd 100644 --- a/js/views/attachment_view.js +++ b/js/views/attachment_view.js @@ -5,7 +5,6 @@ /* global i18n: false */ /* global Signal: false */ -/* global textsecure: false */ /* global Whisper: false */ // eslint-disable-next-line func-names @@ -119,20 +118,7 @@ Signal.Backbone.Views.Lightbox.show(this.lightboxView.el); }, isVoiceMessage() { - if ( - // 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; + return Signal.Types.Attachment.isVoiceMessage(this.model); }, isAudio() { const { contentType } = this.model; From a0ddab669be21f9849a645a9314ae440c2cf0465 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 12:50:11 -0400 Subject: [PATCH 07/23] Organize `global`s --- js/models/messages.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/js/models/messages.js b/js/models/messages.js index 67431eea68..5851cef6b2 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -1,10 +1,12 @@ /* global _: false */ /* global Backbone: false */ -/* global Whisper: false */ -/* global textsecure: false */ + /* global ConversationController: false */ -/* global i18n: false */ /* global getAccountManager: false */ +/* global i18n: false */ +/* global Signal: false */ +/* global textsecure: false */ +/* global Whisper: false */ /* eslint-disable more/no-then */ @@ -14,8 +16,8 @@ window.Whisper = window.Whisper || {}; - const { Message: TypedMessage } = window.Signal.Types; - const { deleteAttachmentData } = window.Signal.Migrations; + const { Message: TypedMessage } = Signal.Types; + const { deleteAttachmentData } = Signal.Migrations; window.Whisper.Message = Backbone.Model.extend({ database: Whisper.Database, From 86346ee239082364c2c93404dd34b8dd1f2bb616 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 12:51:45 -0400 Subject: [PATCH 08/23] Expose `Signal.Types.Attachment` to style guide --- ts/styleguide/StyleGuideUtil.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ts/styleguide/StyleGuideUtil.ts b/ts/styleguide/StyleGuideUtil.ts index ecba629d8f..3e13fa0e40 100644 --- a/ts/styleguide/StyleGuideUtil.ts +++ b/ts/styleguide/StyleGuideUtil.ts @@ -19,6 +19,7 @@ export { BackboneWrapper } from '../components/utility/BackboneWrapper'; import { Quote } from '../components/conversation/Quote'; import * as HTML from '../html'; +import * as Attachment from '../../ts/types/Attachment'; import * as MIME from '../../ts/types/MIME'; // TypeScript wants two things when you import: @@ -125,6 +126,7 @@ parent.ReactDOM = ReactDOM; parent.Signal.HTML = HTML; parent.Signal.Types.MIME = MIME; +parent.Signal.Types.Attachment = Attachment; parent.Signal.Components = { Quote, }; From 6589247750f8cef7d75210395174d8796292d166 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 12:52:07 -0400 Subject: [PATCH 09/23] Expose `SignalService` to style guide --- ts/styleguide/StyleGuideUtil.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ts/styleguide/StyleGuideUtil.ts b/ts/styleguide/StyleGuideUtil.ts index 3e13fa0e40..6a0fc160b5 100644 --- a/ts/styleguide/StyleGuideUtil.ts +++ b/ts/styleguide/StyleGuideUtil.ts @@ -21,6 +21,7 @@ import * as HTML from '../html'; import * as Attachment from '../../ts/types/Attachment'; import * as MIME from '../../ts/types/MIME'; +import { signalservice as SignalService } from '../../ts/protobuf/SignalService'; // TypeScript wants two things when you import: // 1) a normal typescript file @@ -131,6 +132,7 @@ parent.Signal.Components = { Quote, }; parent.Signal.Util = Util; +parent.SignalService = SignalService; parent.filesize = filesize; parent.ConversationController._initialFetchComplete = true; From 38f3ad6518905dd078921e1abb109c604086a5f0 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 12:52:27 -0400 Subject: [PATCH 10/23] Use `SignalService` vs `textsecure` for protobuf --- js/models/messages.js | 6 +----- ts/components/conversation/Message.md | 2 +- ts/components/conversation/Quote.md | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/js/models/messages.js b/js/models/messages.js index 5851cef6b2..032428d215 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -33,9 +33,6 @@ this.on('change:expireTimer', this.setToExpire); this.on('unload', this.unload); this.setToExpire(); - - this.VOICE_FLAG = - textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE; }, idForLogging() { return `${this.get('source')}.${this.get('sourceDevice')} ${this.get( @@ -248,8 +245,7 @@ }); return Object.assign({}, attachment, { - // eslint-disable-next-line no-bitwise - isVoiceMessage: Boolean(attachment.flags & this.VOICE_FLAG), + isVoiceMessage: Signal.Types.Attachment.isVoiceMessage(attachment), thumbnail: thumbnailWithObjectUrl, }); }, diff --git a/ts/components/conversation/Message.md b/ts/components/conversation/Message.md index afefcd5078..1b01c3cba1 100644 --- a/ts/components/conversation/Message.md +++ b/ts/components/conversation/Message.md @@ -597,7 +597,7 @@ const outgoing = new Whisper.Message({ sent_at: Date.now() - 15000, attachments: [ { - flags: textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE, + flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE, data: util.mp3, fileName: 'agnus_dei.mp3', contentType: 'audio/mp3', diff --git a/ts/components/conversation/Quote.md b/ts/components/conversation/Quote.md index 7cdf0b514a..5ab2f58966 100644 --- a/ts/components/conversation/Quote.md +++ b/ts/components/conversation/Quote.md @@ -572,7 +572,7 @@ const outgoing = new Whisper.Message({ attachments: [ { // 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', fileName: 'agnus_dei.mp4', }, From ce8db96069c2f2c6bf01b286b475ac6a55277a8e Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 14:10:49 -0400 Subject: [PATCH 11/23] Gruntfile: :abc:-ize `watch` targets --- Gruntfile.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 72d0aa4e85..f83efef73b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -186,17 +186,17 @@ module.exports = function(grunt) { }, }, watch: { - sass: { - files: ['./stylesheets/*.scss'], - tasks: ['sass'], + dist: { + files: ['<%= dist.src %>', '<%= dist.res %>'], + tasks: ['copy_dist'], }, libtextsecure: { files: ['./libtextsecure/*.js', './libtextsecure/storage/*.js'], tasks: ['concat:libtextsecure'], }, - dist: { - files: ['<%= dist.src %>', '<%= dist.res %>'], - tasks: ['copy_dist'], + sass: { + files: ['./stylesheets/*.scss'], + tasks: ['sass'], }, scripts: { files: ['<%= jshint.files %>'], From c5d6e4a60267a252910b8187d657b949c9c03ea6 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 14:11:12 -0400 Subject: [PATCH 12/23] Gruntfile: Remove dependency on `npm` --- Gruntfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index f83efef73b..bc43e1bb1d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -216,7 +216,7 @@ module.exports = function(grunt) { cmd: 'tx pull', }, transpile: { - cmd: 'npm run transpile', + cmd: 'yarn transpile', }, }, 'test-release': { From f88ebdf8b7a5ae9a0eb77addcbc06f32ddf06efe Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 14:12:38 -0400 Subject: [PATCH 13/23] Compile protocol buffers as part of `yarn grunt dev` --- .gitignore | 1 + .prettierignore | 3 ++- Gruntfile.js | 8 ++++++++ ts/protobuf/README.md | 3 +++ 4 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 ts/protobuf/README.md diff --git a/.gitignore b/.gitignore index 4cb12784f5..1eb148df02 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ test/test.js # React / TypeScript ts/**/*.js +ts/protobuf/*.d.ts diff --git a/.prettierignore b/.prettierignore index 548f196286..f2403751a0 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,7 +10,8 @@ libtextsecure/components.js libtextsecure/test/test.js test/test.js ts/**/*.js -ts/protobuf/** +ts/protobuf/*.js +ts/protobuf/*.d.ts # Third-party files components/** diff --git a/Gruntfile.js b/Gruntfile.js index bc43e1bb1d..1ba0900262 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -194,6 +194,10 @@ module.exports = function(grunt) { files: ['./libtextsecure/*.js', './libtextsecure/storage/*.js'], tasks: ['concat:libtextsecure'], }, + protobuf: { + files: ['./protos/SignalService.proto'], + tasks: ['exec:build-protobuf'], + }, sass: { files: ['./stylesheets/*.scss'], tasks: ['sass'], @@ -218,6 +222,9 @@ module.exports = function(grunt) { transpile: { cmd: 'yarn transpile', }, + 'build-protobuf': { + cmd: 'yarn build-protobuf', + }, }, 'test-release': { osx: { @@ -503,6 +510,7 @@ module.exports = function(grunt) { 'copy:deps', 'sass', 'date', + 'exec:build-protobuf', 'exec:transpile', ]); }; diff --git a/ts/protobuf/README.md b/ts/protobuf/README.md new file mode 100644 index 0000000000..57cdbc733d --- /dev/null +++ b/ts/protobuf/README.md @@ -0,0 +1,3 @@ +# Protocol Buffers + +Placeholder directory for Protocol Buffers compiled to JavaScript / TypeScript. From 15759f0db51be940836ae3864fb875ff60a9931a Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 14:15:06 -0400 Subject: [PATCH 14/23] Normalize package naming --- ts/protobuf/index.ts | 3 +++ ts/styleguide/StyleGuideUtil.ts | 2 +- ts/test/types/Attachment_test.ts | 2 +- ts/types/Attachment.ts | 6 +++--- 4 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 ts/protobuf/index.ts diff --git a/ts/protobuf/index.ts b/ts/protobuf/index.ts new file mode 100644 index 0000000000..b1b81f2ce9 --- /dev/null +++ b/ts/protobuf/index.ts @@ -0,0 +1,3 @@ +import { signalservice as SignalService } from './SignalService'; + +export { SignalService }; diff --git a/ts/styleguide/StyleGuideUtil.ts b/ts/styleguide/StyleGuideUtil.ts index 6a0fc160b5..1b8167c699 100644 --- a/ts/styleguide/StyleGuideUtil.ts +++ b/ts/styleguide/StyleGuideUtil.ts @@ -21,7 +21,7 @@ import * as HTML from '../html'; import * as Attachment from '../../ts/types/Attachment'; import * as MIME from '../../ts/types/MIME'; -import { signalservice as SignalService } from '../../ts/protobuf/SignalService'; +import { SignalService } from '../../ts/protobuf'; // TypeScript wants two things when you import: // 1) a normal typescript file diff --git a/ts/test/types/Attachment_test.ts b/ts/test/types/Attachment_test.ts index 49e1621e3a..14b0cdcee7 100644 --- a/ts/test/types/Attachment_test.ts +++ b/ts/test/types/Attachment_test.ts @@ -6,7 +6,7 @@ import { assert } from 'chai'; import * as Attachment from '../../types/Attachment'; import * as MIME from '../../types/MIME'; -import { signalservice as SignalService } from '../../protobuf/SignalService'; +import { SignalService } from '../../protobuf'; // @ts-ignore import { stringToArrayBuffer } from '../../../js/modules/string_to_array_buffer'; diff --git a/ts/types/Attachment.ts b/ts/types/Attachment.ts index 6ed7f54234..e4e58ffe84 100644 --- a/ts/types/Attachment.ts +++ b/ts/types/Attachment.ts @@ -2,10 +2,10 @@ import is from '@sindresorhus/is'; import moment from 'moment'; import * as GoogleChrome from '../util/GoogleChrome'; -import { saveURLAsFile } from '../util/saveURLAsFile'; -import { arrayBufferToObjectURL } from '../util/arrayBufferToObjectURL'; import * as MIME from './MIME'; -import { signalservice as SignalService } from '../protobuf/SignalService'; +import { arrayBufferToObjectURL } from '../util/arrayBufferToObjectURL'; +import { saveURLAsFile } from '../util/saveURLAsFile'; +import { SignalService } from '../protobuf'; export type Attachment = { fileName?: string | null; From cb09da0f635f03ed1a6a5113e0a3dcd7d2f09cc2 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 15:53:20 -0400 Subject: [PATCH 15/23] Run all build steps in CI --- .prettierignore | 5 ++++- .travis.yml | 3 +-- appveyor.yml | 3 +-- package.json | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.prettierignore b/.prettierignore index f2403751a0..c7b5de7190 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,16 +2,19 @@ # supports `.gitignore`: https://github.com/prettier/prettier/issues/2294 # Generated files +config/local-*.json +config/local.json dist/** js/components.js js/libsignal-protocol-worker.js js/libtextsecure.js libtextsecure/components.js libtextsecure/test/test.js +stylesheets/*.css test/test.js ts/**/*.js -ts/protobuf/*.js ts/protobuf/*.d.ts +ts/protobuf/*.js # Third-party files components/** diff --git a/.travis.yml b/.travis.yml index 96730e76c2..b56c17dd63 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,11 +7,10 @@ dist: trusty install: - yarn install --frozen-lockfile script: - - yarn transpile + - yarn generate - yarn lint - yarn test-node - yarn nsp check - - yarn generate - yarn prepare-beta-build - $(yarn bin)/build --config.extraMetadata.environment=$SIGNAL_ENV --config.mac.bundleVersion='$TRAVIS_BUILD_NUMBER' --publish=never - ./travis.sh diff --git a/appveyor.yml b/appveyor.yml index 52dac757c0..1ba2bac72a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -12,11 +12,10 @@ install: - yarn install --frozen-lockfile build_script: - - yarn transpile + - yarn generate - yarn lint-windows - yarn test-node - yarn nsp check - - yarn generate - node build\grunt.js - type package.json | findstr /v certificateSubjectName > temp.json - move temp.json package.json diff --git a/package.json b/package.json index 1af087b47d..ea9ec7bec3 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "start": "electron .", "grunt": "grunt", "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-release": "SIGNAL_ENV=production npm run build -- --config.directories.output=release", "build-protobuf": "pbjs --target static-module --wrap commonjs --out ts/protobuf/SignalService.js protos/SignalService.proto && pbts --out ts/protobuf/SignalService.d.ts ts/protobuf/SignalService.js", From 55f85465c6b2273326aa950b277ca4117e67cbd6 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 16:15:54 -0400 Subject: [PATCH 16/23] Remove generated protobuf as part of `yarn clean-transpile` --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ea9ec7bec3..2a7fc89556 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "tslint": "tslint --config tslint.json --format stylish --project .", "format": "prettier --write \"*.{css,js,json,md,scss,ts,tsx}\" \"./**/*.{css,js,json,md,scss,ts,tsx}\"", "transpile": "tsc", - "clean-transpile": "rimraf ts/**/*.js ts/*.js", + "clean-transpile": "rimraf ts/**/*.js ts/*.js ts/protobuf/*.d.ts", "open-coverage": "open coverage/lcov-report/index.html", "styleguide": "styleguidist server" }, From 086c64deb8e302523ed3f1d89028f47c6ebacd82 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 16:43:10 -0400 Subject: [PATCH 17/23] Add `yarn clean-protobuf` command --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 2a7fc89556..3ef776ce1e 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "build": "build --config.extraMetadata.environment=$SIGNAL_ENV", "build-release": "SIGNAL_ENV=production npm run build -- --config.directories.output=release", "build-protobuf": "pbjs --target static-module --wrap commonjs --out ts/protobuf/SignalService.js protos/SignalService.proto && pbts --out ts/protobuf/SignalService.d.ts ts/protobuf/SignalService.js", + "clean-protobuf": "rimraf ts/protobuf/*.d.ts", "prepare-beta-build": "node prepare_beta_build.js", "prepare-import-build": "node prepare_import_build.js", "publish-to-apt": "NAME=$npm_package_name VERSION=$npm_package_version ./aptly.sh", @@ -34,7 +35,7 @@ "tslint": "tslint --config tslint.json --format stylish --project .", "format": "prettier --write \"*.{css,js,json,md,scss,ts,tsx}\" \"./**/*.{css,js,json,md,scss,ts,tsx}\"", "transpile": "tsc", - "clean-transpile": "rimraf ts/**/*.js ts/*.js ts/protobuf/*.d.ts", + "clean-transpile": "rimraf ts/**/*.js ts/*.js", "open-coverage": "open coverage/lcov-report/index.html", "styleguide": "styleguidist server" }, From 2810f264618c995a21cb419448bdc17f83051961 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 18:58:19 -0400 Subject: [PATCH 18/23] Remove unused `WhisperTextProtocol.proto` --- protos/WhisperTextProtocol.proto | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 protos/WhisperTextProtocol.proto diff --git a/protos/WhisperTextProtocol.proto b/protos/WhisperTextProtocol.proto deleted file mode 100644 index 852d5640b5..0000000000 --- a/protos/WhisperTextProtocol.proto +++ /dev/null @@ -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; -} From 4e39882afc98783b4864f8a226b4bd5121b0a26e Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 19:05:49 -0400 Subject: [PATCH 19/23] Compile protocol buffers first --- Gruntfile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 1ba0900262..4b23b8bdad 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -506,11 +506,11 @@ module.exports = function(grunt) { grunt.registerTask('copy_dist', ['gitinfo', 'copy:res', 'copy:src']); grunt.registerTask('date', ['gitinfo', 'getExpireTime']); grunt.registerTask('default', [ + 'exec:build-protobuf', + 'exec:transpile', 'concat', 'copy:deps', 'sass', 'date', - 'exec:build-protobuf', - 'exec:transpile', ]); }; From c8e1c29b16c8337a986a66bbd522885b0f2bae66 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 19:07:15 -0400 Subject: [PATCH 20/23] Improve naming --- libtextsecure/test/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libtextsecure/test/index.html b/libtextsecure/test/index.html index ca81c39ad7..acb9304363 100644 --- a/libtextsecure/test/index.html +++ b/libtextsecure/test/index.html @@ -2,7 +2,7 @@ - libTextSecure test runner + libtextsecure test runner From daa4a3d63f1d13882715facf9a5257c4e334abd5 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 19:08:43 -0400 Subject: [PATCH 21/23] Compile all protocol buffers --- package.json | 6 ++++-- ts/protobuf/index.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 3ef776ce1e..3213678bac 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,10 @@ "generate": "yarn icon-gen && yarn grunt", "build": "build --config.extraMetadata.environment=$SIGNAL_ENV", "build-release": "SIGNAL_ENV=production npm run build -- --config.directories.output=release", - "build-protobuf": "pbjs --target static-module --wrap commonjs --out ts/protobuf/SignalService.js protos/SignalService.proto && pbts --out ts/protobuf/SignalService.d.ts ts/protobuf/SignalService.js", - "clean-protobuf": "rimraf ts/protobuf/*.d.ts", + "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-import-build": "node prepare_import_build.js", "publish-to-apt": "NAME=$npm_package_name VERSION=$npm_package_version ./aptly.sh", diff --git a/ts/protobuf/index.ts b/ts/protobuf/index.ts index b1b81f2ce9..c760e2caec 100644 --- a/ts/protobuf/index.ts +++ b/ts/protobuf/index.ts @@ -1,3 +1,3 @@ -import { signalservice as SignalService } from './SignalService'; +import { signalservice as SignalService } from './compiled'; export { SignalService }; From 60077d0b7b9923a73b990239373ab2f6c779d7dd Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 19:11:56 -0400 Subject: [PATCH 22/23] Use protocol buffer module for `libtextsecure` --- Gruntfile.js | 1 - bower.json | 8 +- components/protobuf/dist/ProtoBuf.js | 4335 -------------------------- libtextsecure/protobufs.js | 42 - libtextsecure/test/index.html | 1 - package.json | 6 +- preload.js | 5 +- 7 files changed, 9 insertions(+), 4389 deletions(-) delete mode 100644 components/protobuf/dist/ProtoBuf.js delete mode 100644 libtextsecure/protobufs.js diff --git a/Gruntfile.js b/Gruntfile.js index 4b23b8bdad..5ed7cc4932 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -56,7 +56,6 @@ module.exports = function(grunt) { 'libtextsecure/storage/user.js', 'libtextsecure/storage/groups.js', 'libtextsecure/storage/unprocessed.js', - 'libtextsecure/protobufs.js', 'libtextsecure/helpers.js', 'libtextsecure/stringview.js', 'libtextsecure/event_target.js', diff --git a/bower.json b/bower.json index d9fbcbff04..22f96ec8f8 100644 --- a/bower.json +++ b/bower.json @@ -8,7 +8,6 @@ "jquery": "liliakai/jquery#2.1.1-ajax-nativetransport", "underscore": "~1.7.0", "backbone": "~1.1.2", - "protobuf": "~3.8.0", "mustache": "~0.8.2", "qrcode": "https://github.com/davidshimjs/qrcodejs.git#1c78ccd71", "indexeddb-backbonejs-adapter": "*", @@ -33,9 +32,6 @@ "bytebuffer": [ "dist/ByteBufferAB.js" ], - "protobuf": [ - "dist/ProtoBuf.js" - ], "mustache": [ "mustache.js" ], @@ -89,7 +85,6 @@ "jquery", "long", "bytebuffer", - "protobuf", "mustache", "underscore", "backbone", @@ -102,8 +97,7 @@ ], "libtextsecure": [ "long", - "bytebuffer", - "protobuf" + "bytebuffer" ] } } diff --git a/components/protobuf/dist/ProtoBuf.js b/components/protobuf/dist/ProtoBuf.js deleted file mode 100644 index 0e12340a7f..0000000000 --- a/components/protobuf/dist/ProtoBuf.js +++ /dev/null @@ -1,4335 +0,0 @@ -/* - Copyright 2013 Daniel Wirtz - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -/** - * @license ProtoBuf.js (c) 2013 Daniel Wirtz - * Released under the Apache License, Version 2.0 - * see: https://github.com/dcodeIO/ProtoBuf.js for details - */ -(function(global) { - "use strict"; - - function init(ByteBuffer) { - - /** - * The ProtoBuf namespace. - * @exports ProtoBuf - * @namespace - * @expose - */ - var ProtoBuf = {}; - - /** - * ProtoBuf.js version. - * @type {string} - * @const - * @expose - */ - ProtoBuf.VERSION = "3.8.2"; - - /** - * Wire types. - * @type {Object.} - * @const - * @expose - */ - ProtoBuf.WIRE_TYPES = {}; - - /** - * Varint wire type. - * @type {number} - * @expose - */ - ProtoBuf.WIRE_TYPES.VARINT = 0; - - /** - * Fixed 64 bits wire type. - * @type {number} - * @const - * @expose - */ - ProtoBuf.WIRE_TYPES.BITS64 = 1; - - /** - * Length delimited wire type. - * @type {number} - * @const - * @expose - */ - ProtoBuf.WIRE_TYPES.LDELIM = 2; - - /** - * Start group wire type. - * @type {number} - * @const - * @expose - */ - ProtoBuf.WIRE_TYPES.STARTGROUP = 3; - - /** - * End group wire type. - * @type {number} - * @const - * @expose - */ - ProtoBuf.WIRE_TYPES.ENDGROUP = 4; - - /** - * Fixed 32 bits wire type. - * @type {number} - * @const - * @expose - */ - ProtoBuf.WIRE_TYPES.BITS32 = 5; - - /** - * Packable wire types. - * @type {!Array.} - * @const - * @expose - */ - ProtoBuf.PACKABLE_WIRE_TYPES = [ - ProtoBuf.WIRE_TYPES.VARINT, - ProtoBuf.WIRE_TYPES.BITS64, - ProtoBuf.WIRE_TYPES.BITS32 - ]; - - /** - * Types. - * @dict - * @type {Object.} - * @const - * @expose - */ - ProtoBuf.TYPES = { - // According to the protobuf spec. - "int32": { - name: "int32", - wireType: ProtoBuf.WIRE_TYPES.VARINT - }, - "uint32": { - name: "uint32", - wireType: ProtoBuf.WIRE_TYPES.VARINT - }, - "sint32": { - name: "sint32", - wireType: ProtoBuf.WIRE_TYPES.VARINT - }, - "int64": { - name: "int64", - wireType: ProtoBuf.WIRE_TYPES.VARINT - }, - "uint64": { - name: "uint64", - wireType: ProtoBuf.WIRE_TYPES.VARINT - }, - "sint64": { - name: "sint64", - wireType: ProtoBuf.WIRE_TYPES.VARINT - }, - "bool": { - name: "bool", - wireType: ProtoBuf.WIRE_TYPES.VARINT - }, - "double": { - name: "double", - wireType: ProtoBuf.WIRE_TYPES.BITS64 - }, - "string": { - name: "string", - wireType: ProtoBuf.WIRE_TYPES.LDELIM - }, - "bytes": { - name: "bytes", - wireType: ProtoBuf.WIRE_TYPES.LDELIM - }, - "fixed32": { - name: "fixed32", - wireType: ProtoBuf.WIRE_TYPES.BITS32 - }, - "sfixed32": { - name: "sfixed32", - wireType: ProtoBuf.WIRE_TYPES.BITS32 - }, - "fixed64": { - name: "fixed64", - wireType: ProtoBuf.WIRE_TYPES.BITS64 - }, - "sfixed64": { - name: "sfixed64", - wireType: ProtoBuf.WIRE_TYPES.BITS64 - }, - "float": { - name: "float", - wireType: ProtoBuf.WIRE_TYPES.BITS32 - }, - "enum": { - name: "enum", - wireType: ProtoBuf.WIRE_TYPES.VARINT - }, - "message": { - name: "message", - wireType: ProtoBuf.WIRE_TYPES.LDELIM - }, - "group": { - name: "group", - wireType: ProtoBuf.WIRE_TYPES.STARTGROUP - } - }; - - /** - * Minimum field id. - * @type {number} - * @const - * @expose - */ - ProtoBuf.ID_MIN = 1; - - /** - * Maximum field id. - * @type {number} - * @const - * @expose - */ - ProtoBuf.ID_MAX = 0x1FFFFFFF; - - /** - * @type {!function(new: ByteBuffer, ...[*])} - * @expose - */ - ProtoBuf.ByteBuffer = ByteBuffer; - - /** - * @type {?function(new: Long, ...[*])} - * @expose - */ - ProtoBuf.Long = ByteBuffer.Long || null; - - /** - * If set to `true`, field names will be converted from underscore notation to camel case. Defaults to `false`. - * Must be set prior to parsing. - * @type {boolean} - * @expose - */ - ProtoBuf.convertFieldsToCamelCase = false; - - /** - * By default, messages are populated with (setX, set_x) accessors for each field. This can be disabled by - * setting this to `false` prior to building messages. - * @type {boolean} - * @expose - */ - ProtoBuf.populateAccessors = true; - - /** - * @alias ProtoBuf.Util - * @expose - */ - ProtoBuf.Util = (function() { - "use strict"; - - // Object.create polyfill - // ref: https://developer.mozilla.org/de/docs/JavaScript/Reference/Global_Objects/Object/create - if (!Object.create) - /** @expose */ - Object.create = function (o) { - if (arguments.length > 1) - throw Error('Object.create polyfill only accepts the first parameter.'); - function F() {} - F.prototype = o; - return new F(); - }; - - /** - * ProtoBuf utilities. - * @exports ProtoBuf.Util - * @namespace - */ - var Util = {}; - - /** - * Flag if running in node (fs is available) or not. - * @type {boolean} - * @const - * @expose - */ - Util.IS_NODE = false; - try { - // There is no reliable way to detect node.js as an environment, so our - // best bet is to feature-detect what we actually need. - Util.IS_NODE = - typeof require === 'function' && - typeof require("fs").readFileSync === 'function' && - typeof require("path").resolve === 'function'; - } catch (e) {} - - /** - * Constructs a XMLHttpRequest object. - * @return {XMLHttpRequest} - * @throws {Error} If XMLHttpRequest is not supported - * @expose - */ - Util.XHR = function() { - // No dependencies please, ref: http://www.quirksmode.org/js/xmlhttp.html - var XMLHttpFactories = [ - function () {return new XMLHttpRequest()}, - function () {return new ActiveXObject("Msxml2.XMLHTTP")}, - function () {return new ActiveXObject("Msxml3.XMLHTTP")}, - function () {return new ActiveXObject("Microsoft.XMLHTTP")} - ]; - /** @type {?XMLHttpRequest} */ - var xhr = null; - for (var i=0;i} - * @expose - */ - ProtoBuf.Lang = { - OPEN: "{", - CLOSE: "}", - OPTOPEN: "[", - OPTCLOSE: "]", - OPTEND: ",", - EQUAL: "=", - END: ";", - STRINGOPEN: '"', - STRINGCLOSE: '"', - STRINGOPEN_SQ: "'", - STRINGCLOSE_SQ: "'", - COPTOPEN: '(', - COPTCLOSE: ')', - DELIM: /[\s\{\}=;\[\],'"\(\)]/g, - // KEYWORD: /^(?:package|option|import|message|enum|extend|service|syntax|extensions|group)$/, - RULE: /^(?:required|optional|repeated)$/, - TYPE: /^(?:double|float|int32|uint32|sint32|int64|uint64|sint64|fixed32|sfixed32|fixed64|sfixed64|bool|string|bytes)$/, - NAME: /^[a-zA-Z_][a-zA-Z_0-9]*$/, - TYPEDEF: /^[a-zA-Z][a-zA-Z_0-9]*$/, - TYPEREF: /^(?:\.?[a-zA-Z_][a-zA-Z_0-9]*)+$/, - FQTYPEREF: /^(?:\.[a-zA-Z][a-zA-Z_0-9]*)+$/, - NUMBER: /^-?(?:[1-9][0-9]*|0|0x[0-9a-fA-F]+|0[0-7]+|([0-9]*\.[0-9]+([Ee][+-]?[0-9]+)?))$/, - NUMBER_DEC: /^(?:[1-9][0-9]*|0)$/, - NUMBER_HEX: /^0x[0-9a-fA-F]+$/, - NUMBER_OCT: /^0[0-7]+$/, - NUMBER_FLT: /^[0-9]*\.[0-9]+([Ee][+-]?[0-9]+)?$/, - ID: /^(?:[1-9][0-9]*|0|0x[0-9a-fA-F]+|0[0-7]+)$/, - NEGID: /^\-?(?:[1-9][0-9]*|0|0x[0-9a-fA-F]+|0[0-7]+)$/, - WHITESPACE: /\s/, - STRING: /(?:"([^"\\]*(?:\\.[^"\\]*)*)")|(?:'([^'\\]*(?:\\.[^'\\]*)*)')/g, - BOOL: /^(?:true|false)$/i - }; - - /** - * @alias ProtoBuf.DotProto - * @expose - */ - ProtoBuf.DotProto = (function(ProtoBuf, Lang) { - "use strict"; - - /** - * Utilities to parse .proto files. - * @exports ProtoBuf.DotProto - * @namespace - */ - var DotProto = {}; - - /** - * Constructs a new Tokenizer. - * @exports ProtoBuf.DotProto.Tokenizer - * @class prototype tokenizer - * @param {string} proto Proto to tokenize - * @constructor - */ - var Tokenizer = function(proto) { - - /** - * Source to parse. - * @type {string} - * @expose - */ - this.source = ""+proto; // In case it's a buffer - - /** - * Current index. - * @type {number} - * @expose - */ - this.index = 0; - - /** - * Current line. - * @type {number} - * @expose - */ - this.line = 1; - - /** - * Stacked values. - * @type {Array} - * @expose - */ - this.stack = []; - - /** - * Whether currently reading a string or not. - * @type {boolean} - * @expose - */ - this.readingString = false; - - /** - * Whatever character ends the string. Either a single or double quote character. - * @type {string} - * @expose - */ - this.stringEndsWith = Lang.STRINGCLOSE; - }; - - /** - * @alias ProtoBuf.DotProto.Tokenizer.prototype - * @inner - */ - var TokenizerPrototype = Tokenizer.prototype; - - /** - * Reads a string beginning at the current index. - * @return {string} The string - * @throws {Error} If it's not a valid string - * @private - */ - TokenizerPrototype._readString = function() { - Lang.STRING.lastIndex = this.index-1; // Include the open quote - var match; - if ((match = Lang.STRING.exec(this.source)) !== null) { - var s = typeof match[1] !== 'undefined' ? match[1] : match[2]; - this.index = Lang.STRING.lastIndex; - this.stack.push(this.stringEndsWith); - return s; - } - throw Error("Unterminated string at line "+this.line+", index "+this.index); - }; - - /** - * Gets the next token and advances by one. - * @return {?string} Token or `null` on EOF - * @throws {Error} If it's not a valid proto file - * @expose - */ - TokenizerPrototype.next = function() { - if (this.stack.length > 0) - return this.stack.shift(); - if (this.index >= this.source.length) - return null; // No more tokens - if (this.readingString) { - this.readingString = false; - return this._readString(); - } - var repeat, last; - do { - repeat = false; - // Strip white spaces - while (Lang.WHITESPACE.test(last = this.source.charAt(this.index))) { - this.index++; - if (last === "\n") - this.line++; - if (this.index === this.source.length) - return null; - } - // Strip comments - if (this.source.charAt(this.index) === '/') { - if (this.source.charAt(++this.index) === '/') { // Single line - while (this.source.charAt(this.index) !== "\n") { - this.index++; - if (this.index == this.source.length) - return null; - } - this.index++; - this.line++; - repeat = true; - } else if (this.source.charAt(this.index) === '*') { /* Block */ - last = ''; - while (last+(last=this.source.charAt(this.index)) !== '*/') { - this.index++; - if (last === "\n") - this.line++; - if (this.index === this.source.length) - return null; - } - this.index++; - repeat = true; - } else - throw Error("Unterminated comment at line "+this.line+": /"+this.source.charAt(this.index)); - } - } while (repeat); - if (this.index === this.source.length) return null; - - // Read the next token - var end = this.index; - Lang.DELIM.lastIndex = 0; - var delim = Lang.DELIM.test(this.source.charAt(end)); - if (!delim) { - ++end; - while(end < this.source.length && !Lang.DELIM.test(this.source.charAt(end))) - end++; - } else - ++end; - var token = this.source.substring(this.index, this.index = end); - if (token === Lang.STRINGOPEN) - this.readingString = true, - this.stringEndsWith = Lang.STRINGCLOSE; - else if (token === Lang.STRINGOPEN_SQ) - this.readingString = true, - this.stringEndsWith = Lang.STRINGCLOSE_SQ; - return token; - }; - - /** - * Peeks for the next token. - * @return {?string} Token or `null` on EOF - * @throws {Error} If it's not a valid proto file - * @expose - */ - TokenizerPrototype.peek = function() { - if (this.stack.length === 0) { - var token = this.next(); - if (token === null) - return null; - this.stack.push(token); - } - return this.stack[0]; - }; - - /** - * Returns a string representation of this object. - * @return {string} String representation as of "Tokenizer(index/length)" - * @expose - */ - TokenizerPrototype.toString = function() { - return "Tokenizer("+this.index+"/"+this.source.length+" at line "+this.line+")"; - }; - - /** - * @alias ProtoBuf.DotProto.Tokenizer - * @expose - */ - DotProto.Tokenizer = Tokenizer; - - /** - * Constructs a new Parser. - * @exports ProtoBuf.DotProto.Parser - * @class prototype parser - * @param {string} proto Protocol source - * @constructor - */ - var Parser = function(proto) { - - /** - * Tokenizer. - * @type {ProtoBuf.DotProto.Tokenizer} - * @expose - */ - this.tn = new Tokenizer(proto); - }; - - /** - * @alias ProtoBuf.DotProto.Parser.prototype - * @inner - */ - var ParserPrototype = Parser.prototype; - - /** - * Runs the parser. - * @return {{package: string|null, messages: Array., enums: Array., imports: Array., options: object}} - * @throws {Error} If the source cannot be parsed - * @expose - */ - ParserPrototype.parse = function() { - var topLevel = { - "name": "[ROOT]", // temporary - "package": null, - "messages": [], - "enums": [], - "imports": [], - "options": {}, - "services": [] - }; - var token, head = true; - while(token = this.tn.next()) { - switch (token) { - case 'package': - if (!head || topLevel["package"] !== null) - throw Error("Unexpected package at line "+this.tn.line); - topLevel["package"] = this._parsePackage(token); - break; - case 'import': - if (!head) - throw Error("Unexpected import at line "+this.tn.line); - topLevel.imports.push(this._parseImport(token)); - break; - case 'message': - this._parseMessage(topLevel, null, token); - head = false; - break; - case 'enum': - this._parseEnum(topLevel, token); - head = false; - break; - case 'option': - if (!head) - throw Error("Unexpected option at line "+this.tn.line); - this._parseOption(topLevel, token); - break; - case 'service': - this._parseService(topLevel, token); - break; - case 'extend': - this._parseExtend(topLevel, token); - break; - case 'syntax': - this._parseIgnoredStatement(topLevel, token); - break; - default: - throw Error("Unexpected token at line "+this.tn.line+": "+token); - } - } - delete topLevel["name"]; - return topLevel; - }; - - /** - * Parses a number value. - * @param {string} val Number value to parse - * @return {number} Number - * @throws {Error} If the number value is invalid - * @private - */ - ParserPrototype._parseNumber = function(val) { - var sign = 1; - if (val.charAt(0) == '-') - sign = -1, - val = val.substring(1); - if (Lang.NUMBER_DEC.test(val)) - return sign*parseInt(val, 10); - else if (Lang.NUMBER_HEX.test(val)) - return sign*parseInt(val.substring(2), 16); - else if (Lang.NUMBER_OCT.test(val)) - return sign*parseInt(val.substring(1), 8); - else if (Lang.NUMBER_FLT.test(val)) - return sign*parseFloat(val); - throw Error("Illegal number at line "+this.tn.line+": "+(sign < 0 ? '-' : '')+val); - }; - - /** - * Parses a (possibly multiline) string. - * @returns {string} - * @private - */ - ParserPrototype._parseString = function() { - var value = "", token; - do { - token = this.tn.next(); // Known to be = this.tn.stringEndsWith - value += this.tn.next(); - token = this.tn.next(); - if (token !== this.tn.stringEndsWith) - throw Error("Illegal end of string at line "+this.tn.line+": "+token); - token = this.tn.peek(); - } while (token === Lang.STRINGOPEN || token === Lang.STRINGOPEN_SQ); - return value; - }; - - /** - * Parses an ID value. - * @param {string} val ID value to parse - * @param {boolean=} neg Whether the ID may be negative, defaults to `false` - * @returns {number} ID - * @throws {Error} If the ID value is invalid - * @private - */ - ParserPrototype._parseId = function(val, neg) { - var id = -1; - var sign = 1; - if (val.charAt(0) == '-') - sign = -1, - val = val.substring(1); - if (Lang.NUMBER_DEC.test(val)) - id = parseInt(val); - else if (Lang.NUMBER_HEX.test(val)) - id = parseInt(val.substring(2), 16); - else if (Lang.NUMBER_OCT.test(val)) - id = parseInt(val.substring(1), 8); - else - throw Error("Illegal id at line "+this.tn.line+": "+(sign < 0 ? '-' : '')+val); - id = (sign*id)|0; // Force to 32bit - if (!neg && id < 0) - throw Error("Illegal id at line "+this.tn.line+": "+(sign < 0 ? '-' : '')+val); - return id; - }; - - /** - * Parses the package definition. - * @param {string} token Initial token - * @return {string} Package name - * @throws {Error} If the package definition cannot be parsed - * @private - */ - ParserPrototype._parsePackage = function(token) { - token = this.tn.next(); - if (!Lang.TYPEREF.test(token)) - throw Error("Illegal package name at line "+this.tn.line+": "+token); - var pkg = token; - token = this.tn.next(); - if (token != Lang.END) - throw Error("Illegal end of package at line "+this.tn.line+": "+token); - return pkg; - }; - - /** - * Parses an import definition. - * @param {string} token Initial token - * @return {string} Import file name - * @throws {Error} If the import definition cannot be parsed - * @private - */ - ParserPrototype._parseImport = function(token) { - token = this.tn.peek(); - if (token === "public") - this.tn.next(), - token = this.tn.peek(); - if (token !== Lang.STRINGOPEN && token !== Lang.STRINGOPEN_SQ) - throw Error("Illegal start of import at line "+this.tn.line+": "+token); - var imported = this._parseString(); - token = this.tn.next(); - if (token !== Lang.END) - throw Error("Illegal end of import at line "+this.tn.line+": "+token); - return imported; - }; - - /** - * Parses a namespace option. - * @param {Object} parent Parent definition - * @param {string} token Initial token - * @throws {Error} If the option cannot be parsed - * @private - */ - ParserPrototype._parseOption = function(parent, token) { - token = this.tn.next(); - var custom = false; - if (token == Lang.COPTOPEN) - custom = true, - token = this.tn.next(); - if (!Lang.TYPEREF.test(token)) - // we can allow options of the form google.protobuf.* since they will just get ignored anyways - if (!/google\.protobuf\./.test(token)) - throw Error("Illegal option name in message "+parent.name+" at line "+this.tn.line+": "+token); - var name = token; - token = this.tn.next(); - if (custom) { // (my_method_option).foo, (my_method_option), some_method_option, (foo.my_option).bar - if (token !== Lang.COPTCLOSE) - throw Error("Illegal end in message "+parent.name+", option "+name+" at line "+this.tn.line+": "+token); - name = '('+name+')'; - token = this.tn.next(); - if (Lang.FQTYPEREF.test(token)) - name += token, - token = this.tn.next(); - } - if (token !== Lang.EQUAL) - throw Error("Illegal operator in message "+parent.name+", option "+name+" at line "+this.tn.line+": "+token); - var value; - token = this.tn.peek(); - if (token === Lang.STRINGOPEN || token === Lang.STRINGOPEN_SQ) - value = this._parseString(); - else { - this.tn.next(); - if (Lang.NUMBER.test(token)) - value = this._parseNumber(token, true); - else if (Lang.BOOL.test(token)) - value = token === 'true'; - else if (Lang.TYPEREF.test(token)) - value = token; - else - throw Error("Illegal option value in message "+parent.name+", option "+name+" at line "+this.tn.line+": "+token); - } - token = this.tn.next(); - if (token !== Lang.END) - throw Error("Illegal end of option in message "+parent.name+", option "+name+" at line "+this.tn.line+": "+token); - parent["options"][name] = value; - }; - - /** - * Parses an ignored statement of the form ['keyword', ..., ';']. - * @param {Object} parent Parent definition - * @param {string} keyword Initial token - * @throws {Error} If the directive cannot be parsed - * @private - */ - ParserPrototype._parseIgnoredStatement = function(parent, keyword) { - var token; - do { - token = this.tn.next(); - if (token === null) - throw Error("Unexpected EOF in "+parent.name+", "+keyword+" at line "+this.tn.line); - if (token === Lang.END) - break; - } while (true); - }; - - /** - * Parses a service definition. - * @param {Object} parent Parent definition - * @param {string} token Initial token - * @throws {Error} If the service cannot be parsed - * @private - */ - ParserPrototype._parseService = function(parent, token) { - token = this.tn.next(); - if (!Lang.NAME.test(token)) - throw Error("Illegal service name at line "+this.tn.line+": "+token); - var name = token; - var svc = { - "name": name, - "rpc": {}, - "options": {} - }; - token = this.tn.next(); - if (token !== Lang.OPEN) - throw Error("Illegal start of service "+name+" at line "+this.tn.line+": "+token); - do { - token = this.tn.next(); - if (token === "option") - this._parseOption(svc, token); - else if (token === 'rpc') - this._parseServiceRPC(svc, token); - else if (token !== Lang.CLOSE) - throw Error("Illegal type of service "+name+" at line "+this.tn.line+": "+token); - } while (token !== Lang.CLOSE); - parent["services"].push(svc); - }; - - /** - * Parses a RPC service definition of the form ['rpc', name, (request), 'returns', (response)]. - * @param {Object} svc Parent definition - * @param {string} token Initial token - * @private - */ - ParserPrototype._parseServiceRPC = function(svc, token) { - var type = token; - token = this.tn.next(); - if (!Lang.NAME.test(token)) - throw Error("Illegal method name in service "+svc["name"]+" at line "+this.tn.line+": "+token); - var name = token; - var method = { - "request": null, - "response": null, - "options": {} - }; - token = this.tn.next(); - if (token !== Lang.COPTOPEN) - throw Error("Illegal start of request type in service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); - token = this.tn.next(); - if (!Lang.TYPEREF.test(token)) - throw Error("Illegal request type in service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); - method["request"] = token; - token = this.tn.next(); - if (token != Lang.COPTCLOSE) - throw Error("Illegal end of request type in service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); - token = this.tn.next(); - if (token.toLowerCase() !== "returns") - throw Error("Illegal delimiter in service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); - token = this.tn.next(); - if (token != Lang.COPTOPEN) - throw Error("Illegal start of response type in service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); - token = this.tn.next(); - method["response"] = token; - token = this.tn.next(); - if (token !== Lang.COPTCLOSE) - throw Error("Illegal end of response type in service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); - token = this.tn.next(); - if (token === Lang.OPEN) { - do { - token = this.tn.next(); - if (token === 'option') - this._parseOption(method, token); // <- will fail for the custom-options example - else if (token !== Lang.CLOSE) - throw Error("Illegal start of option inservice "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); - } while (token !== Lang.CLOSE); - if (this.tn.peek() === Lang.END) - this.tn.next(); - } else if (token !== Lang.END) - throw Error("Illegal delimiter in service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); - if (typeof svc[type] === 'undefined') - svc[type] = {}; - svc[type][name] = method; - }; - - /** - * Parses a message definition. - * @param {Object} parent Parent definition - * @param {Object} fld Field definition if this is a group, otherwise `null` - * @param {string} token First token - * @return {Object} - * @throws {Error} If the message cannot be parsed - * @private - */ - ParserPrototype._parseMessage = function(parent, fld, token) { - /** @dict */ - var msg = {}; // Note: At some point we might want to exclude the parser, so we need a dict. - var isGroup = token === "group"; - token = this.tn.next(); - if (!Lang.NAME.test(token)) - throw Error("Illegal "+(isGroup ? "group" : "message")+" name"+(parent ? " in message "+parent["name"] : "")+" at line "+this.tn.line+": "+token); - msg["name"] = token; - if (isGroup) { - token = this.tn.next(); - if (token !== Lang.EQUAL) - throw Error("Illegal id assignment after group "+msg.name+" at line "+this.tn.line+": "+token); - token = this.tn.next(); - try { - fld["id"] = this._parseId(token); - } catch (e) { - throw Error("Illegal field id value for group "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); - } - msg["isGroup"] = true; - } - msg["fields"] = []; // Note: Using arrays to support also browser that cannot preserve order of object keys. - msg["enums"] = []; - msg["messages"] = []; - msg["options"] = {}; - msg["oneofs"] = {}; - token = this.tn.next(); - if (token === Lang.OPTOPEN && fld) - this._parseFieldOptions(msg, fld, token), - token = this.tn.next(); - if (token !== Lang.OPEN) - throw Error("Illegal start of "+(isGroup ? "group" : "message")+" "+msg.name+" at line "+this.tn.line+": "+token); - // msg["extensions"] = undefined - do { - token = this.tn.next(); - if (token === Lang.CLOSE) { - token = this.tn.peek(); - if (token === Lang.END) - this.tn.next(); - break; - } else if (Lang.RULE.test(token)) - this._parseMessageField(msg, token); - else if (token === "oneof") - this._parseMessageOneOf(msg, token); - else if (token === "enum") - this._parseEnum(msg, token); - else if (token === "message") - this._parseMessage(msg, null, token); - else if (token === "option") - this._parseOption(msg, token); - else if (token === "extensions") - msg["extensions"] = this._parseExtensions(msg, token); - else if (token === "extend") - this._parseExtend(msg, token); - else - throw Error("Illegal token in message "+msg.name+" at line "+this.tn.line+": "+token); - } while (true); - parent["messages"].push(msg); - return msg; - }; - - /** - * Parses a message field. - * @param {Object} msg Message definition - * @param {string} token Initial token - * @returns {!Object} Field descriptor - * @throws {Error} If the message field cannot be parsed - * @private - */ - ParserPrototype._parseMessageField = function(msg, token) { - /** @dict */ - var fld = {}, grp = null; - fld["rule"] = token; - /** @dict */ - fld["options"] = {}; - token = this.tn.next(); - if (token === "group") { - // "A [legacy] group simply combines a nested message type and a field into a single declaration. In your - // code, you can treat this message just as if it had a Result type field called result (the latter name is - // converted to lower-case so that it does not conflict with the former)." - grp = this._parseMessage(msg, fld, token); - if (!/^[A-Z]/.test(grp["name"])) - throw Error('Group names must start with a capital letter'); - fld["type"] = grp["name"]; - fld["name"] = grp["name"].toLowerCase(); - token = this.tn.peek(); - if (token === Lang.END) - this.tn.next(); - } else { - if (!Lang.TYPE.test(token) && !Lang.TYPEREF.test(token)) - throw Error("Illegal field type in message "+msg.name+" at line "+this.tn.line+": "+token); - fld["type"] = token; - token = this.tn.next(); - if (!Lang.NAME.test(token)) - throw Error("Illegal field name in message "+msg.name+" at line "+this.tn.line+": "+token); - fld["name"] = token; - token = this.tn.next(); - if (token !== Lang.EQUAL) - throw Error("Illegal token in field "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); - token = this.tn.next(); - try { - fld["id"] = this._parseId(token); - } catch (e) { - throw Error("Illegal field id in message "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); - } - token = this.tn.next(); - if (token === Lang.OPTOPEN) - this._parseFieldOptions(msg, fld, token), - token = this.tn.next(); - if (token !== Lang.END) - throw Error("Illegal delimiter in message "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); - } - msg["fields"].push(fld); - return fld; - }; - - /** - * Parses a message oneof. - * @param {Object} msg Message definition - * @param {string} token Initial token - * @throws {Error} If the message oneof cannot be parsed - * @private - */ - ParserPrototype._parseMessageOneOf = function(msg, token) { - token = this.tn.next(); - if (!Lang.NAME.test(token)) - throw Error("Illegal oneof name in message "+msg.name+" at line "+this.tn.line+": "+token); - var name = token, - fld; - var fields = []; - token = this.tn.next(); - if (token !== Lang.OPEN) - throw Error("Illegal start of oneof "+name+" at line "+this.tn.line+": "+token); - while (this.tn.peek() !== Lang.CLOSE) { - fld = this._parseMessageField(msg, "optional"); - fld["oneof"] = name; - fields.push(fld["id"]); - } - this.tn.next(); - msg["oneofs"][name] = fields; - }; - - /** - * Parses a set of field option definitions. - * @param {Object} msg Message definition - * @param {Object} fld Field definition - * @param {string} token Initial token - * @throws {Error} If the message field options cannot be parsed - * @private - */ - ParserPrototype._parseFieldOptions = function(msg, fld, token) { - var first = true; - do { - token = this.tn.next(); - if (token === Lang.OPTCLOSE) - break; - else if (token === Lang.OPTEND) { - if (first) - throw Error("Illegal start of options in message "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); - token = this.tn.next(); - } - this._parseFieldOption(msg, fld, token); - first = false; - } while (true); - }; - - /** - * Parses a single field option. - * @param {Object} msg Message definition - * @param {Object} fld Field definition - * @param {string} token Initial token - * @throws {Error} If the mesage field option cannot be parsed - * @private - */ - ParserPrototype._parseFieldOption = function(msg, fld, token) { - var custom = false; - if (token === Lang.COPTOPEN) - token = this.tn.next(), - custom = true; - if (!Lang.TYPEREF.test(token)) - throw Error("Illegal field option in "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); - var name = token; - token = this.tn.next(); - if (custom) { - if (token !== Lang.COPTCLOSE) - throw Error("Illegal delimiter in "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); - name = '('+name+')'; - token = this.tn.next(); - if (Lang.FQTYPEREF.test(token)) - name += token, - token = this.tn.next(); - } - if (token !== Lang.EQUAL) - throw Error("Illegal token in "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); - var value; - token = this.tn.peek(); - if (token === Lang.STRINGOPEN || token === Lang.STRINGOPEN_SQ) { - value = this._parseString(); - } else if (Lang.NUMBER.test(token, true)) - value = this._parseNumber(this.tn.next(), true); - else if (Lang.BOOL.test(token)) - value = this.tn.next().toLowerCase() === 'true'; - else if (Lang.TYPEREF.test(token)) - value = this.tn.next(); // TODO: Resolve? - else - throw Error("Illegal value in message "+msg.name+"#"+fld.name+", option "+name+" at line "+this.tn.line+": "+token); - fld["options"][name] = value; - }; - - /** - * Parses an enum. - * @param {Object} msg Message definition - * @param {string} token Initial token - * @throws {Error} If the enum cannot be parsed - * @private - */ - ParserPrototype._parseEnum = function(msg, token) { - /** @dict */ - var enm = {}; - token = this.tn.next(); - if (!Lang.NAME.test(token)) - throw Error("Illegal enum name in message "+msg.name+" at line "+this.tn.line+": "+token); - enm["name"] = token; - token = this.tn.next(); - if (token !== Lang.OPEN) - throw Error("Illegal start of enum "+enm.name+" at line "+this.tn.line+": "+token); - enm["values"] = []; - enm["options"] = {}; - do { - token = this.tn.next(); - if (token === Lang.CLOSE) { - token = this.tn.peek(); - if (token === Lang.END) - this.tn.next(); - break; - } - if (token == 'option') - this._parseOption(enm, token); - else { - if (!Lang.NAME.test(token)) - throw Error("Illegal name in enum "+enm.name+" at line "+this.tn.line+": "+token); - this._parseEnumValue(enm, token); - } - } while (true); - msg["enums"].push(enm); - }; - - /** - * Parses an enum value. - * @param {Object} enm Enum definition - * @param {string} token Initial token - * @throws {Error} If the enum value cannot be parsed - * @private - */ - ParserPrototype._parseEnumValue = function(enm, token) { - /** @dict */ - var val = {}; - val["name"] = token; - token = this.tn.next(); - if (token !== Lang.EQUAL) - throw Error("Illegal token in enum "+enm.name+" at line "+this.tn.line+": "+token); - token = this.tn.next(); - try { - val["id"] = this._parseId(token, true); - } catch (e) { - throw Error("Illegal id in enum "+enm.name+" at line "+this.tn.line+": "+token); - } - enm["values"].push(val); - token = this.tn.next(); - if (token === Lang.OPTOPEN) { - var opt = { 'options' : {} }; // TODO: Actually expose them somehow. - this._parseFieldOptions(enm, opt, token); - token = this.tn.next(); - } - if (token !== Lang.END) - throw Error("Illegal delimiter in enum "+enm.name+" at line "+this.tn.line+": "+token); - }; - - /** - * Parses an extensions statement. - * @param {Object} msg Message object - * @param {string} token Initial token - * @throws {Error} If the extensions statement cannot be parsed - * @private - */ - ParserPrototype._parseExtensions = function(msg, token) { - /** @type {Array.} */ - var range = []; - token = this.tn.next(); - if (token === "min") // FIXME: Does the official implementation support this? - range.push(ProtoBuf.ID_MIN); - else if (token === "max") - range.push(ProtoBuf.ID_MAX); - else - range.push(this._parseNumber(token)); - token = this.tn.next(); - if (token !== 'to') - throw Error("Illegal extensions delimiter in message "+msg.name+" at line "+this.tn.line+": "+token); - token = this.tn.next(); - if (token === "min") - range.push(ProtoBuf.ID_MIN); - else if (token === "max") - range.push(ProtoBuf.ID_MAX); - else - range.push(this._parseNumber(token)); - token = this.tn.next(); - if (token !== Lang.END) - throw Error("Illegal extensions delimiter in message "+msg.name+" at line "+this.tn.line+": "+token); - return range; - }; - - /** - * Parses an extend block. - * @param {Object} parent Parent object - * @param {string} token Initial token - * @throws {Error} If the extend block cannot be parsed - * @private - */ - ParserPrototype._parseExtend = function(parent, token) { - token = this.tn.next(); - if (!Lang.TYPEREF.test(token)) - throw Error("Illegal message name at line "+this.tn.line+": "+token); - /** @dict */ - var ext = {}; - ext["ref"] = token; - ext["fields"] = []; - token = this.tn.next(); - if (token !== Lang.OPEN) - throw Error("Illegal start of extend "+ext.name+" at line "+this.tn.line+": "+token); - do { - token = this.tn.next(); - if (token === Lang.CLOSE) { - token = this.tn.peek(); - if (token == Lang.END) - this.tn.next(); - break; - } else if (Lang.RULE.test(token)) - this._parseMessageField(ext, token); - else - throw Error("Illegal token in extend "+ext.name+" at line "+this.tn.line+": "+token); - } while (true); - parent["messages"].push(ext); - return ext; - }; - - /** - * Returns a string representation of this object. - * @returns {string} String representation as of "Parser" - */ - ParserPrototype.toString = function() { - return "Parser"; - }; - - /** - * @alias ProtoBuf.DotProto.Parser - * @expose - */ - DotProto.Parser = Parser; - - return DotProto; - - })(ProtoBuf, ProtoBuf.Lang); - - /** - * @alias ProtoBuf.Reflect - * @expose - */ - ProtoBuf.Reflect = (function(ProtoBuf) { - "use strict"; - - /** - * Reflection types. - * @exports ProtoBuf.Reflect - * @namespace - */ - var Reflect = {}; - - /** - * Constructs a Reflect base class. - * @exports ProtoBuf.Reflect.T - * @constructor - * @abstract - * @param {!ProtoBuf.Builder} builder Builder reference - * @param {?ProtoBuf.Reflect.T} parent Parent object - * @param {string} name Object name - */ - var T = function(builder, parent, name) { - - /** - * Builder reference. - * @type {!ProtoBuf.Builder} - * @expose - */ - this.builder = builder; - - /** - * Parent object. - * @type {?ProtoBuf.Reflect.T} - * @expose - */ - this.parent = parent; - - /** - * Object name in namespace. - * @type {string} - * @expose - */ - this.name = name; - - /** - * Fully qualified class name - * @type {string} - * @expose - */ - this.className; - }; - - /** - * @alias ProtoBuf.Reflect.T.prototype - * @inner - */ - var TPrototype = T.prototype; - - /** - * Returns the fully qualified name of this object. - * @returns {string} Fully qualified name as of ".PATH.TO.THIS" - * @expose - */ - TPrototype.fqn = function() { - var name = this.name, - ptr = this; - do { - ptr = ptr.parent; - if (ptr == null) - break; - name = ptr.name+"."+name; - } while (true); - return name; - }; - - /** - * Returns a string representation of this Reflect object (its fully qualified name). - * @param {boolean=} includeClass Set to true to include the class name. Defaults to false. - * @return String representation - * @expose - */ - TPrototype.toString = function(includeClass) { - return (includeClass ? this.className + " " : "") + this.fqn(); - }; - - /** - * Builds this type. - * @throws {Error} If this type cannot be built directly - * @expose - */ - TPrototype.build = function() { - throw Error(this.toString(true)+" cannot be built directly"); - }; - - /** - * @alias ProtoBuf.Reflect.T - * @expose - */ - Reflect.T = T; - - /** - * Constructs a new Namespace. - * @exports ProtoBuf.Reflect.Namespace - * @param {!ProtoBuf.Builder} builder Builder reference - * @param {?ProtoBuf.Reflect.Namespace} parent Namespace parent - * @param {string} name Namespace name - * @param {Object.=} options Namespace options - * @constructor - * @extends ProtoBuf.Reflect.T - */ - var Namespace = function(builder, parent, name, options) { - T.call(this, builder, parent, name); - - /** - * @override - */ - this.className = "Namespace"; - - /** - * Children inside the namespace. - * @type {!Array.} - */ - this.children = []; - - /** - * Options. - * @type {!Object.} - */ - this.options = options || {}; - }; - - /** - * @alias ProtoBuf.Reflect.Namespace.prototype - * @inner - */ - var NamespacePrototype = Namespace.prototype = Object.create(T.prototype); - - /** - * Returns an array of the namespace's children. - * @param {ProtoBuf.Reflect.T=} type Filter type (returns instances of this type only). Defaults to null (all children). - * @return {Array.} - * @expose - */ - NamespacePrototype.getChildren = function(type) { - type = type || null; - if (type == null) - return this.children.slice(); - var children = []; - for (var i=0, k=this.children.length; i} Runtime namespace - * @expose - */ - NamespacePrototype.build = function() { - /** @dict */ - var ns = {}; - var children = this.children; - for (var i=0, k=children.length, child; i} - */ - NamespacePrototype.buildOpt = function() { - var opt = {}, - keys = Object.keys(this.options); - for (var i=0, k=keys.length; i}null} Option value or NULL if there is no such option - */ - NamespacePrototype.getOption = function(name) { - if (typeof name === 'undefined') - return this.options; - return typeof this.options[name] !== 'undefined' ? this.options[name] : null; - }; - - /** - * @alias ProtoBuf.Reflect.Namespace - * @expose - */ - Reflect.Namespace = Namespace; - - /** - * Constructs a new Message. - * @exports ProtoBuf.Reflect.Message - * @param {!ProtoBuf.Builder} builder Builder reference - * @param {!ProtoBuf.Reflect.Namespace} parent Parent message or namespace - * @param {string} name Message name - * @param {Object.=} options Message options - * @param {boolean=} isGroup `true` if this is a legacy group - * @constructor - * @extends ProtoBuf.Reflect.Namespace - */ - var Message = function(builder, parent, name, options, isGroup) { - Namespace.call(this, builder, parent, name, options); - - /** - * @override - */ - this.className = "Message"; - - /** - * Extensions range. - * @type {!Array.} - * @expose - */ - this.extensions = [ProtoBuf.ID_MIN, ProtoBuf.ID_MAX]; - - /** - * Runtime message class. - * @type {?function(new:ProtoBuf.Builder.Message)} - * @expose - */ - this.clazz = null; - - /** - * Whether this is a legacy group or not. - * @type {boolean} - * @expose - */ - this.isGroup = !!isGroup; - - // The following cached collections are used to efficiently iterate over or look up fields when decoding. - - /** - * Cached fields. - * @type {?Array.} - * @private - */ - this._fields = null; - - /** - * Cached fields by id. - * @type {?Object.} - * @private - */ - this._fieldsById = null; - - /** - * Cached fields by name. - * @type {?Object.} - * @private - */ - this._fieldsByName = null; - }; - - /** - * @alias ProtoBuf.Reflect.Message.prototype - * @inner - */ - var MessagePrototype = Message.prototype = Object.create(Namespace.prototype); - - /** - * Builds the message and returns the runtime counterpart, which is a fully functional class. - * @see ProtoBuf.Builder.Message - * @param {boolean=} rebuild Whether to rebuild or not, defaults to false - * @return {ProtoBuf.Reflect.Message} Message class - * @throws {Error} If the message cannot be built - * @expose - */ - MessagePrototype.build = function(rebuild) { - if (this.clazz && !rebuild) - return this.clazz; - - // Create the runtime Message class in its own scope - var clazz = (function(ProtoBuf, T) { - - var fields = T.getChildren(ProtoBuf.Reflect.Message.Field), - oneofs = T.getChildren(ProtoBuf.Reflect.Message.OneOf); - - /** - * Constructs a new runtime Message. - * @name ProtoBuf.Builder.Message - * @class Barebone of all runtime messages. - * @param {!Object.|string} values Preset values - * @param {...string} var_args - * @constructor - * @throws {Error} If the message cannot be created - */ - var Message = function(values, var_args) { - ProtoBuf.Builder.Message.call(this); - - // Create virtual oneof properties - for (var i=0, k=oneofs.length; i 0) { - // Set field values from a values object - if (arguments.length === 1 && typeof values === 'object' && - /* not another Message */ typeof values.encode !== 'function' && - /* not a repeated field */ !ProtoBuf.Util.isArray(values) && - /* not a ByteBuffer */ !(values instanceof ByteBuffer) && - /* not an ArrayBuffer */ !(values instanceof ArrayBuffer) && - /* not a Long */ !(ProtoBuf.Long && values instanceof ProtoBuf.Long)) { - var keys = Object.keys(values); - for (i=0, k=keys.length; i} Raw payload - * @expose - */ - MessagePrototype.toRaw = function(includeBinaryAsBase64) { - return cloneRaw(this, !!includeBinaryAsBase64); - }; - - /** - * Decodes a message from the specified buffer or string. - * @name ProtoBuf.Builder.Message.decode - * @function - * @param {!ByteBuffer|!ArrayBuffer|!Buffer|string} buffer Buffer to decode from - * @param {string=} enc Encoding if buffer is a string: hex, utf8 (not recommended), defaults to base64 - * @return {!ProtoBuf.Builder.Message} Decoded message - * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still - * returns the decoded message with missing fields in the `decoded` property on the error. - * @expose - * @see ProtoBuf.Builder.Message.decode64 - * @see ProtoBuf.Builder.Message.decodeHex - */ - Message.decode = function(buffer, enc) { - if (typeof buffer === 'string') - buffer = ByteBuffer.wrap(buffer, enc ? enc : "base64"); - buffer = buffer instanceof ByteBuffer ? buffer : ByteBuffer.wrap(buffer); // May throw - var le = buffer.littleEndian; - try { - var msg = T.decode(buffer.LE()); - buffer.LE(le); - return msg; - } catch (e) { - buffer.LE(le); - throw(e); - } - }; - - /** - * Decodes a varint32 length-delimited message from the specified buffer or string. - * @name ProtoBuf.Builder.Message.decodeDelimited - * @function - * @param {!ByteBuffer|!ArrayBuffer|!Buffer|string} buffer Buffer to decode from - * @param {string=} enc Encoding if buffer is a string: hex, utf8 (not recommended), defaults to base64 - * @return {ProtoBuf.Builder.Message} Decoded message or `null` if not enough bytes are available yet - * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still - * returns the decoded message with missing fields in the `decoded` property on the error. - * @expose - */ - Message.decodeDelimited = function(buffer, enc) { - if (typeof buffer === 'string') - buffer = ByteBuffer.wrap(buffer, enc ? enc : "base64"); - buffer = buffer instanceof ByteBuffer ? buffer : ByteBuffer.wrap(buffer); // May throw - if (buffer.remaining() < 1) - return null; - var off = buffer.offset, - len = buffer.readVarint32(); - if (buffer.remaining() < len) { - buffer.offset = off; - return null; - } - try { - var msg = T.decode(buffer.slice(buffer.offset, buffer.offset + len).LE()); - buffer.offset += len; - return msg; - } catch (err) { - buffer.offset += len; - throw err; - } - }; - - /** - * Decodes the message from the specified base64 encoded string. - * @name ProtoBuf.Builder.Message.decode64 - * @function - * @param {string} str String to decode from - * @return {!ProtoBuf.Builder.Message} Decoded message - * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still - * returns the decoded message with missing fields in the `decoded` property on the error. - * @expose - */ - Message.decode64 = function(str) { - return Message.decode(str, "base64"); - }; - - /** - * Decodes the message from the specified hex encoded string. - * @name ProtoBuf.Builder.Message.decodeHex - * @function - * @param {string} str String to decode from - * @return {!ProtoBuf.Builder.Message} Decoded message - * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still - * returns the decoded message with missing fields in the `decoded` property on the error. - * @expose - */ - Message.decodeHex = function(str) { - return Message.decode(str, "hex"); - }; - - // Utility - - /** - * Returns a string representation of this Message. - * @name ProtoBuf.Builder.Message#toString - * @function - * @return {string} String representation as of ".Fully.Qualified.MessageName" - * @expose - */ - MessagePrototype.toString = function() { - return T.toString(); - }; - - // Properties - - /** - * Options. - * @name ProtoBuf.Builder.Message.$options - * @type {Object.} - * @expose - */ - var $options; // cc - - /** - * Reflection type. - * @name ProtoBuf.Builder.Message#$type - * @type {!ProtoBuf.Reflect.Message} - * @expose - */ - var $type; // cc - - if (Object.defineProperty) - Object.defineProperty(Message, '$options', { "value": T.buildOpt() }), - Object.defineProperty(MessagePrototype, "$type", { - get: function() { return T; } - }); - - return Message; - - })(ProtoBuf, this); - - // Static enums and prototyped sub-messages / cached collections - this._fields = []; - this._fieldsById = {}; - this._fieldsByName = {}; - for (var i=0, k=this.children.length, child; i> 3; - switch (wireType) { - case ProtoBuf.WIRE_TYPES.VARINT: - do tag = buf.readUint8(); - while ((tag & 0x80) === 0x80); - break; - case ProtoBuf.WIRE_TYPES.BITS64: - buf.offset += 8; - break; - case ProtoBuf.WIRE_TYPES.LDELIM: - tag = buf.readVarint32(); // reads the varint - buf.offset += tag; // skips n bytes - break; - case ProtoBuf.WIRE_TYPES.STARTGROUP: - skipTillGroupEnd(id, buf); - break; - case ProtoBuf.WIRE_TYPES.ENDGROUP: - if (id === expectedId) - return false; - else - throw Error("Illegal GROUPEND after unknown group: "+id+" ("+expectedId+" expected)"); - case ProtoBuf.WIRE_TYPES.BITS32: - buf.offset += 4; - break; - default: - throw Error("Illegal wire type in unknown group "+expectedId+": "+wireType); - } - return true; - } - - /** - * Decodes an encoded message and returns the decoded message. - * @param {ByteBuffer} buffer ByteBuffer to decode from - * @param {number=} length Message length. Defaults to decode all the available data. - * @param {number=} expectedGroupEndId Expected GROUPEND id if this is a legacy group - * @return {ProtoBuf.Builder.Message} Decoded message - * @throws {Error} If the message cannot be decoded - * @expose - */ - MessagePrototype.decode = function(buffer, length, expectedGroupEndId) { - length = typeof length === 'number' ? length : -1; - var start = buffer.offset, - msg = new (this.clazz)(), - tag, wireType, id, field; - while (buffer.offset < start+length || (length === -1 && buffer.remaining() > 0)) { - tag = buffer.readVarint32(); - wireType = tag & 0x07; - id = tag >> 3; - if (wireType === ProtoBuf.WIRE_TYPES.ENDGROUP) { - if (id !== expectedGroupEndId) - throw Error("Illegal group end indicator for "+this.toString(true)+": "+id+" ("+(expectedGroupEndId ? expectedGroupEndId+" expected" : "not a group")+")"); - break; - } - if (!(field = this._fieldsById[id])) { - // "messages created by your new code can be parsed by your old code: old binaries simply ignore the new field when parsing." - switch (wireType) { - case ProtoBuf.WIRE_TYPES.VARINT: - buffer.readVarint32(); - break; - case ProtoBuf.WIRE_TYPES.BITS32: - buffer.offset += 4; - break; - case ProtoBuf.WIRE_TYPES.BITS64: - buffer.offset += 8; - break; - case ProtoBuf.WIRE_TYPES.LDELIM: - var len = buffer.readVarint32(); - buffer.offset += len; - break; - case ProtoBuf.WIRE_TYPES.STARTGROUP: - while (skipTillGroupEnd(id, buffer)) {} - break; - default: - throw Error("Illegal wire type for unknown field "+id+" in "+this.toString(true)+"#decode: "+wireType); - } - continue; - } - if (field.repeated && !field.options["packed"]) - msg[field.name].push(field.decode(wireType, buffer)); - else { - msg[field.name] = field.decode(wireType, buffer); - if (field.oneof) { - if (this[field.oneof.name] !== null) - this[this[field.oneof.name]] = null; - msg[field.oneof.name] = field.name; - } - } - } - - // Check if all required fields are present and set default values for optional fields that are not - for (var i=0, k=this._fields.length; i=} options Options - * @param {!ProtoBuf.Reflect.Message.OneOf=} oneof Enclosing OneOf - * @constructor - * @extends ProtoBuf.Reflect.T - */ - var Field = function(builder, message, rule, type, name, id, options, oneof) { - T.call(this, builder, message, name); - - /** - * @override - */ - this.className = "Message.Field"; - - /** - * Message field required flag. - * @type {boolean} - * @expose - */ - this.required = rule === "required"; - - /** - * Message field repeated flag. - * @type {boolean} - * @expose - */ - this.repeated = rule === "repeated"; - - /** - * Message field type. Type reference string if unresolved, protobuf type if resolved. - * @type {string|{name: string, wireType: number}} - * @expose - */ - this.type = type; - - /** - * Resolved type reference inside the global namespace. - * @type {ProtoBuf.Reflect.T|null} - * @expose - */ - this.resolvedType = null; - - /** - * Unique message field id. - * @type {number} - * @expose - */ - this.id = id; - - /** - * Message field options. - * @type {!Object.} - * @dict - * @expose - */ - this.options = options || {}; - - /** - * Default value. - * @type {*} - * @expose - */ - this.defaultValue = null; - - /** - * Enclosing OneOf. - * @type {?ProtoBuf.Reflect.Message.OneOf} - * @expose - */ - this.oneof = oneof || null; - - /** - * Original field name. - * @type {string} - * @expose - */ - this.originalName = this.name; // Used to revert camelcase transformation on naming collisions - - // Convert field names to camel case notation if the override is set - if (this.builder.options['convertFieldsToCamelCase'] && !(this instanceof Message.ExtensionField)) - this.name = Field._toCamelCase(this.name); - }; - - /** - * Converts a field name to camel case. - * @param {string} name Likely underscore notated name - * @returns {string} Camel case notated name - * @private - */ - Field._toCamelCase = function(name) { - return name.replace(/_([a-zA-Z])/g, function($0, $1) { - return $1.toUpperCase(); - }); - }; - - /** - * @alias ProtoBuf.Reflect.Message.Field.prototype - * @inner - */ - var FieldPrototype = Field.prototype = Object.create(T.prototype); - - /** - * Builds the field. - * @override - * @expose - */ - FieldPrototype.build = function() { - this.defaultValue = typeof this.options['default'] !== 'undefined' - ? this.verifyValue(this.options['default']) : null; - }; - - /** - * Makes a Long from a value. - * @param {{low: number, high: number, unsigned: boolean}|string|number} value Value - * @param {boolean=} unsigned Whether unsigned or not, defaults to reuse it from Long-like objects or to signed for - * strings and numbers - * @returns {!Long} - * @throws {Error} If the value cannot be converted to a Long - * @inner - */ - function mkLong(value, unsigned) { - if (value && typeof value.low === 'number' && typeof value.high === 'number' && typeof value.unsigned === 'boolean' - && value.low === value.low && value.high === value.high) - return new ProtoBuf.Long(value.low, value.high, typeof unsigned === 'undefined' ? value.unsigned : unsigned); - if (typeof value === 'string') - return ProtoBuf.Long.fromString(value, unsigned || false, 10); - if (typeof value === 'number') - return ProtoBuf.Long.fromNumber(value, unsigned || false); - throw Error("not convertible to Long"); - } - - /** - * Checks if the given value can be set for this field. - * @param {*} value Value to check - * @param {boolean=} skipRepeated Whether to skip the repeated value check or not. Defaults to false. - * @return {*} Verified, maybe adjusted, value - * @throws {Error} If the value cannot be set for this field - * @expose - */ - FieldPrototype.verifyValue = function(value, skipRepeated) { - skipRepeated = skipRepeated || false; - var fail = function(val, msg) { - throw Error("Illegal value for "+this.toString(true)+" of type "+this.type.name+": "+val+" ("+msg+")"); - }.bind(this); - if (value === null) { // NULL values for optional fields - if (this.required) - fail(typeof value, "required"); - return null; - } - var i; - if (this.repeated && !skipRepeated) { // Repeated values as arrays - if (!ProtoBuf.Util.isArray(value)) - value = [value]; - var res = []; - for (i=0; i 4294967295 ? value | 0 : value; - - // Unsigned 32bit - case ProtoBuf.TYPES["uint32"]: - case ProtoBuf.TYPES["fixed32"]: - if (typeof value !== 'number' || (value === value && value % 1 !== 0)) - fail(typeof value, "not an integer"); - return value < 0 ? value >>> 0 : value; - - // Signed 64bit - case ProtoBuf.TYPES["int64"]: - case ProtoBuf.TYPES["sint64"]: - case ProtoBuf.TYPES["sfixed64"]: { - if (ProtoBuf.Long) - try { - return mkLong(value, false); - } catch (e) { - fail(typeof value, e.message); - } - else - fail(typeof value, "requires Long.js"); - } - - // Unsigned 64bit - case ProtoBuf.TYPES["uint64"]: - case ProtoBuf.TYPES["fixed64"]: { - if (ProtoBuf.Long) - try { - return mkLong(value, true); - } catch (e) { - fail(typeof value, e.message); - } - else - fail(typeof value, "requires Long.js"); - } - - // Bool - case ProtoBuf.TYPES["bool"]: - if (typeof value !== 'boolean') - fail(typeof value, "not a boolean"); - return value; - - // Float - case ProtoBuf.TYPES["float"]: - case ProtoBuf.TYPES["double"]: - if (typeof value !== 'number') - fail(typeof value, "not a number"); - return value; - - // Length-delimited string - case ProtoBuf.TYPES["string"]: - if (typeof value !== 'string' && !(value && value instanceof String)) - fail(typeof value, "not a string"); - return ""+value; // Convert String object to string - - // Length-delimited bytes - case ProtoBuf.TYPES["bytes"]: - if (ByteBuffer.isByteBuffer(value)) - return value; - return ByteBuffer.wrap(value, "base64"); - - // Constant enum value - case ProtoBuf.TYPES["enum"]: { - var values = this.resolvedType.getChildren(Enum.Value); - for (i=0; i= 0) { - // "All of the elements of the field are packed into a single key-value pair with wire type 2 - // (length-delimited). Each element is encoded the same way it would be normally, except without a - // tag preceding it." - buffer.writeVarint32((this.id << 3) | ProtoBuf.WIRE_TYPES.LDELIM); - buffer.ensureCapacity(buffer.offset += 1); // We do not know the length yet, so let's assume a varint of length 1 - var start = buffer.offset; // Remember where the contents begin - for (i=0; i 1) { // We need to move the contents - var contents = buffer.slice(start, buffer.offset); - start += varintLen-1; - buffer.offset = start; - buffer.append(contents); - } - buffer.writeVarint32(len, start-varintLen); - } else { - // "If your message definition has repeated elements (without the [packed=true] option), the encoded - // message has zero or more key-value pairs with the same tag number" - for (i=0; i= 0) { - n += ByteBuffer.calculateVarint32((this.id << 3) | ProtoBuf.WIRE_TYPES.LDELIM); - ni = 0; - for (i=0; i= 0) { - if (!skipRepeated) { - nBytes = buffer.readVarint32(); - nBytes = buffer.offset + nBytes; // Limit - var values = []; - while (buffer.offset < nBytes) - values.push(this.decode(this.type.wireType, buffer, true)); - return values; - } - // Read the next value otherwise... - } - switch (this.type) { - // 32bit signed varint - case ProtoBuf.TYPES["int32"]: - return buffer.readVarint32() | 0; - - // 32bit unsigned varint - case ProtoBuf.TYPES["uint32"]: - return buffer.readVarint32() >>> 0; - - // 32bit signed varint zig-zag - case ProtoBuf.TYPES["sint32"]: - return buffer.readVarint32ZigZag() | 0; - - // Fixed 32bit unsigned - case ProtoBuf.TYPES["fixed32"]: - return buffer.readUint32() >>> 0; - - case ProtoBuf.TYPES["sfixed32"]: - return buffer.readInt32() | 0; - - // 64bit signed varint - case ProtoBuf.TYPES["int64"]: - return buffer.readVarint64(); - - // 64bit unsigned varint - case ProtoBuf.TYPES["uint64"]: - return buffer.readVarint64().toUnsigned(); - - // 64bit signed varint zig-zag - case ProtoBuf.TYPES["sint64"]: - return buffer.readVarint64ZigZag(); - - // Fixed 64bit unsigned - case ProtoBuf.TYPES["fixed64"]: - return buffer.readUint64(); - - // Fixed 64bit signed - case ProtoBuf.TYPES["sfixed64"]: - return buffer.readInt64(); - - // Bool varint - case ProtoBuf.TYPES["bool"]: - return !!buffer.readVarint32(); - - // Constant enum value (varint) - case ProtoBuf.TYPES["enum"]: - // The following Builder.Message#set will already throw - return buffer.readVarint32(); - - // 32bit float - case ProtoBuf.TYPES["float"]: - return buffer.readFloat(); - - // 64bit float - case ProtoBuf.TYPES["double"]: - return buffer.readDouble(); - - // Length-delimited string - case ProtoBuf.TYPES["string"]: - return buffer.readVString(); - - // Length-delimited bytes - case ProtoBuf.TYPES["bytes"]: { - nBytes = buffer.readVarint32(); - if (buffer.remaining() < nBytes) - throw Error("Illegal number of bytes for "+this.toString(true)+": "+nBytes+" required but got only "+buffer.remaining()); - value = buffer.clone(); // Offset already set - value.limit = value.offset+nBytes; - buffer.offset += nBytes; - return value; - } - - // Length-delimited embedded message - case ProtoBuf.TYPES["message"]: { - nBytes = buffer.readVarint32(); - return this.resolvedType.decode(buffer, nBytes); - } - - // Legacy group - case ProtoBuf.TYPES["group"]: - return this.resolvedType.decode(buffer, -1, this.id); - } - - // We should never end here - throw Error("[INTERNAL] Illegal wire type for "+this.toString(true)+": "+wireType); - }; - - /** - * @alias ProtoBuf.Reflect.Message.Field - * @expose - */ - Reflect.Message.Field = Field; - - /** - * Constructs a new Message ExtensionField. - * @exports ProtoBuf.Reflect.Message.ExtensionField - * @param {!ProtoBuf.Builder} builder Builder reference - * @param {!ProtoBuf.Reflect.Message} message Message reference - * @param {string} rule Rule, one of requried, optional, repeated - * @param {string} type Data type, e.g. int32 - * @param {string} name Field name - * @param {number} id Unique field id - * @param {Object.=} options Options - * @constructor - * @extends ProtoBuf.Reflect.Message.Field - */ - var ExtensionField = function(builder, message, rule, type, name, id, options) { - Field.call(this, builder, message, rule, type, name, id, options); - - /** - * Extension reference. - * @type {!ProtoBuf.Reflect.Extension} - * @expose - */ - this.extension; - }; - - // Extends Field - ExtensionField.prototype = Object.create(Field.prototype); - - /** - * @alias ProtoBuf.Reflect.Message.ExtensionField - * @expose - */ - Reflect.Message.ExtensionField = ExtensionField; - - /** - * Constructs a new Message OneOf. - * @exports ProtoBuf.Reflect.Message.OneOf - * @param {!ProtoBuf.Builder} builder Builder reference - * @param {!ProtoBuf.Reflect.Message} message Message reference - * @param {string} name OneOf name - * @constructor - * @extends ProtoBuf.Reflect.T - */ - var OneOf = function(builder, message, name) { - T.call(this, builder, message, name); - - /** - * Enclosed fields. - * @type {!Array.} - * @expose - */ - this.fields = []; - }; - - /** - * @alias ProtoBuf.Reflect.Message.OneOf - * @expose - */ - Reflect.Message.OneOf = OneOf; - - /** - * Constructs a new Enum. - * @exports ProtoBuf.Reflect.Enum - * @param {!ProtoBuf.Builder} builder Builder reference - * @param {!ProtoBuf.Reflect.T} parent Parent Reflect object - * @param {string} name Enum name - * @param {Object.=} options Enum options - * @constructor - * @extends ProtoBuf.Reflect.Namespace - */ - var Enum = function(builder, parent, name, options) { - Namespace.call(this, builder, parent, name, options); - - /** - * @override - */ - this.className = "Enum"; - - /** - * Runtime enum object. - * @type {Object.|null} - * @expose - */ - this.object = null; - }; - - /** - * @alias ProtoBuf.Reflect.Enum.prototype - * @inner - */ - var EnumPrototype = Enum.prototype = Object.create(Namespace.prototype); - - /** - * Builds this enum and returns the runtime counterpart. - * @return {Object} - * @expose - */ - EnumPrototype.build = function() { - var enm = {}, - values = this.getChildren(Enum.Value); - for (var i=0, k=values.length; i=} options Options - * @constructor - * @extends ProtoBuf.Reflect.Namespace - */ - var Service = function(builder, root, name, options) { - Namespace.call(this, builder, root, name, options); - - /** - * @override - */ - this.className = "Service"; - - /** - * Built runtime service class. - * @type {?function(new:ProtoBuf.Builder.Service)} - */ - this.clazz = null; - }; - - /** - * @alias ProtoBuf.Reflect.Service.prototype - * @inner - */ - var ServicePrototype = Service.prototype = Object.create(Namespace.prototype); - - /** - * Builds the service and returns the runtime counterpart, which is a fully functional class. - * @see ProtoBuf.Builder.Service - * @param {boolean=} rebuild Whether to rebuild or not - * @return {Function} Service class - * @throws {Error} If the message cannot be built - * @expose - */ - ServicePrototype.build = function(rebuild) { - if (this.clazz && !rebuild) - return this.clazz; - - // Create the runtime Service class in its own scope - return this.clazz = (function(ProtoBuf, T) { - - /** - * Constructs a new runtime Service. - * @name ProtoBuf.Builder.Service - * @param {function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))=} rpcImpl RPC implementation receiving the method name and the message - * @class Barebone of all runtime services. - * @constructor - * @throws {Error} If the service cannot be created - */ - var Service = function(rpcImpl) { - ProtoBuf.Builder.Service.call(this); - - /** - * Service implementation. - * @name ProtoBuf.Builder.Service#rpcImpl - * @type {!function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))} - * @expose - */ - this.rpcImpl = rpcImpl || function(name, msg, callback) { - // This is what a user has to implement: A function receiving the method name, the actual message to - // send (type checked) and the callback that's either provided with the error as its first - // argument or null and the actual response message. - setTimeout(callback.bind(this, Error("Not implemented, see: https://github.com/dcodeIO/ProtoBuf.js/wiki/Services")), 0); // Must be async! - }; - }; - - /** - * @alias ProtoBuf.Builder.Service.prototype - * @inner - */ - var ServicePrototype = Service.prototype = Object.create(ProtoBuf.Builder.Service.prototype); - - if (Object.defineProperty) - Object.defineProperty(Service, "$options", { "value": T.buildOpt() }), - Object.defineProperty(ServicePrototype, "$options", { "value": Service["$options"] }); - - /** - * Asynchronously performs an RPC call using the given RPC implementation. - * @name ProtoBuf.Builder.Service.[Method] - * @function - * @param {!function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))} rpcImpl RPC implementation - * @param {ProtoBuf.Builder.Message} req Request - * @param {function(Error, (ProtoBuf.Builder.Message|ByteBuffer|Buffer|string)=)} callback Callback receiving - * the error if any and the response either as a pre-parsed message or as its raw bytes - * @abstract - */ - - /** - * Asynchronously performs an RPC call using the instance's RPC implementation. - * @name ProtoBuf.Builder.Service#[Method] - * @function - * @param {ProtoBuf.Builder.Message} req Request - * @param {function(Error, (ProtoBuf.Builder.Message|ByteBuffer|Buffer|string)=)} callback Callback receiving - * the error if any and the response either as a pre-parsed message or as its raw bytes - * @abstract - */ - - var rpc = T.getChildren(ProtoBuf.Reflect.Service.RPCMethod); - for (var i=0; i=} options Options - * @constructor - * @extends ProtoBuf.Reflect.T - */ - var Method = function(builder, svc, name, options) { - T.call(this, builder, svc, name); - - /** - * @override - */ - this.className = "Service.Method"; - - /** - * Options. - * @type {Object.} - * @expose - */ - this.options = options || {}; - }; - - /** - * @alias ProtoBuf.Reflect.Service.Method.prototype - * @inner - */ - var MethodPrototype = Method.prototype = Object.create(T.prototype); - - /** - * Builds the method's '$options' property. - * @name ProtoBuf.Reflect.Service.Method#buildOpt - * @function - * @return {Object.} - */ - MethodPrototype.buildOpt = NamespacePrototype.buildOpt; - - /** - * @alias ProtoBuf.Reflect.Service.Method - * @expose - */ - Reflect.Service.Method = Method; - - /** - * RPC service method. - * @exports ProtoBuf.Reflect.Service.RPCMethod - * @param {!ProtoBuf.Builder} builder Builder reference - * @param {!ProtoBuf.Reflect.Service} svc Service - * @param {string} name Method name - * @param {string} request Request message name - * @param {string} response Response message name - * @param {Object.=} options Options - * @constructor - * @extends ProtoBuf.Reflect.Service.Method - */ - var RPCMethod = function(builder, svc, name, request, response, options) { - Method.call(this, builder, svc, name, options); - - /** - * @override - */ - this.className = "Service.RPCMethod"; - - /** - * Request message name. - * @type {string} - * @expose - */ - this.requestName = request; - - /** - * Response message name. - * @type {string} - * @expose - */ - this.responseName = response; - - /** - * Resolved request message type. - * @type {ProtoBuf.Reflect.Message} - * @expose - */ - this.resolvedRequestType = null; - - /** - * Resolved response message type. - * @type {ProtoBuf.Reflect.Message} - * @expose - */ - this.resolvedResponseType = null; - }; - - // Extends Method - RPCMethod.prototype = Object.create(Method.prototype); - - /** - * @alias ProtoBuf.Reflect.Service.RPCMethod - * @expose - */ - Reflect.Service.RPCMethod = RPCMethod; - - return Reflect; - - })(ProtoBuf); - - /** - * @alias ProtoBuf.Builder - * @expose - */ - ProtoBuf.Builder = (function(ProtoBuf, Lang, Reflect) { - "use strict"; - - /** - * Constructs a new Builder. - * @exports ProtoBuf.Builder - * @class Provides the functionality to build protocol messages. - * @param {Object.=} options Options - * @constructor - */ - var Builder = function(options) { - - /** - * Namespace. - * @type {ProtoBuf.Reflect.Namespace} - * @expose - */ - this.ns = new Reflect.Namespace(this, null, ""); // Global namespace - - /** - * Namespace pointer. - * @type {ProtoBuf.Reflect.T} - * @expose - */ - this.ptr = this.ns; - - /** - * Resolved flag. - * @type {boolean} - * @expose - */ - this.resolved = false; - - /** - * The current building result. - * @type {Object.|null} - * @expose - */ - this.result = null; - - /** - * Imported files. - * @type {Array.} - * @expose - */ - this.files = {}; - - /** - * Import root override. - * @type {?string} - * @expose - */ - this.importRoot = null; - - /** - * Options. - * @type {!Object.} - * @expose - */ - this.options = options || {}; - }; - - /** - * @alias ProtoBuf.Builder.prototype - * @inner - */ - var BuilderPrototype = Builder.prototype; - - /** - * Resets the pointer to the root namespace. - * @expose - */ - BuilderPrototype.reset = function() { - this.ptr = this.ns; - }; - - /** - * Defines a package on top of the current pointer position and places the pointer on it. - * @param {string} pkg - * @param {Object.=} options - * @return {ProtoBuf.Builder} this - * @throws {Error} If the package name is invalid - * @expose - */ - BuilderPrototype.define = function(pkg, options) { - if (typeof pkg !== 'string' || !Lang.TYPEREF.test(pkg)) - throw Error("Illegal package: "+pkg); - var part = pkg.split("."), i; - for (i=0; i} def Definition - * @return {boolean} true if valid, else false - * @expose - */ - Builder.isValidMessage = function(def) { - // Messages require a string name - if (typeof def["name"] !== 'string' || !Lang.NAME.test(def["name"])) - return false; - // Messages must not contain values (that'd be an enum) or methods (that'd be a service) - if (typeof def["values"] !== 'undefined' || typeof def["rpc"] !== 'undefined') - return false; - // Fields, enums and messages are arrays if provided - var i; - if (typeof def["fields"] !== 'undefined') { - if (!ProtoBuf.Util.isArray(def["fields"])) - return false; - var ids = [], id; // IDs must be unique - for (i=0; i= 0) - return false; - ids.push(id); - } - ids = null; - } - if (typeof def["enums"] !== 'undefined') { - if (!ProtoBuf.Util.isArray(def["enums"])) - return false; - for (i=0; i - var keys = Object.keys(def["options"]); - for (var i=0, key; i>} defs Messages, enums or services to create - * @return {ProtoBuf.Builder} this - * @throws {Error} If a message definition is invalid - * @expose - */ - BuilderPrototype.create = function(defs) { - if (!defs) - return this; // Nothing to create - if (!ProtoBuf.Util.isArray(defs)) - defs = [defs]; - if (defs.length == 0) - return this; - - // It's quite hard to keep track of scopes and memory here, so let's do this iteratively. - var stack = []; - stack.push(defs); // One level [a, b, c] - while (stack.length > 0) { - defs = stack.pop(); - if (ProtoBuf.Util.isArray(defs)) { // Stack always contains entire namespaces - while (defs.length > 0) { - var def = defs.shift(); // Namespace always contains an array of messages, enums and services - if (Builder.isValidMessage(def)) { - var obj = new Reflect.Message(this, this.ptr, def["name"], def["options"], def["isGroup"]); - // Create OneOfs - var oneofs = {}; - if (def["oneofs"]) { - var keys = Object.keys(def["oneofs"]); - for (var i=0, k=keys.length; i 0) { - for (i=0, k=def["fields"].length; i 0) - for (i=0; i 0) - for (i=0; i ProtoBuf.ID_MAX) - obj.extensions[1] = ProtoBuf.ID_MAX; - } - this.ptr.addChild(obj); // Add to current namespace - if (subObj.length > 0) { - stack.push(defs); // Push the current level back - defs = subObj; // Continue processing sub level - subObj = null; - this.ptr = obj; // And move the pointer to this namespace - obj = null; - continue; - } - subObj = null; - obj = null; - } else if (Builder.isValidEnum(def)) { - obj = new Reflect.Enum(this, this.ptr, def["name"], def["options"]); - for (i=0; i obj.extensions[1]) - throw Error("Illegal extended field id in message "+obj.name+": "+def['fields'][i]['id']+" ("+obj.extensions.join(' to ')+" expected)"); - // Convert extension field names to camel case notation if the override is set - var name = def["fields"][i]["name"]; - if (this.options['convertFieldsToCamelCase']) - name = Reflect.Message.Field._toCamelCase(def["fields"][i]["name"]); - // see #161: Extensions use their fully qualified name as their runtime key and... - fld = new Reflect.Message.ExtensionField(this, obj, def["fields"][i]["rule"], def["fields"][i]["type"], this.ptr.fqn()+'.'+name, def["fields"][i]["id"], def["fields"][i]["options"]); - // ...are added on top of the current namespace as an extension which is used for - // resolving their type later on (the extension always keeps the original name to - // prevent naming collisions) - var ext = new Reflect.Extension(this, this.ptr, def["fields"][i]["name"], fld); - fld.extension = ext; - this.ptr.addChild(ext); - obj.addChild(fld); - } - } else if (!/\.?google\.protobuf\./.test(def["ref"])) // Silently skip internal extensions - throw Error("Extended message "+def["ref"]+" is not defined"); - } else - throw Error("Not a valid definition: "+JSON.stringify(def)); - def = null; - } - // Break goes here - } else - throw Error("Not a valid namespace: "+JSON.stringify(defs)); - defs = null; - this.ptr = this.ptr.parent; // This namespace is s done - } - this.resolved = false; // Require re-resolve - this.result = null; // Require re-build - return this; - }; - - /** - * Imports another definition into this builder. - * @param {Object.} json Parsed import - * @param {(string|{root: string, file: string})=} filename Imported file name - * @return {ProtoBuf.Builder} this - * @throws {Error} If the definition or file cannot be imported - * @expose - */ - BuilderPrototype["import"] = function(json, filename) { - if (typeof filename === 'string') { - if (ProtoBuf.Util.IS_NODE) - filename = require("path")['resolve'](filename); - if (this.files[filename] === true) { - this.reset(); - return this; // Skip duplicate imports - } - this.files[filename] = true; - } - if (!!json['imports'] && json['imports'].length > 0) { - var importRoot, delim = '/', resetRoot = false; - if (typeof filename === 'object') { // If an import root is specified, override - this.importRoot = filename["root"]; resetRoot = true; // ... and reset afterwards - importRoot = this.importRoot; - filename = filename["file"]; - if (importRoot.indexOf("\\") >= 0 || filename.indexOf("\\") >= 0) delim = '\\'; - } else if (typeof filename === 'string') { - if (this.importRoot) // If import root is overridden, use it - importRoot = this.importRoot; - else { // Otherwise compute from filename - if (filename.indexOf("/") >= 0) { // Unix - importRoot = filename.replace(/\/[^\/]*$/, ""); - if (/* /file.proto */ importRoot === "") - importRoot = "/"; - } else if (filename.indexOf("\\") >= 0) { // Windows - importRoot = filename.replace(/\\[^\\]*$/, ""); - delim = '\\'; - } else - importRoot = "."; - } - } else - importRoot = null; - - for (var i=0; i= 0) - return false; - ids.push(id); - } - ids = null; - } - return true; - }; - - /** - * Resolves all namespace objects. - * @throws {Error} If a type cannot be resolved - * @expose - */ - BuilderPrototype.resolveAll = function() { - // Resolve all reflected objects - var res; - if (this.ptr == null || typeof this.ptr.type === 'object') - return; // Done (already resolved) - if (this.ptr instanceof Reflect.Namespace) { - // Build all children - var children = this.ptr.children; - for (var i= 0, k=children.length; i} - * @throws {Error} If a type could not be resolved - * @expose - */ - BuilderPrototype.build = function(path) { - this.reset(); - if (!this.resolved) - this.resolveAll(), - this.resolved = true, - this.result = null; // Require re-build - if (this.result == null) // (Re-)Build - this.result = this.ns.build(); - if (!path) - return this.result; - else { - var part = path.split("."); - var ptr = this.result; // Build namespace pointer (no hasChild etc.) - for (var i=0; i=} options Builder options, defaults to global options set on ProtoBuf - * @return {!ProtoBuf.Builder} Builder - * @expose - */ - ProtoBuf.newBuilder = function(options) { - options = options || {}; - if (typeof options['convertFieldsToCamelCase'] === 'undefined') - options['convertFieldsToCamelCase'] = ProtoBuf.convertFieldsToCamelCase; - if (typeof options['populateAccessors'] === 'undefined') - options['populateAccessors'] = ProtoBuf.populateAccessors; - return new ProtoBuf.Builder(options); - }; - - /** - * Loads a .json definition and returns the Builder. - * @param {!*|string} json JSON definition - * @param {(ProtoBuf.Builder|string|{root: string, file: string})=} builder Builder to append to. Will create a new one if omitted. - * @param {(string|{root: string, file: string})=} filename The corresponding file name if known. Must be specified for imports. - * @return {ProtoBuf.Builder} Builder to create new messages - * @throws {Error} If the definition cannot be parsed or built - * @expose - */ - ProtoBuf.loadJson = function(json, builder, filename) { - if (typeof builder === 'string' || (builder && typeof builder["file"] === 'string' && typeof builder["root"] === 'string')) - filename = builder, - builder = null; - if (!builder || typeof builder !== 'object') - builder = ProtoBuf.newBuilder(); - if (typeof json === 'string') - json = JSON.parse(json); - builder["import"](json, filename); - builder.resolveAll(); - return builder; - }; - - /** - * Loads a .json file and returns the Builder. - * @param {string|!{root: string, file: string}} filename Path to json file or an object specifying 'file' with - * an overridden 'root' path for all imported files. - * @param {function(?Error, !ProtoBuf.Builder=)=} callback Callback that will receive `null` as the first and - * the Builder as its second argument on success, otherwise the error as its first argument. If omitted, the - * file will be read synchronously and this function will return the Builder. - * @param {ProtoBuf.Builder=} builder Builder to append to. Will create a new one if omitted. - * @return {?ProtoBuf.Builder|undefined} The Builder if synchronous (no callback specified, will be NULL if the - * request has failed), else undefined - * @expose - */ - ProtoBuf.loadJsonFile = function(filename, callback, builder) { - if (callback && typeof callback === 'object') - builder = callback, - callback = null; - else if (!callback || typeof callback !== 'function') - callback = null; - if (callback) - return ProtoBuf.Util.fetch(typeof filename === 'string' ? filename : filename["root"]+"/"+filename["file"], function(contents) { - if (contents === null) { - callback(Error("Failed to fetch file")); - return; - } - try { - callback(null, ProtoBuf.loadJson(JSON.parse(contents), builder, filename)); - } catch (e) { - callback(e); - } - }); - var contents = ProtoBuf.Util.fetch(typeof filename === 'object' ? filename["root"]+"/"+filename["file"] : filename); - return contents === null ? null : ProtoBuf.loadJson(JSON.parse(contents), builder, filename); - }; - - return ProtoBuf; - } - - /* CommonJS */ if (typeof require === 'function' && typeof module === 'object' && module && typeof exports === 'object' && exports) - module['exports'] = init(require("bytebuffer")); - /* AMD */ else if (typeof define === 'function' && define["amd"]) - define(["ByteBuffer"], init); - /* Global */ else - (global["dcodeIO"] = global["dcodeIO"] || {})["ProtoBuf"] = init(global["dcodeIO"]["ByteBuffer"]); - -})(this); diff --git a/libtextsecure/protobufs.js b/libtextsecure/protobufs.js deleted file mode 100644 index 4ef228edc5..0000000000 --- a/libtextsecure/protobufs.js +++ /dev/null @@ -1,42 +0,0 @@ -(function() { - 'use strict'; - window.textsecure = window.textsecure || {}; - window.textsecure.protobuf = {}; - - function loadProtoBufs(filename) { - return dcodeIO.ProtoBuf.loadProtoFile( - { root: window.PROTO_ROOT, file: filename }, - function(error, result) { - if (error) { - var text = - 'Error loading protos from ' + - filename + - ' (root: ' + - window.PROTO_ROOT + - ') ' + - (error && error.stack ? error.stack : error); - console.log(text); - throw error; - } - var protos = result.build('signalservice'); - if (!protos) { - var text = - 'Error loading protos from ' + - filename + - ' (root: ' + - window.PROTO_ROOT + - ')'; - console.log(text); - throw new Error(text); - } - for (var protoName in protos) { - textsecure.protobuf[protoName] = protos[protoName]; - } - } - ); - } - - loadProtoBufs('SignalService.proto'); - loadProtoBufs('SubProtocol.proto'); - loadProtoBufs('DeviceMessages.proto'); -})(); diff --git a/libtextsecure/test/index.html b/libtextsecure/test/index.html index acb9304363..d61c915907 100644 --- a/libtextsecure/test/index.html +++ b/libtextsecure/test/index.html @@ -18,7 +18,6 @@ - diff --git a/package.json b/package.json index 3213678bac..09dd25e502 100644 --- a/package.json +++ b/package.json @@ -18,10 +18,12 @@ "generate": "yarn icon-gen && yarn grunt", "build": "build --config.extraMetadata.environment=$SIGNAL_ENV", "build-release": "SIGNAL_ENV=production npm run build -- --config.directories.output=release", + "build-libtextsecure-protobuf": "pbjs --target static-module --root signalservice --out libtextsecure/protobuf-compiled.js protos/DeviceMessages.proto protos/SubProtocol.proto protos/SignalService.proto", + "clean-libtextsecure-protobuf": "rm -f libtextsecure/protobuf-compiled.js", "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", + "build-protobuf": "yarn build-libtextsecure-protobuf && yarn build-module-protobuf", + "clean-protobuf": "yarn clean-libtextsecure-protobuf && yarn clean-module-protobuf", "prepare-beta-build": "node prepare_beta_build.js", "prepare-import-build": "node prepare_import_build.js", "publish-to-apt": "NAME=$npm_package_name VERSION=$npm_package_version ./aptly.sh", diff --git a/preload.js b/preload.js index 412456a887..96c2a50e82 100644 --- a/preload.js +++ b/preload.js @@ -9,6 +9,7 @@ const Attachment = require('./js/modules/types/attachment'); const Attachments = require('./app/attachments'); const Message = require('./js/modules/types/message'); const { deferredToPromise } = require('./js/modules/deferred_to_promise'); +const { SignalService } = require('./ts/protobuf'); const { app } = electron.remote; @@ -98,11 +99,13 @@ window.filesize = require('filesize'); window.libphonenumber = require('google-libphonenumber').PhoneNumberUtil.getInstance(); window.libphonenumber.PhoneNumberFormat = require('google-libphonenumber').PhoneNumberFormat; window.loadImage = require('blueimp-load-image'); - window.nodeBuffer = Buffer; window.nodeFetch = require('node-fetch'); window.ProxyAgent = require('proxy-agent'); +window.textsecure = window.textsecure || {}; +window.textsecure.protobuf = SignalService; + // Note: when modifying this file, consider whether our React Components or Backbone Views // will need these things to render in the Style Guide. If so, go update one of these // two locations: From ba4d8952c6f3e52be9ed4752e07a334b7efef2a1 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Mon, 7 May 2018 19:12:53 -0400 Subject: [PATCH 23/23] Revert "Use protocol buffer module for `libtextsecure`" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This does not work with `ArrayBuffer` out of the box and will need to be investigated and tested more before switching. Some APIs also seemed to have changed: - Before (OOP): `new textsecure.protobuf.WebSocketMessage(…).encode();` - After (functional): ``` textsecure.protobuf.WebSocketMessage.encode( new textsecure.protobuf.WebSocketMessage(…) ); ``` This reverts commit d758119d9de4f99742ea941e86180b1d600c2f22. --- Gruntfile.js | 1 + bower.json | 8 +- components/protobuf/dist/ProtoBuf.js | 4335 ++++++++++++++++++++++++++ libtextsecure/protobufs.js | 42 + libtextsecure/test/index.html | 1 + package.json | 6 +- preload.js | 5 +- 7 files changed, 4389 insertions(+), 9 deletions(-) create mode 100644 components/protobuf/dist/ProtoBuf.js create mode 100644 libtextsecure/protobufs.js diff --git a/Gruntfile.js b/Gruntfile.js index 5ed7cc4932..4b23b8bdad 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -56,6 +56,7 @@ module.exports = function(grunt) { 'libtextsecure/storage/user.js', 'libtextsecure/storage/groups.js', 'libtextsecure/storage/unprocessed.js', + 'libtextsecure/protobufs.js', 'libtextsecure/helpers.js', 'libtextsecure/stringview.js', 'libtextsecure/event_target.js', diff --git a/bower.json b/bower.json index 22f96ec8f8..d9fbcbff04 100644 --- a/bower.json +++ b/bower.json @@ -8,6 +8,7 @@ "jquery": "liliakai/jquery#2.1.1-ajax-nativetransport", "underscore": "~1.7.0", "backbone": "~1.1.2", + "protobuf": "~3.8.0", "mustache": "~0.8.2", "qrcode": "https://github.com/davidshimjs/qrcodejs.git#1c78ccd71", "indexeddb-backbonejs-adapter": "*", @@ -32,6 +33,9 @@ "bytebuffer": [ "dist/ByteBufferAB.js" ], + "protobuf": [ + "dist/ProtoBuf.js" + ], "mustache": [ "mustache.js" ], @@ -85,6 +89,7 @@ "jquery", "long", "bytebuffer", + "protobuf", "mustache", "underscore", "backbone", @@ -97,7 +102,8 @@ ], "libtextsecure": [ "long", - "bytebuffer" + "bytebuffer", + "protobuf" ] } } diff --git a/components/protobuf/dist/ProtoBuf.js b/components/protobuf/dist/ProtoBuf.js new file mode 100644 index 0000000000..0e12340a7f --- /dev/null +++ b/components/protobuf/dist/ProtoBuf.js @@ -0,0 +1,4335 @@ +/* + Copyright 2013 Daniel Wirtz + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +/** + * @license ProtoBuf.js (c) 2013 Daniel Wirtz + * Released under the Apache License, Version 2.0 + * see: https://github.com/dcodeIO/ProtoBuf.js for details + */ +(function(global) { + "use strict"; + + function init(ByteBuffer) { + + /** + * The ProtoBuf namespace. + * @exports ProtoBuf + * @namespace + * @expose + */ + var ProtoBuf = {}; + + /** + * ProtoBuf.js version. + * @type {string} + * @const + * @expose + */ + ProtoBuf.VERSION = "3.8.2"; + + /** + * Wire types. + * @type {Object.} + * @const + * @expose + */ + ProtoBuf.WIRE_TYPES = {}; + + /** + * Varint wire type. + * @type {number} + * @expose + */ + ProtoBuf.WIRE_TYPES.VARINT = 0; + + /** + * Fixed 64 bits wire type. + * @type {number} + * @const + * @expose + */ + ProtoBuf.WIRE_TYPES.BITS64 = 1; + + /** + * Length delimited wire type. + * @type {number} + * @const + * @expose + */ + ProtoBuf.WIRE_TYPES.LDELIM = 2; + + /** + * Start group wire type. + * @type {number} + * @const + * @expose + */ + ProtoBuf.WIRE_TYPES.STARTGROUP = 3; + + /** + * End group wire type. + * @type {number} + * @const + * @expose + */ + ProtoBuf.WIRE_TYPES.ENDGROUP = 4; + + /** + * Fixed 32 bits wire type. + * @type {number} + * @const + * @expose + */ + ProtoBuf.WIRE_TYPES.BITS32 = 5; + + /** + * Packable wire types. + * @type {!Array.} + * @const + * @expose + */ + ProtoBuf.PACKABLE_WIRE_TYPES = [ + ProtoBuf.WIRE_TYPES.VARINT, + ProtoBuf.WIRE_TYPES.BITS64, + ProtoBuf.WIRE_TYPES.BITS32 + ]; + + /** + * Types. + * @dict + * @type {Object.} + * @const + * @expose + */ + ProtoBuf.TYPES = { + // According to the protobuf spec. + "int32": { + name: "int32", + wireType: ProtoBuf.WIRE_TYPES.VARINT + }, + "uint32": { + name: "uint32", + wireType: ProtoBuf.WIRE_TYPES.VARINT + }, + "sint32": { + name: "sint32", + wireType: ProtoBuf.WIRE_TYPES.VARINT + }, + "int64": { + name: "int64", + wireType: ProtoBuf.WIRE_TYPES.VARINT + }, + "uint64": { + name: "uint64", + wireType: ProtoBuf.WIRE_TYPES.VARINT + }, + "sint64": { + name: "sint64", + wireType: ProtoBuf.WIRE_TYPES.VARINT + }, + "bool": { + name: "bool", + wireType: ProtoBuf.WIRE_TYPES.VARINT + }, + "double": { + name: "double", + wireType: ProtoBuf.WIRE_TYPES.BITS64 + }, + "string": { + name: "string", + wireType: ProtoBuf.WIRE_TYPES.LDELIM + }, + "bytes": { + name: "bytes", + wireType: ProtoBuf.WIRE_TYPES.LDELIM + }, + "fixed32": { + name: "fixed32", + wireType: ProtoBuf.WIRE_TYPES.BITS32 + }, + "sfixed32": { + name: "sfixed32", + wireType: ProtoBuf.WIRE_TYPES.BITS32 + }, + "fixed64": { + name: "fixed64", + wireType: ProtoBuf.WIRE_TYPES.BITS64 + }, + "sfixed64": { + name: "sfixed64", + wireType: ProtoBuf.WIRE_TYPES.BITS64 + }, + "float": { + name: "float", + wireType: ProtoBuf.WIRE_TYPES.BITS32 + }, + "enum": { + name: "enum", + wireType: ProtoBuf.WIRE_TYPES.VARINT + }, + "message": { + name: "message", + wireType: ProtoBuf.WIRE_TYPES.LDELIM + }, + "group": { + name: "group", + wireType: ProtoBuf.WIRE_TYPES.STARTGROUP + } + }; + + /** + * Minimum field id. + * @type {number} + * @const + * @expose + */ + ProtoBuf.ID_MIN = 1; + + /** + * Maximum field id. + * @type {number} + * @const + * @expose + */ + ProtoBuf.ID_MAX = 0x1FFFFFFF; + + /** + * @type {!function(new: ByteBuffer, ...[*])} + * @expose + */ + ProtoBuf.ByteBuffer = ByteBuffer; + + /** + * @type {?function(new: Long, ...[*])} + * @expose + */ + ProtoBuf.Long = ByteBuffer.Long || null; + + /** + * If set to `true`, field names will be converted from underscore notation to camel case. Defaults to `false`. + * Must be set prior to parsing. + * @type {boolean} + * @expose + */ + ProtoBuf.convertFieldsToCamelCase = false; + + /** + * By default, messages are populated with (setX, set_x) accessors for each field. This can be disabled by + * setting this to `false` prior to building messages. + * @type {boolean} + * @expose + */ + ProtoBuf.populateAccessors = true; + + /** + * @alias ProtoBuf.Util + * @expose + */ + ProtoBuf.Util = (function() { + "use strict"; + + // Object.create polyfill + // ref: https://developer.mozilla.org/de/docs/JavaScript/Reference/Global_Objects/Object/create + if (!Object.create) + /** @expose */ + Object.create = function (o) { + if (arguments.length > 1) + throw Error('Object.create polyfill only accepts the first parameter.'); + function F() {} + F.prototype = o; + return new F(); + }; + + /** + * ProtoBuf utilities. + * @exports ProtoBuf.Util + * @namespace + */ + var Util = {}; + + /** + * Flag if running in node (fs is available) or not. + * @type {boolean} + * @const + * @expose + */ + Util.IS_NODE = false; + try { + // There is no reliable way to detect node.js as an environment, so our + // best bet is to feature-detect what we actually need. + Util.IS_NODE = + typeof require === 'function' && + typeof require("fs").readFileSync === 'function' && + typeof require("path").resolve === 'function'; + } catch (e) {} + + /** + * Constructs a XMLHttpRequest object. + * @return {XMLHttpRequest} + * @throws {Error} If XMLHttpRequest is not supported + * @expose + */ + Util.XHR = function() { + // No dependencies please, ref: http://www.quirksmode.org/js/xmlhttp.html + var XMLHttpFactories = [ + function () {return new XMLHttpRequest()}, + function () {return new ActiveXObject("Msxml2.XMLHTTP")}, + function () {return new ActiveXObject("Msxml3.XMLHTTP")}, + function () {return new ActiveXObject("Microsoft.XMLHTTP")} + ]; + /** @type {?XMLHttpRequest} */ + var xhr = null; + for (var i=0;i} + * @expose + */ + ProtoBuf.Lang = { + OPEN: "{", + CLOSE: "}", + OPTOPEN: "[", + OPTCLOSE: "]", + OPTEND: ",", + EQUAL: "=", + END: ";", + STRINGOPEN: '"', + STRINGCLOSE: '"', + STRINGOPEN_SQ: "'", + STRINGCLOSE_SQ: "'", + COPTOPEN: '(', + COPTCLOSE: ')', + DELIM: /[\s\{\}=;\[\],'"\(\)]/g, + // KEYWORD: /^(?:package|option|import|message|enum|extend|service|syntax|extensions|group)$/, + RULE: /^(?:required|optional|repeated)$/, + TYPE: /^(?:double|float|int32|uint32|sint32|int64|uint64|sint64|fixed32|sfixed32|fixed64|sfixed64|bool|string|bytes)$/, + NAME: /^[a-zA-Z_][a-zA-Z_0-9]*$/, + TYPEDEF: /^[a-zA-Z][a-zA-Z_0-9]*$/, + TYPEREF: /^(?:\.?[a-zA-Z_][a-zA-Z_0-9]*)+$/, + FQTYPEREF: /^(?:\.[a-zA-Z][a-zA-Z_0-9]*)+$/, + NUMBER: /^-?(?:[1-9][0-9]*|0|0x[0-9a-fA-F]+|0[0-7]+|([0-9]*\.[0-9]+([Ee][+-]?[0-9]+)?))$/, + NUMBER_DEC: /^(?:[1-9][0-9]*|0)$/, + NUMBER_HEX: /^0x[0-9a-fA-F]+$/, + NUMBER_OCT: /^0[0-7]+$/, + NUMBER_FLT: /^[0-9]*\.[0-9]+([Ee][+-]?[0-9]+)?$/, + ID: /^(?:[1-9][0-9]*|0|0x[0-9a-fA-F]+|0[0-7]+)$/, + NEGID: /^\-?(?:[1-9][0-9]*|0|0x[0-9a-fA-F]+|0[0-7]+)$/, + WHITESPACE: /\s/, + STRING: /(?:"([^"\\]*(?:\\.[^"\\]*)*)")|(?:'([^'\\]*(?:\\.[^'\\]*)*)')/g, + BOOL: /^(?:true|false)$/i + }; + + /** + * @alias ProtoBuf.DotProto + * @expose + */ + ProtoBuf.DotProto = (function(ProtoBuf, Lang) { + "use strict"; + + /** + * Utilities to parse .proto files. + * @exports ProtoBuf.DotProto + * @namespace + */ + var DotProto = {}; + + /** + * Constructs a new Tokenizer. + * @exports ProtoBuf.DotProto.Tokenizer + * @class prototype tokenizer + * @param {string} proto Proto to tokenize + * @constructor + */ + var Tokenizer = function(proto) { + + /** + * Source to parse. + * @type {string} + * @expose + */ + this.source = ""+proto; // In case it's a buffer + + /** + * Current index. + * @type {number} + * @expose + */ + this.index = 0; + + /** + * Current line. + * @type {number} + * @expose + */ + this.line = 1; + + /** + * Stacked values. + * @type {Array} + * @expose + */ + this.stack = []; + + /** + * Whether currently reading a string or not. + * @type {boolean} + * @expose + */ + this.readingString = false; + + /** + * Whatever character ends the string. Either a single or double quote character. + * @type {string} + * @expose + */ + this.stringEndsWith = Lang.STRINGCLOSE; + }; + + /** + * @alias ProtoBuf.DotProto.Tokenizer.prototype + * @inner + */ + var TokenizerPrototype = Tokenizer.prototype; + + /** + * Reads a string beginning at the current index. + * @return {string} The string + * @throws {Error} If it's not a valid string + * @private + */ + TokenizerPrototype._readString = function() { + Lang.STRING.lastIndex = this.index-1; // Include the open quote + var match; + if ((match = Lang.STRING.exec(this.source)) !== null) { + var s = typeof match[1] !== 'undefined' ? match[1] : match[2]; + this.index = Lang.STRING.lastIndex; + this.stack.push(this.stringEndsWith); + return s; + } + throw Error("Unterminated string at line "+this.line+", index "+this.index); + }; + + /** + * Gets the next token and advances by one. + * @return {?string} Token or `null` on EOF + * @throws {Error} If it's not a valid proto file + * @expose + */ + TokenizerPrototype.next = function() { + if (this.stack.length > 0) + return this.stack.shift(); + if (this.index >= this.source.length) + return null; // No more tokens + if (this.readingString) { + this.readingString = false; + return this._readString(); + } + var repeat, last; + do { + repeat = false; + // Strip white spaces + while (Lang.WHITESPACE.test(last = this.source.charAt(this.index))) { + this.index++; + if (last === "\n") + this.line++; + if (this.index === this.source.length) + return null; + } + // Strip comments + if (this.source.charAt(this.index) === '/') { + if (this.source.charAt(++this.index) === '/') { // Single line + while (this.source.charAt(this.index) !== "\n") { + this.index++; + if (this.index == this.source.length) + return null; + } + this.index++; + this.line++; + repeat = true; + } else if (this.source.charAt(this.index) === '*') { /* Block */ + last = ''; + while (last+(last=this.source.charAt(this.index)) !== '*/') { + this.index++; + if (last === "\n") + this.line++; + if (this.index === this.source.length) + return null; + } + this.index++; + repeat = true; + } else + throw Error("Unterminated comment at line "+this.line+": /"+this.source.charAt(this.index)); + } + } while (repeat); + if (this.index === this.source.length) return null; + + // Read the next token + var end = this.index; + Lang.DELIM.lastIndex = 0; + var delim = Lang.DELIM.test(this.source.charAt(end)); + if (!delim) { + ++end; + while(end < this.source.length && !Lang.DELIM.test(this.source.charAt(end))) + end++; + } else + ++end; + var token = this.source.substring(this.index, this.index = end); + if (token === Lang.STRINGOPEN) + this.readingString = true, + this.stringEndsWith = Lang.STRINGCLOSE; + else if (token === Lang.STRINGOPEN_SQ) + this.readingString = true, + this.stringEndsWith = Lang.STRINGCLOSE_SQ; + return token; + }; + + /** + * Peeks for the next token. + * @return {?string} Token or `null` on EOF + * @throws {Error} If it's not a valid proto file + * @expose + */ + TokenizerPrototype.peek = function() { + if (this.stack.length === 0) { + var token = this.next(); + if (token === null) + return null; + this.stack.push(token); + } + return this.stack[0]; + }; + + /** + * Returns a string representation of this object. + * @return {string} String representation as of "Tokenizer(index/length)" + * @expose + */ + TokenizerPrototype.toString = function() { + return "Tokenizer("+this.index+"/"+this.source.length+" at line "+this.line+")"; + }; + + /** + * @alias ProtoBuf.DotProto.Tokenizer + * @expose + */ + DotProto.Tokenizer = Tokenizer; + + /** + * Constructs a new Parser. + * @exports ProtoBuf.DotProto.Parser + * @class prototype parser + * @param {string} proto Protocol source + * @constructor + */ + var Parser = function(proto) { + + /** + * Tokenizer. + * @type {ProtoBuf.DotProto.Tokenizer} + * @expose + */ + this.tn = new Tokenizer(proto); + }; + + /** + * @alias ProtoBuf.DotProto.Parser.prototype + * @inner + */ + var ParserPrototype = Parser.prototype; + + /** + * Runs the parser. + * @return {{package: string|null, messages: Array., enums: Array., imports: Array., options: object}} + * @throws {Error} If the source cannot be parsed + * @expose + */ + ParserPrototype.parse = function() { + var topLevel = { + "name": "[ROOT]", // temporary + "package": null, + "messages": [], + "enums": [], + "imports": [], + "options": {}, + "services": [] + }; + var token, head = true; + while(token = this.tn.next()) { + switch (token) { + case 'package': + if (!head || topLevel["package"] !== null) + throw Error("Unexpected package at line "+this.tn.line); + topLevel["package"] = this._parsePackage(token); + break; + case 'import': + if (!head) + throw Error("Unexpected import at line "+this.tn.line); + topLevel.imports.push(this._parseImport(token)); + break; + case 'message': + this._parseMessage(topLevel, null, token); + head = false; + break; + case 'enum': + this._parseEnum(topLevel, token); + head = false; + break; + case 'option': + if (!head) + throw Error("Unexpected option at line "+this.tn.line); + this._parseOption(topLevel, token); + break; + case 'service': + this._parseService(topLevel, token); + break; + case 'extend': + this._parseExtend(topLevel, token); + break; + case 'syntax': + this._parseIgnoredStatement(topLevel, token); + break; + default: + throw Error("Unexpected token at line "+this.tn.line+": "+token); + } + } + delete topLevel["name"]; + return topLevel; + }; + + /** + * Parses a number value. + * @param {string} val Number value to parse + * @return {number} Number + * @throws {Error} If the number value is invalid + * @private + */ + ParserPrototype._parseNumber = function(val) { + var sign = 1; + if (val.charAt(0) == '-') + sign = -1, + val = val.substring(1); + if (Lang.NUMBER_DEC.test(val)) + return sign*parseInt(val, 10); + else if (Lang.NUMBER_HEX.test(val)) + return sign*parseInt(val.substring(2), 16); + else if (Lang.NUMBER_OCT.test(val)) + return sign*parseInt(val.substring(1), 8); + else if (Lang.NUMBER_FLT.test(val)) + return sign*parseFloat(val); + throw Error("Illegal number at line "+this.tn.line+": "+(sign < 0 ? '-' : '')+val); + }; + + /** + * Parses a (possibly multiline) string. + * @returns {string} + * @private + */ + ParserPrototype._parseString = function() { + var value = "", token; + do { + token = this.tn.next(); // Known to be = this.tn.stringEndsWith + value += this.tn.next(); + token = this.tn.next(); + if (token !== this.tn.stringEndsWith) + throw Error("Illegal end of string at line "+this.tn.line+": "+token); + token = this.tn.peek(); + } while (token === Lang.STRINGOPEN || token === Lang.STRINGOPEN_SQ); + return value; + }; + + /** + * Parses an ID value. + * @param {string} val ID value to parse + * @param {boolean=} neg Whether the ID may be negative, defaults to `false` + * @returns {number} ID + * @throws {Error} If the ID value is invalid + * @private + */ + ParserPrototype._parseId = function(val, neg) { + var id = -1; + var sign = 1; + if (val.charAt(0) == '-') + sign = -1, + val = val.substring(1); + if (Lang.NUMBER_DEC.test(val)) + id = parseInt(val); + else if (Lang.NUMBER_HEX.test(val)) + id = parseInt(val.substring(2), 16); + else if (Lang.NUMBER_OCT.test(val)) + id = parseInt(val.substring(1), 8); + else + throw Error("Illegal id at line "+this.tn.line+": "+(sign < 0 ? '-' : '')+val); + id = (sign*id)|0; // Force to 32bit + if (!neg && id < 0) + throw Error("Illegal id at line "+this.tn.line+": "+(sign < 0 ? '-' : '')+val); + return id; + }; + + /** + * Parses the package definition. + * @param {string} token Initial token + * @return {string} Package name + * @throws {Error} If the package definition cannot be parsed + * @private + */ + ParserPrototype._parsePackage = function(token) { + token = this.tn.next(); + if (!Lang.TYPEREF.test(token)) + throw Error("Illegal package name at line "+this.tn.line+": "+token); + var pkg = token; + token = this.tn.next(); + if (token != Lang.END) + throw Error("Illegal end of package at line "+this.tn.line+": "+token); + return pkg; + }; + + /** + * Parses an import definition. + * @param {string} token Initial token + * @return {string} Import file name + * @throws {Error} If the import definition cannot be parsed + * @private + */ + ParserPrototype._parseImport = function(token) { + token = this.tn.peek(); + if (token === "public") + this.tn.next(), + token = this.tn.peek(); + if (token !== Lang.STRINGOPEN && token !== Lang.STRINGOPEN_SQ) + throw Error("Illegal start of import at line "+this.tn.line+": "+token); + var imported = this._parseString(); + token = this.tn.next(); + if (token !== Lang.END) + throw Error("Illegal end of import at line "+this.tn.line+": "+token); + return imported; + }; + + /** + * Parses a namespace option. + * @param {Object} parent Parent definition + * @param {string} token Initial token + * @throws {Error} If the option cannot be parsed + * @private + */ + ParserPrototype._parseOption = function(parent, token) { + token = this.tn.next(); + var custom = false; + if (token == Lang.COPTOPEN) + custom = true, + token = this.tn.next(); + if (!Lang.TYPEREF.test(token)) + // we can allow options of the form google.protobuf.* since they will just get ignored anyways + if (!/google\.protobuf\./.test(token)) + throw Error("Illegal option name in message "+parent.name+" at line "+this.tn.line+": "+token); + var name = token; + token = this.tn.next(); + if (custom) { // (my_method_option).foo, (my_method_option), some_method_option, (foo.my_option).bar + if (token !== Lang.COPTCLOSE) + throw Error("Illegal end in message "+parent.name+", option "+name+" at line "+this.tn.line+": "+token); + name = '('+name+')'; + token = this.tn.next(); + if (Lang.FQTYPEREF.test(token)) + name += token, + token = this.tn.next(); + } + if (token !== Lang.EQUAL) + throw Error("Illegal operator in message "+parent.name+", option "+name+" at line "+this.tn.line+": "+token); + var value; + token = this.tn.peek(); + if (token === Lang.STRINGOPEN || token === Lang.STRINGOPEN_SQ) + value = this._parseString(); + else { + this.tn.next(); + if (Lang.NUMBER.test(token)) + value = this._parseNumber(token, true); + else if (Lang.BOOL.test(token)) + value = token === 'true'; + else if (Lang.TYPEREF.test(token)) + value = token; + else + throw Error("Illegal option value in message "+parent.name+", option "+name+" at line "+this.tn.line+": "+token); + } + token = this.tn.next(); + if (token !== Lang.END) + throw Error("Illegal end of option in message "+parent.name+", option "+name+" at line "+this.tn.line+": "+token); + parent["options"][name] = value; + }; + + /** + * Parses an ignored statement of the form ['keyword', ..., ';']. + * @param {Object} parent Parent definition + * @param {string} keyword Initial token + * @throws {Error} If the directive cannot be parsed + * @private + */ + ParserPrototype._parseIgnoredStatement = function(parent, keyword) { + var token; + do { + token = this.tn.next(); + if (token === null) + throw Error("Unexpected EOF in "+parent.name+", "+keyword+" at line "+this.tn.line); + if (token === Lang.END) + break; + } while (true); + }; + + /** + * Parses a service definition. + * @param {Object} parent Parent definition + * @param {string} token Initial token + * @throws {Error} If the service cannot be parsed + * @private + */ + ParserPrototype._parseService = function(parent, token) { + token = this.tn.next(); + if (!Lang.NAME.test(token)) + throw Error("Illegal service name at line "+this.tn.line+": "+token); + var name = token; + var svc = { + "name": name, + "rpc": {}, + "options": {} + }; + token = this.tn.next(); + if (token !== Lang.OPEN) + throw Error("Illegal start of service "+name+" at line "+this.tn.line+": "+token); + do { + token = this.tn.next(); + if (token === "option") + this._parseOption(svc, token); + else if (token === 'rpc') + this._parseServiceRPC(svc, token); + else if (token !== Lang.CLOSE) + throw Error("Illegal type of service "+name+" at line "+this.tn.line+": "+token); + } while (token !== Lang.CLOSE); + parent["services"].push(svc); + }; + + /** + * Parses a RPC service definition of the form ['rpc', name, (request), 'returns', (response)]. + * @param {Object} svc Parent definition + * @param {string} token Initial token + * @private + */ + ParserPrototype._parseServiceRPC = function(svc, token) { + var type = token; + token = this.tn.next(); + if (!Lang.NAME.test(token)) + throw Error("Illegal method name in service "+svc["name"]+" at line "+this.tn.line+": "+token); + var name = token; + var method = { + "request": null, + "response": null, + "options": {} + }; + token = this.tn.next(); + if (token !== Lang.COPTOPEN) + throw Error("Illegal start of request type in service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); + token = this.tn.next(); + if (!Lang.TYPEREF.test(token)) + throw Error("Illegal request type in service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); + method["request"] = token; + token = this.tn.next(); + if (token != Lang.COPTCLOSE) + throw Error("Illegal end of request type in service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); + token = this.tn.next(); + if (token.toLowerCase() !== "returns") + throw Error("Illegal delimiter in service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); + token = this.tn.next(); + if (token != Lang.COPTOPEN) + throw Error("Illegal start of response type in service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); + token = this.tn.next(); + method["response"] = token; + token = this.tn.next(); + if (token !== Lang.COPTCLOSE) + throw Error("Illegal end of response type in service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); + token = this.tn.next(); + if (token === Lang.OPEN) { + do { + token = this.tn.next(); + if (token === 'option') + this._parseOption(method, token); // <- will fail for the custom-options example + else if (token !== Lang.CLOSE) + throw Error("Illegal start of option inservice "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); + } while (token !== Lang.CLOSE); + if (this.tn.peek() === Lang.END) + this.tn.next(); + } else if (token !== Lang.END) + throw Error("Illegal delimiter in service "+svc["name"]+"#"+name+" at line "+this.tn.line+": "+token); + if (typeof svc[type] === 'undefined') + svc[type] = {}; + svc[type][name] = method; + }; + + /** + * Parses a message definition. + * @param {Object} parent Parent definition + * @param {Object} fld Field definition if this is a group, otherwise `null` + * @param {string} token First token + * @return {Object} + * @throws {Error} If the message cannot be parsed + * @private + */ + ParserPrototype._parseMessage = function(parent, fld, token) { + /** @dict */ + var msg = {}; // Note: At some point we might want to exclude the parser, so we need a dict. + var isGroup = token === "group"; + token = this.tn.next(); + if (!Lang.NAME.test(token)) + throw Error("Illegal "+(isGroup ? "group" : "message")+" name"+(parent ? " in message "+parent["name"] : "")+" at line "+this.tn.line+": "+token); + msg["name"] = token; + if (isGroup) { + token = this.tn.next(); + if (token !== Lang.EQUAL) + throw Error("Illegal id assignment after group "+msg.name+" at line "+this.tn.line+": "+token); + token = this.tn.next(); + try { + fld["id"] = this._parseId(token); + } catch (e) { + throw Error("Illegal field id value for group "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); + } + msg["isGroup"] = true; + } + msg["fields"] = []; // Note: Using arrays to support also browser that cannot preserve order of object keys. + msg["enums"] = []; + msg["messages"] = []; + msg["options"] = {}; + msg["oneofs"] = {}; + token = this.tn.next(); + if (token === Lang.OPTOPEN && fld) + this._parseFieldOptions(msg, fld, token), + token = this.tn.next(); + if (token !== Lang.OPEN) + throw Error("Illegal start of "+(isGroup ? "group" : "message")+" "+msg.name+" at line "+this.tn.line+": "+token); + // msg["extensions"] = undefined + do { + token = this.tn.next(); + if (token === Lang.CLOSE) { + token = this.tn.peek(); + if (token === Lang.END) + this.tn.next(); + break; + } else if (Lang.RULE.test(token)) + this._parseMessageField(msg, token); + else if (token === "oneof") + this._parseMessageOneOf(msg, token); + else if (token === "enum") + this._parseEnum(msg, token); + else if (token === "message") + this._parseMessage(msg, null, token); + else if (token === "option") + this._parseOption(msg, token); + else if (token === "extensions") + msg["extensions"] = this._parseExtensions(msg, token); + else if (token === "extend") + this._parseExtend(msg, token); + else + throw Error("Illegal token in message "+msg.name+" at line "+this.tn.line+": "+token); + } while (true); + parent["messages"].push(msg); + return msg; + }; + + /** + * Parses a message field. + * @param {Object} msg Message definition + * @param {string} token Initial token + * @returns {!Object} Field descriptor + * @throws {Error} If the message field cannot be parsed + * @private + */ + ParserPrototype._parseMessageField = function(msg, token) { + /** @dict */ + var fld = {}, grp = null; + fld["rule"] = token; + /** @dict */ + fld["options"] = {}; + token = this.tn.next(); + if (token === "group") { + // "A [legacy] group simply combines a nested message type and a field into a single declaration. In your + // code, you can treat this message just as if it had a Result type field called result (the latter name is + // converted to lower-case so that it does not conflict with the former)." + grp = this._parseMessage(msg, fld, token); + if (!/^[A-Z]/.test(grp["name"])) + throw Error('Group names must start with a capital letter'); + fld["type"] = grp["name"]; + fld["name"] = grp["name"].toLowerCase(); + token = this.tn.peek(); + if (token === Lang.END) + this.tn.next(); + } else { + if (!Lang.TYPE.test(token) && !Lang.TYPEREF.test(token)) + throw Error("Illegal field type in message "+msg.name+" at line "+this.tn.line+": "+token); + fld["type"] = token; + token = this.tn.next(); + if (!Lang.NAME.test(token)) + throw Error("Illegal field name in message "+msg.name+" at line "+this.tn.line+": "+token); + fld["name"] = token; + token = this.tn.next(); + if (token !== Lang.EQUAL) + throw Error("Illegal token in field "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); + token = this.tn.next(); + try { + fld["id"] = this._parseId(token); + } catch (e) { + throw Error("Illegal field id in message "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); + } + token = this.tn.next(); + if (token === Lang.OPTOPEN) + this._parseFieldOptions(msg, fld, token), + token = this.tn.next(); + if (token !== Lang.END) + throw Error("Illegal delimiter in message "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); + } + msg["fields"].push(fld); + return fld; + }; + + /** + * Parses a message oneof. + * @param {Object} msg Message definition + * @param {string} token Initial token + * @throws {Error} If the message oneof cannot be parsed + * @private + */ + ParserPrototype._parseMessageOneOf = function(msg, token) { + token = this.tn.next(); + if (!Lang.NAME.test(token)) + throw Error("Illegal oneof name in message "+msg.name+" at line "+this.tn.line+": "+token); + var name = token, + fld; + var fields = []; + token = this.tn.next(); + if (token !== Lang.OPEN) + throw Error("Illegal start of oneof "+name+" at line "+this.tn.line+": "+token); + while (this.tn.peek() !== Lang.CLOSE) { + fld = this._parseMessageField(msg, "optional"); + fld["oneof"] = name; + fields.push(fld["id"]); + } + this.tn.next(); + msg["oneofs"][name] = fields; + }; + + /** + * Parses a set of field option definitions. + * @param {Object} msg Message definition + * @param {Object} fld Field definition + * @param {string} token Initial token + * @throws {Error} If the message field options cannot be parsed + * @private + */ + ParserPrototype._parseFieldOptions = function(msg, fld, token) { + var first = true; + do { + token = this.tn.next(); + if (token === Lang.OPTCLOSE) + break; + else if (token === Lang.OPTEND) { + if (first) + throw Error("Illegal start of options in message "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); + token = this.tn.next(); + } + this._parseFieldOption(msg, fld, token); + first = false; + } while (true); + }; + + /** + * Parses a single field option. + * @param {Object} msg Message definition + * @param {Object} fld Field definition + * @param {string} token Initial token + * @throws {Error} If the mesage field option cannot be parsed + * @private + */ + ParserPrototype._parseFieldOption = function(msg, fld, token) { + var custom = false; + if (token === Lang.COPTOPEN) + token = this.tn.next(), + custom = true; + if (!Lang.TYPEREF.test(token)) + throw Error("Illegal field option in "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); + var name = token; + token = this.tn.next(); + if (custom) { + if (token !== Lang.COPTCLOSE) + throw Error("Illegal delimiter in "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); + name = '('+name+')'; + token = this.tn.next(); + if (Lang.FQTYPEREF.test(token)) + name += token, + token = this.tn.next(); + } + if (token !== Lang.EQUAL) + throw Error("Illegal token in "+msg.name+"#"+fld.name+" at line "+this.tn.line+": "+token); + var value; + token = this.tn.peek(); + if (token === Lang.STRINGOPEN || token === Lang.STRINGOPEN_SQ) { + value = this._parseString(); + } else if (Lang.NUMBER.test(token, true)) + value = this._parseNumber(this.tn.next(), true); + else if (Lang.BOOL.test(token)) + value = this.tn.next().toLowerCase() === 'true'; + else if (Lang.TYPEREF.test(token)) + value = this.tn.next(); // TODO: Resolve? + else + throw Error("Illegal value in message "+msg.name+"#"+fld.name+", option "+name+" at line "+this.tn.line+": "+token); + fld["options"][name] = value; + }; + + /** + * Parses an enum. + * @param {Object} msg Message definition + * @param {string} token Initial token + * @throws {Error} If the enum cannot be parsed + * @private + */ + ParserPrototype._parseEnum = function(msg, token) { + /** @dict */ + var enm = {}; + token = this.tn.next(); + if (!Lang.NAME.test(token)) + throw Error("Illegal enum name in message "+msg.name+" at line "+this.tn.line+": "+token); + enm["name"] = token; + token = this.tn.next(); + if (token !== Lang.OPEN) + throw Error("Illegal start of enum "+enm.name+" at line "+this.tn.line+": "+token); + enm["values"] = []; + enm["options"] = {}; + do { + token = this.tn.next(); + if (token === Lang.CLOSE) { + token = this.tn.peek(); + if (token === Lang.END) + this.tn.next(); + break; + } + if (token == 'option') + this._parseOption(enm, token); + else { + if (!Lang.NAME.test(token)) + throw Error("Illegal name in enum "+enm.name+" at line "+this.tn.line+": "+token); + this._parseEnumValue(enm, token); + } + } while (true); + msg["enums"].push(enm); + }; + + /** + * Parses an enum value. + * @param {Object} enm Enum definition + * @param {string} token Initial token + * @throws {Error} If the enum value cannot be parsed + * @private + */ + ParserPrototype._parseEnumValue = function(enm, token) { + /** @dict */ + var val = {}; + val["name"] = token; + token = this.tn.next(); + if (token !== Lang.EQUAL) + throw Error("Illegal token in enum "+enm.name+" at line "+this.tn.line+": "+token); + token = this.tn.next(); + try { + val["id"] = this._parseId(token, true); + } catch (e) { + throw Error("Illegal id in enum "+enm.name+" at line "+this.tn.line+": "+token); + } + enm["values"].push(val); + token = this.tn.next(); + if (token === Lang.OPTOPEN) { + var opt = { 'options' : {} }; // TODO: Actually expose them somehow. + this._parseFieldOptions(enm, opt, token); + token = this.tn.next(); + } + if (token !== Lang.END) + throw Error("Illegal delimiter in enum "+enm.name+" at line "+this.tn.line+": "+token); + }; + + /** + * Parses an extensions statement. + * @param {Object} msg Message object + * @param {string} token Initial token + * @throws {Error} If the extensions statement cannot be parsed + * @private + */ + ParserPrototype._parseExtensions = function(msg, token) { + /** @type {Array.} */ + var range = []; + token = this.tn.next(); + if (token === "min") // FIXME: Does the official implementation support this? + range.push(ProtoBuf.ID_MIN); + else if (token === "max") + range.push(ProtoBuf.ID_MAX); + else + range.push(this._parseNumber(token)); + token = this.tn.next(); + if (token !== 'to') + throw Error("Illegal extensions delimiter in message "+msg.name+" at line "+this.tn.line+": "+token); + token = this.tn.next(); + if (token === "min") + range.push(ProtoBuf.ID_MIN); + else if (token === "max") + range.push(ProtoBuf.ID_MAX); + else + range.push(this._parseNumber(token)); + token = this.tn.next(); + if (token !== Lang.END) + throw Error("Illegal extensions delimiter in message "+msg.name+" at line "+this.tn.line+": "+token); + return range; + }; + + /** + * Parses an extend block. + * @param {Object} parent Parent object + * @param {string} token Initial token + * @throws {Error} If the extend block cannot be parsed + * @private + */ + ParserPrototype._parseExtend = function(parent, token) { + token = this.tn.next(); + if (!Lang.TYPEREF.test(token)) + throw Error("Illegal message name at line "+this.tn.line+": "+token); + /** @dict */ + var ext = {}; + ext["ref"] = token; + ext["fields"] = []; + token = this.tn.next(); + if (token !== Lang.OPEN) + throw Error("Illegal start of extend "+ext.name+" at line "+this.tn.line+": "+token); + do { + token = this.tn.next(); + if (token === Lang.CLOSE) { + token = this.tn.peek(); + if (token == Lang.END) + this.tn.next(); + break; + } else if (Lang.RULE.test(token)) + this._parseMessageField(ext, token); + else + throw Error("Illegal token in extend "+ext.name+" at line "+this.tn.line+": "+token); + } while (true); + parent["messages"].push(ext); + return ext; + }; + + /** + * Returns a string representation of this object. + * @returns {string} String representation as of "Parser" + */ + ParserPrototype.toString = function() { + return "Parser"; + }; + + /** + * @alias ProtoBuf.DotProto.Parser + * @expose + */ + DotProto.Parser = Parser; + + return DotProto; + + })(ProtoBuf, ProtoBuf.Lang); + + /** + * @alias ProtoBuf.Reflect + * @expose + */ + ProtoBuf.Reflect = (function(ProtoBuf) { + "use strict"; + + /** + * Reflection types. + * @exports ProtoBuf.Reflect + * @namespace + */ + var Reflect = {}; + + /** + * Constructs a Reflect base class. + * @exports ProtoBuf.Reflect.T + * @constructor + * @abstract + * @param {!ProtoBuf.Builder} builder Builder reference + * @param {?ProtoBuf.Reflect.T} parent Parent object + * @param {string} name Object name + */ + var T = function(builder, parent, name) { + + /** + * Builder reference. + * @type {!ProtoBuf.Builder} + * @expose + */ + this.builder = builder; + + /** + * Parent object. + * @type {?ProtoBuf.Reflect.T} + * @expose + */ + this.parent = parent; + + /** + * Object name in namespace. + * @type {string} + * @expose + */ + this.name = name; + + /** + * Fully qualified class name + * @type {string} + * @expose + */ + this.className; + }; + + /** + * @alias ProtoBuf.Reflect.T.prototype + * @inner + */ + var TPrototype = T.prototype; + + /** + * Returns the fully qualified name of this object. + * @returns {string} Fully qualified name as of ".PATH.TO.THIS" + * @expose + */ + TPrototype.fqn = function() { + var name = this.name, + ptr = this; + do { + ptr = ptr.parent; + if (ptr == null) + break; + name = ptr.name+"."+name; + } while (true); + return name; + }; + + /** + * Returns a string representation of this Reflect object (its fully qualified name). + * @param {boolean=} includeClass Set to true to include the class name. Defaults to false. + * @return String representation + * @expose + */ + TPrototype.toString = function(includeClass) { + return (includeClass ? this.className + " " : "") + this.fqn(); + }; + + /** + * Builds this type. + * @throws {Error} If this type cannot be built directly + * @expose + */ + TPrototype.build = function() { + throw Error(this.toString(true)+" cannot be built directly"); + }; + + /** + * @alias ProtoBuf.Reflect.T + * @expose + */ + Reflect.T = T; + + /** + * Constructs a new Namespace. + * @exports ProtoBuf.Reflect.Namespace + * @param {!ProtoBuf.Builder} builder Builder reference + * @param {?ProtoBuf.Reflect.Namespace} parent Namespace parent + * @param {string} name Namespace name + * @param {Object.=} options Namespace options + * @constructor + * @extends ProtoBuf.Reflect.T + */ + var Namespace = function(builder, parent, name, options) { + T.call(this, builder, parent, name); + + /** + * @override + */ + this.className = "Namespace"; + + /** + * Children inside the namespace. + * @type {!Array.} + */ + this.children = []; + + /** + * Options. + * @type {!Object.} + */ + this.options = options || {}; + }; + + /** + * @alias ProtoBuf.Reflect.Namespace.prototype + * @inner + */ + var NamespacePrototype = Namespace.prototype = Object.create(T.prototype); + + /** + * Returns an array of the namespace's children. + * @param {ProtoBuf.Reflect.T=} type Filter type (returns instances of this type only). Defaults to null (all children). + * @return {Array.} + * @expose + */ + NamespacePrototype.getChildren = function(type) { + type = type || null; + if (type == null) + return this.children.slice(); + var children = []; + for (var i=0, k=this.children.length; i} Runtime namespace + * @expose + */ + NamespacePrototype.build = function() { + /** @dict */ + var ns = {}; + var children = this.children; + for (var i=0, k=children.length, child; i} + */ + NamespacePrototype.buildOpt = function() { + var opt = {}, + keys = Object.keys(this.options); + for (var i=0, k=keys.length; i}null} Option value or NULL if there is no such option + */ + NamespacePrototype.getOption = function(name) { + if (typeof name === 'undefined') + return this.options; + return typeof this.options[name] !== 'undefined' ? this.options[name] : null; + }; + + /** + * @alias ProtoBuf.Reflect.Namespace + * @expose + */ + Reflect.Namespace = Namespace; + + /** + * Constructs a new Message. + * @exports ProtoBuf.Reflect.Message + * @param {!ProtoBuf.Builder} builder Builder reference + * @param {!ProtoBuf.Reflect.Namespace} parent Parent message or namespace + * @param {string} name Message name + * @param {Object.=} options Message options + * @param {boolean=} isGroup `true` if this is a legacy group + * @constructor + * @extends ProtoBuf.Reflect.Namespace + */ + var Message = function(builder, parent, name, options, isGroup) { + Namespace.call(this, builder, parent, name, options); + + /** + * @override + */ + this.className = "Message"; + + /** + * Extensions range. + * @type {!Array.} + * @expose + */ + this.extensions = [ProtoBuf.ID_MIN, ProtoBuf.ID_MAX]; + + /** + * Runtime message class. + * @type {?function(new:ProtoBuf.Builder.Message)} + * @expose + */ + this.clazz = null; + + /** + * Whether this is a legacy group or not. + * @type {boolean} + * @expose + */ + this.isGroup = !!isGroup; + + // The following cached collections are used to efficiently iterate over or look up fields when decoding. + + /** + * Cached fields. + * @type {?Array.} + * @private + */ + this._fields = null; + + /** + * Cached fields by id. + * @type {?Object.} + * @private + */ + this._fieldsById = null; + + /** + * Cached fields by name. + * @type {?Object.} + * @private + */ + this._fieldsByName = null; + }; + + /** + * @alias ProtoBuf.Reflect.Message.prototype + * @inner + */ + var MessagePrototype = Message.prototype = Object.create(Namespace.prototype); + + /** + * Builds the message and returns the runtime counterpart, which is a fully functional class. + * @see ProtoBuf.Builder.Message + * @param {boolean=} rebuild Whether to rebuild or not, defaults to false + * @return {ProtoBuf.Reflect.Message} Message class + * @throws {Error} If the message cannot be built + * @expose + */ + MessagePrototype.build = function(rebuild) { + if (this.clazz && !rebuild) + return this.clazz; + + // Create the runtime Message class in its own scope + var clazz = (function(ProtoBuf, T) { + + var fields = T.getChildren(ProtoBuf.Reflect.Message.Field), + oneofs = T.getChildren(ProtoBuf.Reflect.Message.OneOf); + + /** + * Constructs a new runtime Message. + * @name ProtoBuf.Builder.Message + * @class Barebone of all runtime messages. + * @param {!Object.|string} values Preset values + * @param {...string} var_args + * @constructor + * @throws {Error} If the message cannot be created + */ + var Message = function(values, var_args) { + ProtoBuf.Builder.Message.call(this); + + // Create virtual oneof properties + for (var i=0, k=oneofs.length; i 0) { + // Set field values from a values object + if (arguments.length === 1 && typeof values === 'object' && + /* not another Message */ typeof values.encode !== 'function' && + /* not a repeated field */ !ProtoBuf.Util.isArray(values) && + /* not a ByteBuffer */ !(values instanceof ByteBuffer) && + /* not an ArrayBuffer */ !(values instanceof ArrayBuffer) && + /* not a Long */ !(ProtoBuf.Long && values instanceof ProtoBuf.Long)) { + var keys = Object.keys(values); + for (i=0, k=keys.length; i} Raw payload + * @expose + */ + MessagePrototype.toRaw = function(includeBinaryAsBase64) { + return cloneRaw(this, !!includeBinaryAsBase64); + }; + + /** + * Decodes a message from the specified buffer or string. + * @name ProtoBuf.Builder.Message.decode + * @function + * @param {!ByteBuffer|!ArrayBuffer|!Buffer|string} buffer Buffer to decode from + * @param {string=} enc Encoding if buffer is a string: hex, utf8 (not recommended), defaults to base64 + * @return {!ProtoBuf.Builder.Message} Decoded message + * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still + * returns the decoded message with missing fields in the `decoded` property on the error. + * @expose + * @see ProtoBuf.Builder.Message.decode64 + * @see ProtoBuf.Builder.Message.decodeHex + */ + Message.decode = function(buffer, enc) { + if (typeof buffer === 'string') + buffer = ByteBuffer.wrap(buffer, enc ? enc : "base64"); + buffer = buffer instanceof ByteBuffer ? buffer : ByteBuffer.wrap(buffer); // May throw + var le = buffer.littleEndian; + try { + var msg = T.decode(buffer.LE()); + buffer.LE(le); + return msg; + } catch (e) { + buffer.LE(le); + throw(e); + } + }; + + /** + * Decodes a varint32 length-delimited message from the specified buffer or string. + * @name ProtoBuf.Builder.Message.decodeDelimited + * @function + * @param {!ByteBuffer|!ArrayBuffer|!Buffer|string} buffer Buffer to decode from + * @param {string=} enc Encoding if buffer is a string: hex, utf8 (not recommended), defaults to base64 + * @return {ProtoBuf.Builder.Message} Decoded message or `null` if not enough bytes are available yet + * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still + * returns the decoded message with missing fields in the `decoded` property on the error. + * @expose + */ + Message.decodeDelimited = function(buffer, enc) { + if (typeof buffer === 'string') + buffer = ByteBuffer.wrap(buffer, enc ? enc : "base64"); + buffer = buffer instanceof ByteBuffer ? buffer : ByteBuffer.wrap(buffer); // May throw + if (buffer.remaining() < 1) + return null; + var off = buffer.offset, + len = buffer.readVarint32(); + if (buffer.remaining() < len) { + buffer.offset = off; + return null; + } + try { + var msg = T.decode(buffer.slice(buffer.offset, buffer.offset + len).LE()); + buffer.offset += len; + return msg; + } catch (err) { + buffer.offset += len; + throw err; + } + }; + + /** + * Decodes the message from the specified base64 encoded string. + * @name ProtoBuf.Builder.Message.decode64 + * @function + * @param {string} str String to decode from + * @return {!ProtoBuf.Builder.Message} Decoded message + * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still + * returns the decoded message with missing fields in the `decoded` property on the error. + * @expose + */ + Message.decode64 = function(str) { + return Message.decode(str, "base64"); + }; + + /** + * Decodes the message from the specified hex encoded string. + * @name ProtoBuf.Builder.Message.decodeHex + * @function + * @param {string} str String to decode from + * @return {!ProtoBuf.Builder.Message} Decoded message + * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still + * returns the decoded message with missing fields in the `decoded` property on the error. + * @expose + */ + Message.decodeHex = function(str) { + return Message.decode(str, "hex"); + }; + + // Utility + + /** + * Returns a string representation of this Message. + * @name ProtoBuf.Builder.Message#toString + * @function + * @return {string} String representation as of ".Fully.Qualified.MessageName" + * @expose + */ + MessagePrototype.toString = function() { + return T.toString(); + }; + + // Properties + + /** + * Options. + * @name ProtoBuf.Builder.Message.$options + * @type {Object.} + * @expose + */ + var $options; // cc + + /** + * Reflection type. + * @name ProtoBuf.Builder.Message#$type + * @type {!ProtoBuf.Reflect.Message} + * @expose + */ + var $type; // cc + + if (Object.defineProperty) + Object.defineProperty(Message, '$options', { "value": T.buildOpt() }), + Object.defineProperty(MessagePrototype, "$type", { + get: function() { return T; } + }); + + return Message; + + })(ProtoBuf, this); + + // Static enums and prototyped sub-messages / cached collections + this._fields = []; + this._fieldsById = {}; + this._fieldsByName = {}; + for (var i=0, k=this.children.length, child; i> 3; + switch (wireType) { + case ProtoBuf.WIRE_TYPES.VARINT: + do tag = buf.readUint8(); + while ((tag & 0x80) === 0x80); + break; + case ProtoBuf.WIRE_TYPES.BITS64: + buf.offset += 8; + break; + case ProtoBuf.WIRE_TYPES.LDELIM: + tag = buf.readVarint32(); // reads the varint + buf.offset += tag; // skips n bytes + break; + case ProtoBuf.WIRE_TYPES.STARTGROUP: + skipTillGroupEnd(id, buf); + break; + case ProtoBuf.WIRE_TYPES.ENDGROUP: + if (id === expectedId) + return false; + else + throw Error("Illegal GROUPEND after unknown group: "+id+" ("+expectedId+" expected)"); + case ProtoBuf.WIRE_TYPES.BITS32: + buf.offset += 4; + break; + default: + throw Error("Illegal wire type in unknown group "+expectedId+": "+wireType); + } + return true; + } + + /** + * Decodes an encoded message and returns the decoded message. + * @param {ByteBuffer} buffer ByteBuffer to decode from + * @param {number=} length Message length. Defaults to decode all the available data. + * @param {number=} expectedGroupEndId Expected GROUPEND id if this is a legacy group + * @return {ProtoBuf.Builder.Message} Decoded message + * @throws {Error} If the message cannot be decoded + * @expose + */ + MessagePrototype.decode = function(buffer, length, expectedGroupEndId) { + length = typeof length === 'number' ? length : -1; + var start = buffer.offset, + msg = new (this.clazz)(), + tag, wireType, id, field; + while (buffer.offset < start+length || (length === -1 && buffer.remaining() > 0)) { + tag = buffer.readVarint32(); + wireType = tag & 0x07; + id = tag >> 3; + if (wireType === ProtoBuf.WIRE_TYPES.ENDGROUP) { + if (id !== expectedGroupEndId) + throw Error("Illegal group end indicator for "+this.toString(true)+": "+id+" ("+(expectedGroupEndId ? expectedGroupEndId+" expected" : "not a group")+")"); + break; + } + if (!(field = this._fieldsById[id])) { + // "messages created by your new code can be parsed by your old code: old binaries simply ignore the new field when parsing." + switch (wireType) { + case ProtoBuf.WIRE_TYPES.VARINT: + buffer.readVarint32(); + break; + case ProtoBuf.WIRE_TYPES.BITS32: + buffer.offset += 4; + break; + case ProtoBuf.WIRE_TYPES.BITS64: + buffer.offset += 8; + break; + case ProtoBuf.WIRE_TYPES.LDELIM: + var len = buffer.readVarint32(); + buffer.offset += len; + break; + case ProtoBuf.WIRE_TYPES.STARTGROUP: + while (skipTillGroupEnd(id, buffer)) {} + break; + default: + throw Error("Illegal wire type for unknown field "+id+" in "+this.toString(true)+"#decode: "+wireType); + } + continue; + } + if (field.repeated && !field.options["packed"]) + msg[field.name].push(field.decode(wireType, buffer)); + else { + msg[field.name] = field.decode(wireType, buffer); + if (field.oneof) { + if (this[field.oneof.name] !== null) + this[this[field.oneof.name]] = null; + msg[field.oneof.name] = field.name; + } + } + } + + // Check if all required fields are present and set default values for optional fields that are not + for (var i=0, k=this._fields.length; i=} options Options + * @param {!ProtoBuf.Reflect.Message.OneOf=} oneof Enclosing OneOf + * @constructor + * @extends ProtoBuf.Reflect.T + */ + var Field = function(builder, message, rule, type, name, id, options, oneof) { + T.call(this, builder, message, name); + + /** + * @override + */ + this.className = "Message.Field"; + + /** + * Message field required flag. + * @type {boolean} + * @expose + */ + this.required = rule === "required"; + + /** + * Message field repeated flag. + * @type {boolean} + * @expose + */ + this.repeated = rule === "repeated"; + + /** + * Message field type. Type reference string if unresolved, protobuf type if resolved. + * @type {string|{name: string, wireType: number}} + * @expose + */ + this.type = type; + + /** + * Resolved type reference inside the global namespace. + * @type {ProtoBuf.Reflect.T|null} + * @expose + */ + this.resolvedType = null; + + /** + * Unique message field id. + * @type {number} + * @expose + */ + this.id = id; + + /** + * Message field options. + * @type {!Object.} + * @dict + * @expose + */ + this.options = options || {}; + + /** + * Default value. + * @type {*} + * @expose + */ + this.defaultValue = null; + + /** + * Enclosing OneOf. + * @type {?ProtoBuf.Reflect.Message.OneOf} + * @expose + */ + this.oneof = oneof || null; + + /** + * Original field name. + * @type {string} + * @expose + */ + this.originalName = this.name; // Used to revert camelcase transformation on naming collisions + + // Convert field names to camel case notation if the override is set + if (this.builder.options['convertFieldsToCamelCase'] && !(this instanceof Message.ExtensionField)) + this.name = Field._toCamelCase(this.name); + }; + + /** + * Converts a field name to camel case. + * @param {string} name Likely underscore notated name + * @returns {string} Camel case notated name + * @private + */ + Field._toCamelCase = function(name) { + return name.replace(/_([a-zA-Z])/g, function($0, $1) { + return $1.toUpperCase(); + }); + }; + + /** + * @alias ProtoBuf.Reflect.Message.Field.prototype + * @inner + */ + var FieldPrototype = Field.prototype = Object.create(T.prototype); + + /** + * Builds the field. + * @override + * @expose + */ + FieldPrototype.build = function() { + this.defaultValue = typeof this.options['default'] !== 'undefined' + ? this.verifyValue(this.options['default']) : null; + }; + + /** + * Makes a Long from a value. + * @param {{low: number, high: number, unsigned: boolean}|string|number} value Value + * @param {boolean=} unsigned Whether unsigned or not, defaults to reuse it from Long-like objects or to signed for + * strings and numbers + * @returns {!Long} + * @throws {Error} If the value cannot be converted to a Long + * @inner + */ + function mkLong(value, unsigned) { + if (value && typeof value.low === 'number' && typeof value.high === 'number' && typeof value.unsigned === 'boolean' + && value.low === value.low && value.high === value.high) + return new ProtoBuf.Long(value.low, value.high, typeof unsigned === 'undefined' ? value.unsigned : unsigned); + if (typeof value === 'string') + return ProtoBuf.Long.fromString(value, unsigned || false, 10); + if (typeof value === 'number') + return ProtoBuf.Long.fromNumber(value, unsigned || false); + throw Error("not convertible to Long"); + } + + /** + * Checks if the given value can be set for this field. + * @param {*} value Value to check + * @param {boolean=} skipRepeated Whether to skip the repeated value check or not. Defaults to false. + * @return {*} Verified, maybe adjusted, value + * @throws {Error} If the value cannot be set for this field + * @expose + */ + FieldPrototype.verifyValue = function(value, skipRepeated) { + skipRepeated = skipRepeated || false; + var fail = function(val, msg) { + throw Error("Illegal value for "+this.toString(true)+" of type "+this.type.name+": "+val+" ("+msg+")"); + }.bind(this); + if (value === null) { // NULL values for optional fields + if (this.required) + fail(typeof value, "required"); + return null; + } + var i; + if (this.repeated && !skipRepeated) { // Repeated values as arrays + if (!ProtoBuf.Util.isArray(value)) + value = [value]; + var res = []; + for (i=0; i 4294967295 ? value | 0 : value; + + // Unsigned 32bit + case ProtoBuf.TYPES["uint32"]: + case ProtoBuf.TYPES["fixed32"]: + if (typeof value !== 'number' || (value === value && value % 1 !== 0)) + fail(typeof value, "not an integer"); + return value < 0 ? value >>> 0 : value; + + // Signed 64bit + case ProtoBuf.TYPES["int64"]: + case ProtoBuf.TYPES["sint64"]: + case ProtoBuf.TYPES["sfixed64"]: { + if (ProtoBuf.Long) + try { + return mkLong(value, false); + } catch (e) { + fail(typeof value, e.message); + } + else + fail(typeof value, "requires Long.js"); + } + + // Unsigned 64bit + case ProtoBuf.TYPES["uint64"]: + case ProtoBuf.TYPES["fixed64"]: { + if (ProtoBuf.Long) + try { + return mkLong(value, true); + } catch (e) { + fail(typeof value, e.message); + } + else + fail(typeof value, "requires Long.js"); + } + + // Bool + case ProtoBuf.TYPES["bool"]: + if (typeof value !== 'boolean') + fail(typeof value, "not a boolean"); + return value; + + // Float + case ProtoBuf.TYPES["float"]: + case ProtoBuf.TYPES["double"]: + if (typeof value !== 'number') + fail(typeof value, "not a number"); + return value; + + // Length-delimited string + case ProtoBuf.TYPES["string"]: + if (typeof value !== 'string' && !(value && value instanceof String)) + fail(typeof value, "not a string"); + return ""+value; // Convert String object to string + + // Length-delimited bytes + case ProtoBuf.TYPES["bytes"]: + if (ByteBuffer.isByteBuffer(value)) + return value; + return ByteBuffer.wrap(value, "base64"); + + // Constant enum value + case ProtoBuf.TYPES["enum"]: { + var values = this.resolvedType.getChildren(Enum.Value); + for (i=0; i= 0) { + // "All of the elements of the field are packed into a single key-value pair with wire type 2 + // (length-delimited). Each element is encoded the same way it would be normally, except without a + // tag preceding it." + buffer.writeVarint32((this.id << 3) | ProtoBuf.WIRE_TYPES.LDELIM); + buffer.ensureCapacity(buffer.offset += 1); // We do not know the length yet, so let's assume a varint of length 1 + var start = buffer.offset; // Remember where the contents begin + for (i=0; i 1) { // We need to move the contents + var contents = buffer.slice(start, buffer.offset); + start += varintLen-1; + buffer.offset = start; + buffer.append(contents); + } + buffer.writeVarint32(len, start-varintLen); + } else { + // "If your message definition has repeated elements (without the [packed=true] option), the encoded + // message has zero or more key-value pairs with the same tag number" + for (i=0; i= 0) { + n += ByteBuffer.calculateVarint32((this.id << 3) | ProtoBuf.WIRE_TYPES.LDELIM); + ni = 0; + for (i=0; i= 0) { + if (!skipRepeated) { + nBytes = buffer.readVarint32(); + nBytes = buffer.offset + nBytes; // Limit + var values = []; + while (buffer.offset < nBytes) + values.push(this.decode(this.type.wireType, buffer, true)); + return values; + } + // Read the next value otherwise... + } + switch (this.type) { + // 32bit signed varint + case ProtoBuf.TYPES["int32"]: + return buffer.readVarint32() | 0; + + // 32bit unsigned varint + case ProtoBuf.TYPES["uint32"]: + return buffer.readVarint32() >>> 0; + + // 32bit signed varint zig-zag + case ProtoBuf.TYPES["sint32"]: + return buffer.readVarint32ZigZag() | 0; + + // Fixed 32bit unsigned + case ProtoBuf.TYPES["fixed32"]: + return buffer.readUint32() >>> 0; + + case ProtoBuf.TYPES["sfixed32"]: + return buffer.readInt32() | 0; + + // 64bit signed varint + case ProtoBuf.TYPES["int64"]: + return buffer.readVarint64(); + + // 64bit unsigned varint + case ProtoBuf.TYPES["uint64"]: + return buffer.readVarint64().toUnsigned(); + + // 64bit signed varint zig-zag + case ProtoBuf.TYPES["sint64"]: + return buffer.readVarint64ZigZag(); + + // Fixed 64bit unsigned + case ProtoBuf.TYPES["fixed64"]: + return buffer.readUint64(); + + // Fixed 64bit signed + case ProtoBuf.TYPES["sfixed64"]: + return buffer.readInt64(); + + // Bool varint + case ProtoBuf.TYPES["bool"]: + return !!buffer.readVarint32(); + + // Constant enum value (varint) + case ProtoBuf.TYPES["enum"]: + // The following Builder.Message#set will already throw + return buffer.readVarint32(); + + // 32bit float + case ProtoBuf.TYPES["float"]: + return buffer.readFloat(); + + // 64bit float + case ProtoBuf.TYPES["double"]: + return buffer.readDouble(); + + // Length-delimited string + case ProtoBuf.TYPES["string"]: + return buffer.readVString(); + + // Length-delimited bytes + case ProtoBuf.TYPES["bytes"]: { + nBytes = buffer.readVarint32(); + if (buffer.remaining() < nBytes) + throw Error("Illegal number of bytes for "+this.toString(true)+": "+nBytes+" required but got only "+buffer.remaining()); + value = buffer.clone(); // Offset already set + value.limit = value.offset+nBytes; + buffer.offset += nBytes; + return value; + } + + // Length-delimited embedded message + case ProtoBuf.TYPES["message"]: { + nBytes = buffer.readVarint32(); + return this.resolvedType.decode(buffer, nBytes); + } + + // Legacy group + case ProtoBuf.TYPES["group"]: + return this.resolvedType.decode(buffer, -1, this.id); + } + + // We should never end here + throw Error("[INTERNAL] Illegal wire type for "+this.toString(true)+": "+wireType); + }; + + /** + * @alias ProtoBuf.Reflect.Message.Field + * @expose + */ + Reflect.Message.Field = Field; + + /** + * Constructs a new Message ExtensionField. + * @exports ProtoBuf.Reflect.Message.ExtensionField + * @param {!ProtoBuf.Builder} builder Builder reference + * @param {!ProtoBuf.Reflect.Message} message Message reference + * @param {string} rule Rule, one of requried, optional, repeated + * @param {string} type Data type, e.g. int32 + * @param {string} name Field name + * @param {number} id Unique field id + * @param {Object.=} options Options + * @constructor + * @extends ProtoBuf.Reflect.Message.Field + */ + var ExtensionField = function(builder, message, rule, type, name, id, options) { + Field.call(this, builder, message, rule, type, name, id, options); + + /** + * Extension reference. + * @type {!ProtoBuf.Reflect.Extension} + * @expose + */ + this.extension; + }; + + // Extends Field + ExtensionField.prototype = Object.create(Field.prototype); + + /** + * @alias ProtoBuf.Reflect.Message.ExtensionField + * @expose + */ + Reflect.Message.ExtensionField = ExtensionField; + + /** + * Constructs a new Message OneOf. + * @exports ProtoBuf.Reflect.Message.OneOf + * @param {!ProtoBuf.Builder} builder Builder reference + * @param {!ProtoBuf.Reflect.Message} message Message reference + * @param {string} name OneOf name + * @constructor + * @extends ProtoBuf.Reflect.T + */ + var OneOf = function(builder, message, name) { + T.call(this, builder, message, name); + + /** + * Enclosed fields. + * @type {!Array.} + * @expose + */ + this.fields = []; + }; + + /** + * @alias ProtoBuf.Reflect.Message.OneOf + * @expose + */ + Reflect.Message.OneOf = OneOf; + + /** + * Constructs a new Enum. + * @exports ProtoBuf.Reflect.Enum + * @param {!ProtoBuf.Builder} builder Builder reference + * @param {!ProtoBuf.Reflect.T} parent Parent Reflect object + * @param {string} name Enum name + * @param {Object.=} options Enum options + * @constructor + * @extends ProtoBuf.Reflect.Namespace + */ + var Enum = function(builder, parent, name, options) { + Namespace.call(this, builder, parent, name, options); + + /** + * @override + */ + this.className = "Enum"; + + /** + * Runtime enum object. + * @type {Object.|null} + * @expose + */ + this.object = null; + }; + + /** + * @alias ProtoBuf.Reflect.Enum.prototype + * @inner + */ + var EnumPrototype = Enum.prototype = Object.create(Namespace.prototype); + + /** + * Builds this enum and returns the runtime counterpart. + * @return {Object} + * @expose + */ + EnumPrototype.build = function() { + var enm = {}, + values = this.getChildren(Enum.Value); + for (var i=0, k=values.length; i=} options Options + * @constructor + * @extends ProtoBuf.Reflect.Namespace + */ + var Service = function(builder, root, name, options) { + Namespace.call(this, builder, root, name, options); + + /** + * @override + */ + this.className = "Service"; + + /** + * Built runtime service class. + * @type {?function(new:ProtoBuf.Builder.Service)} + */ + this.clazz = null; + }; + + /** + * @alias ProtoBuf.Reflect.Service.prototype + * @inner + */ + var ServicePrototype = Service.prototype = Object.create(Namespace.prototype); + + /** + * Builds the service and returns the runtime counterpart, which is a fully functional class. + * @see ProtoBuf.Builder.Service + * @param {boolean=} rebuild Whether to rebuild or not + * @return {Function} Service class + * @throws {Error} If the message cannot be built + * @expose + */ + ServicePrototype.build = function(rebuild) { + if (this.clazz && !rebuild) + return this.clazz; + + // Create the runtime Service class in its own scope + return this.clazz = (function(ProtoBuf, T) { + + /** + * Constructs a new runtime Service. + * @name ProtoBuf.Builder.Service + * @param {function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))=} rpcImpl RPC implementation receiving the method name and the message + * @class Barebone of all runtime services. + * @constructor + * @throws {Error} If the service cannot be created + */ + var Service = function(rpcImpl) { + ProtoBuf.Builder.Service.call(this); + + /** + * Service implementation. + * @name ProtoBuf.Builder.Service#rpcImpl + * @type {!function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))} + * @expose + */ + this.rpcImpl = rpcImpl || function(name, msg, callback) { + // This is what a user has to implement: A function receiving the method name, the actual message to + // send (type checked) and the callback that's either provided with the error as its first + // argument or null and the actual response message. + setTimeout(callback.bind(this, Error("Not implemented, see: https://github.com/dcodeIO/ProtoBuf.js/wiki/Services")), 0); // Must be async! + }; + }; + + /** + * @alias ProtoBuf.Builder.Service.prototype + * @inner + */ + var ServicePrototype = Service.prototype = Object.create(ProtoBuf.Builder.Service.prototype); + + if (Object.defineProperty) + Object.defineProperty(Service, "$options", { "value": T.buildOpt() }), + Object.defineProperty(ServicePrototype, "$options", { "value": Service["$options"] }); + + /** + * Asynchronously performs an RPC call using the given RPC implementation. + * @name ProtoBuf.Builder.Service.[Method] + * @function + * @param {!function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))} rpcImpl RPC implementation + * @param {ProtoBuf.Builder.Message} req Request + * @param {function(Error, (ProtoBuf.Builder.Message|ByteBuffer|Buffer|string)=)} callback Callback receiving + * the error if any and the response either as a pre-parsed message or as its raw bytes + * @abstract + */ + + /** + * Asynchronously performs an RPC call using the instance's RPC implementation. + * @name ProtoBuf.Builder.Service#[Method] + * @function + * @param {ProtoBuf.Builder.Message} req Request + * @param {function(Error, (ProtoBuf.Builder.Message|ByteBuffer|Buffer|string)=)} callback Callback receiving + * the error if any and the response either as a pre-parsed message or as its raw bytes + * @abstract + */ + + var rpc = T.getChildren(ProtoBuf.Reflect.Service.RPCMethod); + for (var i=0; i=} options Options + * @constructor + * @extends ProtoBuf.Reflect.T + */ + var Method = function(builder, svc, name, options) { + T.call(this, builder, svc, name); + + /** + * @override + */ + this.className = "Service.Method"; + + /** + * Options. + * @type {Object.} + * @expose + */ + this.options = options || {}; + }; + + /** + * @alias ProtoBuf.Reflect.Service.Method.prototype + * @inner + */ + var MethodPrototype = Method.prototype = Object.create(T.prototype); + + /** + * Builds the method's '$options' property. + * @name ProtoBuf.Reflect.Service.Method#buildOpt + * @function + * @return {Object.} + */ + MethodPrototype.buildOpt = NamespacePrototype.buildOpt; + + /** + * @alias ProtoBuf.Reflect.Service.Method + * @expose + */ + Reflect.Service.Method = Method; + + /** + * RPC service method. + * @exports ProtoBuf.Reflect.Service.RPCMethod + * @param {!ProtoBuf.Builder} builder Builder reference + * @param {!ProtoBuf.Reflect.Service} svc Service + * @param {string} name Method name + * @param {string} request Request message name + * @param {string} response Response message name + * @param {Object.=} options Options + * @constructor + * @extends ProtoBuf.Reflect.Service.Method + */ + var RPCMethod = function(builder, svc, name, request, response, options) { + Method.call(this, builder, svc, name, options); + + /** + * @override + */ + this.className = "Service.RPCMethod"; + + /** + * Request message name. + * @type {string} + * @expose + */ + this.requestName = request; + + /** + * Response message name. + * @type {string} + * @expose + */ + this.responseName = response; + + /** + * Resolved request message type. + * @type {ProtoBuf.Reflect.Message} + * @expose + */ + this.resolvedRequestType = null; + + /** + * Resolved response message type. + * @type {ProtoBuf.Reflect.Message} + * @expose + */ + this.resolvedResponseType = null; + }; + + // Extends Method + RPCMethod.prototype = Object.create(Method.prototype); + + /** + * @alias ProtoBuf.Reflect.Service.RPCMethod + * @expose + */ + Reflect.Service.RPCMethod = RPCMethod; + + return Reflect; + + })(ProtoBuf); + + /** + * @alias ProtoBuf.Builder + * @expose + */ + ProtoBuf.Builder = (function(ProtoBuf, Lang, Reflect) { + "use strict"; + + /** + * Constructs a new Builder. + * @exports ProtoBuf.Builder + * @class Provides the functionality to build protocol messages. + * @param {Object.=} options Options + * @constructor + */ + var Builder = function(options) { + + /** + * Namespace. + * @type {ProtoBuf.Reflect.Namespace} + * @expose + */ + this.ns = new Reflect.Namespace(this, null, ""); // Global namespace + + /** + * Namespace pointer. + * @type {ProtoBuf.Reflect.T} + * @expose + */ + this.ptr = this.ns; + + /** + * Resolved flag. + * @type {boolean} + * @expose + */ + this.resolved = false; + + /** + * The current building result. + * @type {Object.|null} + * @expose + */ + this.result = null; + + /** + * Imported files. + * @type {Array.} + * @expose + */ + this.files = {}; + + /** + * Import root override. + * @type {?string} + * @expose + */ + this.importRoot = null; + + /** + * Options. + * @type {!Object.} + * @expose + */ + this.options = options || {}; + }; + + /** + * @alias ProtoBuf.Builder.prototype + * @inner + */ + var BuilderPrototype = Builder.prototype; + + /** + * Resets the pointer to the root namespace. + * @expose + */ + BuilderPrototype.reset = function() { + this.ptr = this.ns; + }; + + /** + * Defines a package on top of the current pointer position and places the pointer on it. + * @param {string} pkg + * @param {Object.=} options + * @return {ProtoBuf.Builder} this + * @throws {Error} If the package name is invalid + * @expose + */ + BuilderPrototype.define = function(pkg, options) { + if (typeof pkg !== 'string' || !Lang.TYPEREF.test(pkg)) + throw Error("Illegal package: "+pkg); + var part = pkg.split("."), i; + for (i=0; i} def Definition + * @return {boolean} true if valid, else false + * @expose + */ + Builder.isValidMessage = function(def) { + // Messages require a string name + if (typeof def["name"] !== 'string' || !Lang.NAME.test(def["name"])) + return false; + // Messages must not contain values (that'd be an enum) or methods (that'd be a service) + if (typeof def["values"] !== 'undefined' || typeof def["rpc"] !== 'undefined') + return false; + // Fields, enums and messages are arrays if provided + var i; + if (typeof def["fields"] !== 'undefined') { + if (!ProtoBuf.Util.isArray(def["fields"])) + return false; + var ids = [], id; // IDs must be unique + for (i=0; i= 0) + return false; + ids.push(id); + } + ids = null; + } + if (typeof def["enums"] !== 'undefined') { + if (!ProtoBuf.Util.isArray(def["enums"])) + return false; + for (i=0; i + var keys = Object.keys(def["options"]); + for (var i=0, key; i>} defs Messages, enums or services to create + * @return {ProtoBuf.Builder} this + * @throws {Error} If a message definition is invalid + * @expose + */ + BuilderPrototype.create = function(defs) { + if (!defs) + return this; // Nothing to create + if (!ProtoBuf.Util.isArray(defs)) + defs = [defs]; + if (defs.length == 0) + return this; + + // It's quite hard to keep track of scopes and memory here, so let's do this iteratively. + var stack = []; + stack.push(defs); // One level [a, b, c] + while (stack.length > 0) { + defs = stack.pop(); + if (ProtoBuf.Util.isArray(defs)) { // Stack always contains entire namespaces + while (defs.length > 0) { + var def = defs.shift(); // Namespace always contains an array of messages, enums and services + if (Builder.isValidMessage(def)) { + var obj = new Reflect.Message(this, this.ptr, def["name"], def["options"], def["isGroup"]); + // Create OneOfs + var oneofs = {}; + if (def["oneofs"]) { + var keys = Object.keys(def["oneofs"]); + for (var i=0, k=keys.length; i 0) { + for (i=0, k=def["fields"].length; i 0) + for (i=0; i 0) + for (i=0; i ProtoBuf.ID_MAX) + obj.extensions[1] = ProtoBuf.ID_MAX; + } + this.ptr.addChild(obj); // Add to current namespace + if (subObj.length > 0) { + stack.push(defs); // Push the current level back + defs = subObj; // Continue processing sub level + subObj = null; + this.ptr = obj; // And move the pointer to this namespace + obj = null; + continue; + } + subObj = null; + obj = null; + } else if (Builder.isValidEnum(def)) { + obj = new Reflect.Enum(this, this.ptr, def["name"], def["options"]); + for (i=0; i obj.extensions[1]) + throw Error("Illegal extended field id in message "+obj.name+": "+def['fields'][i]['id']+" ("+obj.extensions.join(' to ')+" expected)"); + // Convert extension field names to camel case notation if the override is set + var name = def["fields"][i]["name"]; + if (this.options['convertFieldsToCamelCase']) + name = Reflect.Message.Field._toCamelCase(def["fields"][i]["name"]); + // see #161: Extensions use their fully qualified name as their runtime key and... + fld = new Reflect.Message.ExtensionField(this, obj, def["fields"][i]["rule"], def["fields"][i]["type"], this.ptr.fqn()+'.'+name, def["fields"][i]["id"], def["fields"][i]["options"]); + // ...are added on top of the current namespace as an extension which is used for + // resolving their type later on (the extension always keeps the original name to + // prevent naming collisions) + var ext = new Reflect.Extension(this, this.ptr, def["fields"][i]["name"], fld); + fld.extension = ext; + this.ptr.addChild(ext); + obj.addChild(fld); + } + } else if (!/\.?google\.protobuf\./.test(def["ref"])) // Silently skip internal extensions + throw Error("Extended message "+def["ref"]+" is not defined"); + } else + throw Error("Not a valid definition: "+JSON.stringify(def)); + def = null; + } + // Break goes here + } else + throw Error("Not a valid namespace: "+JSON.stringify(defs)); + defs = null; + this.ptr = this.ptr.parent; // This namespace is s done + } + this.resolved = false; // Require re-resolve + this.result = null; // Require re-build + return this; + }; + + /** + * Imports another definition into this builder. + * @param {Object.} json Parsed import + * @param {(string|{root: string, file: string})=} filename Imported file name + * @return {ProtoBuf.Builder} this + * @throws {Error} If the definition or file cannot be imported + * @expose + */ + BuilderPrototype["import"] = function(json, filename) { + if (typeof filename === 'string') { + if (ProtoBuf.Util.IS_NODE) + filename = require("path")['resolve'](filename); + if (this.files[filename] === true) { + this.reset(); + return this; // Skip duplicate imports + } + this.files[filename] = true; + } + if (!!json['imports'] && json['imports'].length > 0) { + var importRoot, delim = '/', resetRoot = false; + if (typeof filename === 'object') { // If an import root is specified, override + this.importRoot = filename["root"]; resetRoot = true; // ... and reset afterwards + importRoot = this.importRoot; + filename = filename["file"]; + if (importRoot.indexOf("\\") >= 0 || filename.indexOf("\\") >= 0) delim = '\\'; + } else if (typeof filename === 'string') { + if (this.importRoot) // If import root is overridden, use it + importRoot = this.importRoot; + else { // Otherwise compute from filename + if (filename.indexOf("/") >= 0) { // Unix + importRoot = filename.replace(/\/[^\/]*$/, ""); + if (/* /file.proto */ importRoot === "") + importRoot = "/"; + } else if (filename.indexOf("\\") >= 0) { // Windows + importRoot = filename.replace(/\\[^\\]*$/, ""); + delim = '\\'; + } else + importRoot = "."; + } + } else + importRoot = null; + + for (var i=0; i= 0) + return false; + ids.push(id); + } + ids = null; + } + return true; + }; + + /** + * Resolves all namespace objects. + * @throws {Error} If a type cannot be resolved + * @expose + */ + BuilderPrototype.resolveAll = function() { + // Resolve all reflected objects + var res; + if (this.ptr == null || typeof this.ptr.type === 'object') + return; // Done (already resolved) + if (this.ptr instanceof Reflect.Namespace) { + // Build all children + var children = this.ptr.children; + for (var i= 0, k=children.length; i} + * @throws {Error} If a type could not be resolved + * @expose + */ + BuilderPrototype.build = function(path) { + this.reset(); + if (!this.resolved) + this.resolveAll(), + this.resolved = true, + this.result = null; // Require re-build + if (this.result == null) // (Re-)Build + this.result = this.ns.build(); + if (!path) + return this.result; + else { + var part = path.split("."); + var ptr = this.result; // Build namespace pointer (no hasChild etc.) + for (var i=0; i=} options Builder options, defaults to global options set on ProtoBuf + * @return {!ProtoBuf.Builder} Builder + * @expose + */ + ProtoBuf.newBuilder = function(options) { + options = options || {}; + if (typeof options['convertFieldsToCamelCase'] === 'undefined') + options['convertFieldsToCamelCase'] = ProtoBuf.convertFieldsToCamelCase; + if (typeof options['populateAccessors'] === 'undefined') + options['populateAccessors'] = ProtoBuf.populateAccessors; + return new ProtoBuf.Builder(options); + }; + + /** + * Loads a .json definition and returns the Builder. + * @param {!*|string} json JSON definition + * @param {(ProtoBuf.Builder|string|{root: string, file: string})=} builder Builder to append to. Will create a new one if omitted. + * @param {(string|{root: string, file: string})=} filename The corresponding file name if known. Must be specified for imports. + * @return {ProtoBuf.Builder} Builder to create new messages + * @throws {Error} If the definition cannot be parsed or built + * @expose + */ + ProtoBuf.loadJson = function(json, builder, filename) { + if (typeof builder === 'string' || (builder && typeof builder["file"] === 'string' && typeof builder["root"] === 'string')) + filename = builder, + builder = null; + if (!builder || typeof builder !== 'object') + builder = ProtoBuf.newBuilder(); + if (typeof json === 'string') + json = JSON.parse(json); + builder["import"](json, filename); + builder.resolveAll(); + return builder; + }; + + /** + * Loads a .json file and returns the Builder. + * @param {string|!{root: string, file: string}} filename Path to json file or an object specifying 'file' with + * an overridden 'root' path for all imported files. + * @param {function(?Error, !ProtoBuf.Builder=)=} callback Callback that will receive `null` as the first and + * the Builder as its second argument on success, otherwise the error as its first argument. If omitted, the + * file will be read synchronously and this function will return the Builder. + * @param {ProtoBuf.Builder=} builder Builder to append to. Will create a new one if omitted. + * @return {?ProtoBuf.Builder|undefined} The Builder if synchronous (no callback specified, will be NULL if the + * request has failed), else undefined + * @expose + */ + ProtoBuf.loadJsonFile = function(filename, callback, builder) { + if (callback && typeof callback === 'object') + builder = callback, + callback = null; + else if (!callback || typeof callback !== 'function') + callback = null; + if (callback) + return ProtoBuf.Util.fetch(typeof filename === 'string' ? filename : filename["root"]+"/"+filename["file"], function(contents) { + if (contents === null) { + callback(Error("Failed to fetch file")); + return; + } + try { + callback(null, ProtoBuf.loadJson(JSON.parse(contents), builder, filename)); + } catch (e) { + callback(e); + } + }); + var contents = ProtoBuf.Util.fetch(typeof filename === 'object' ? filename["root"]+"/"+filename["file"] : filename); + return contents === null ? null : ProtoBuf.loadJson(JSON.parse(contents), builder, filename); + }; + + return ProtoBuf; + } + + /* CommonJS */ if (typeof require === 'function' && typeof module === 'object' && module && typeof exports === 'object' && exports) + module['exports'] = init(require("bytebuffer")); + /* AMD */ else if (typeof define === 'function' && define["amd"]) + define(["ByteBuffer"], init); + /* Global */ else + (global["dcodeIO"] = global["dcodeIO"] || {})["ProtoBuf"] = init(global["dcodeIO"]["ByteBuffer"]); + +})(this); diff --git a/libtextsecure/protobufs.js b/libtextsecure/protobufs.js new file mode 100644 index 0000000000..4ef228edc5 --- /dev/null +++ b/libtextsecure/protobufs.js @@ -0,0 +1,42 @@ +(function() { + 'use strict'; + window.textsecure = window.textsecure || {}; + window.textsecure.protobuf = {}; + + function loadProtoBufs(filename) { + return dcodeIO.ProtoBuf.loadProtoFile( + { root: window.PROTO_ROOT, file: filename }, + function(error, result) { + if (error) { + var text = + 'Error loading protos from ' + + filename + + ' (root: ' + + window.PROTO_ROOT + + ') ' + + (error && error.stack ? error.stack : error); + console.log(text); + throw error; + } + var protos = result.build('signalservice'); + if (!protos) { + var text = + 'Error loading protos from ' + + filename + + ' (root: ' + + window.PROTO_ROOT + + ')'; + console.log(text); + throw new Error(text); + } + for (var protoName in protos) { + textsecure.protobuf[protoName] = protos[protoName]; + } + } + ); + } + + loadProtoBufs('SignalService.proto'); + loadProtoBufs('SubProtocol.proto'); + loadProtoBufs('DeviceMessages.proto'); +})(); diff --git a/libtextsecure/test/index.html b/libtextsecure/test/index.html index d61c915907..acb9304363 100644 --- a/libtextsecure/test/index.html +++ b/libtextsecure/test/index.html @@ -18,6 +18,7 @@ + diff --git a/package.json b/package.json index 09dd25e502..3213678bac 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,10 @@ "generate": "yarn icon-gen && yarn grunt", "build": "build --config.extraMetadata.environment=$SIGNAL_ENV", "build-release": "SIGNAL_ENV=production npm run build -- --config.directories.output=release", - "build-libtextsecure-protobuf": "pbjs --target static-module --root signalservice --out libtextsecure/protobuf-compiled.js protos/DeviceMessages.proto protos/SubProtocol.proto protos/SignalService.proto", - "clean-libtextsecure-protobuf": "rm -f libtextsecure/protobuf-compiled.js", "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-libtextsecure-protobuf && yarn build-module-protobuf", - "clean-protobuf": "yarn clean-libtextsecure-protobuf && yarn clean-module-protobuf", + "build-protobuf": "yarn build-module-protobuf", + "clean-protobuf": "yarn clean-module-protobuf", "prepare-beta-build": "node prepare_beta_build.js", "prepare-import-build": "node prepare_import_build.js", "publish-to-apt": "NAME=$npm_package_name VERSION=$npm_package_version ./aptly.sh", diff --git a/preload.js b/preload.js index 96c2a50e82..412456a887 100644 --- a/preload.js +++ b/preload.js @@ -9,7 +9,6 @@ const Attachment = require('./js/modules/types/attachment'); const Attachments = require('./app/attachments'); const Message = require('./js/modules/types/message'); const { deferredToPromise } = require('./js/modules/deferred_to_promise'); -const { SignalService } = require('./ts/protobuf'); const { app } = electron.remote; @@ -99,13 +98,11 @@ window.filesize = require('filesize'); window.libphonenumber = require('google-libphonenumber').PhoneNumberUtil.getInstance(); window.libphonenumber.PhoneNumberFormat = require('google-libphonenumber').PhoneNumberFormat; window.loadImage = require('blueimp-load-image'); + window.nodeBuffer = Buffer; window.nodeFetch = require('node-fetch'); window.ProxyAgent = require('proxy-agent'); -window.textsecure = window.textsecure || {}; -window.textsecure.protobuf = SignalService; - // Note: when modifying this file, consider whether our React Components or Backbone Views // will need these things to render in the Style Guide. If so, go update one of these // two locations: