diff --git a/app/attachments.js b/app/attachments.js index 8c115058f..148f6beba 100644 --- a/app/attachments.js +++ b/app/attachments.js @@ -189,7 +189,7 @@ exports.writeToDownloads = async ({ data, name }) => { throw new Error('Invalid filename!'); } - writeWithAttributes(normalized, Buffer.from(data)); + await writeWithAttributes(normalized, Buffer.from(data)); return { fullPath: normalized, diff --git a/app/sql_channel.js b/app/sql_channel.js index 8cb5b8121..cbe24656d 100644 --- a/app/sql_channel.js +++ b/app/sql_channel.js @@ -1,5 +1,5 @@ const electron = require('electron'); -const Queue = require('p-queue'); +const Queue = require('p-queue').default; const sql = require('./sql'); const { remove: removeUserConfig } = require('./user_config'); const { remove: removeEphemeralConfig } = require('./ephemeral_config'); diff --git a/components/protobuf/dist/ProtoBuf.js b/components/protobuf/dist/ProtoBuf.js index 0e12340a7..d2990fff0 100644 --- a/components/protobuf/dist/ProtoBuf.js +++ b/components/protobuf/dist/ProtoBuf.js @@ -19,1886 +19,2531 @@ * Released under the Apache License, Version 2.0 * see: https://github.com/dcodeIO/ProtoBuf.js for details */ -(function(global) { +(function(global, factory) { + + /* AMD */ if (typeof define === 'function' && define["amd"]) + define(["ByteBuffer"], factory); + /* CommonJS */ else if (typeof require === "function" && typeof module === "object" && module && module["exports"]) + module["exports"] = factory(require("bytebuffer"), true); + /* Global */ else + (global["dcodeIO"] = global["dcodeIO"] || {})["ProtoBuf"] = factory(global["dcodeIO"]["ByteBuffer"]); + +})(this, function(ByteBuffer, isCommonJS) { "use strict"; - function init(ByteBuffer) { + /** + * The ProtoBuf namespace. + * @exports ProtoBuf + * @namespace + * @expose + */ + var ProtoBuf = {}; + + /** + * @type {!function(new: ByteBuffer, ...[*])} + * @expose + */ + ProtoBuf.ByteBuffer = ByteBuffer; + + /** + * @type {?function(new: Long, ...[*])} + * @expose + */ + ProtoBuf.Long = ByteBuffer.Long || null; + + /** + * ProtoBuf.js version. + * @type {string} + * @const + * @expose + */ + ProtoBuf.VERSION = "4.1.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, + defaultValue: 0 + }, + "uint32": { + name: "uint32", + wireType: ProtoBuf.WIRE_TYPES.VARINT, + defaultValue: 0 + }, + "sint32": { + name: "sint32", + wireType: ProtoBuf.WIRE_TYPES.VARINT, + defaultValue: 0 + }, + "int64": { + name: "int64", + wireType: ProtoBuf.WIRE_TYPES.VARINT, + defaultValue: ProtoBuf.Long ? ProtoBuf.Long.ZERO : undefined + }, + "uint64": { + name: "uint64", + wireType: ProtoBuf.WIRE_TYPES.VARINT, + defaultValue: ProtoBuf.Long ? ProtoBuf.Long.UZERO : undefined + }, + "sint64": { + name: "sint64", + wireType: ProtoBuf.WIRE_TYPES.VARINT, + defaultValue: ProtoBuf.Long ? ProtoBuf.Long.ZERO : undefined + }, + "bool": { + name: "bool", + wireType: ProtoBuf.WIRE_TYPES.VARINT, + defaultValue: false + }, + "double": { + name: "double", + wireType: ProtoBuf.WIRE_TYPES.BITS64, + defaultValue: 0 + }, + "string": { + name: "string", + wireType: ProtoBuf.WIRE_TYPES.LDELIM, + defaultValue: "" + }, + "bytes": { + name: "bytes", + wireType: ProtoBuf.WIRE_TYPES.LDELIM, + defaultValue: null // overridden in the code, must be a unique instance + }, + "fixed32": { + name: "fixed32", + wireType: ProtoBuf.WIRE_TYPES.BITS32, + defaultValue: 0 + }, + "sfixed32": { + name: "sfixed32", + wireType: ProtoBuf.WIRE_TYPES.BITS32, + defaultValue: 0 + }, + "fixed64": { + name: "fixed64", + wireType: ProtoBuf.WIRE_TYPES.BITS64, + defaultValue: ProtoBuf.Long ? ProtoBuf.Long.UZERO : undefined + }, + "sfixed64": { + name: "sfixed64", + wireType: ProtoBuf.WIRE_TYPES.BITS64, + defaultValue: ProtoBuf.Long ? ProtoBuf.Long.ZERO : undefined + }, + "float": { + name: "float", + wireType: ProtoBuf.WIRE_TYPES.BITS32, + defaultValue: 0 + }, + "enum": { + name: "enum", + wireType: ProtoBuf.WIRE_TYPES.VARINT, + defaultValue: 0 + }, + "message": { + name: "message", + wireType: ProtoBuf.WIRE_TYPES.LDELIM, + defaultValue: null + }, + "group": { + name: "group", + wireType: ProtoBuf.WIRE_TYPES.STARTGROUP, + defaultValue: null + } + }; + + /** + * Valid map key types. + * @type {!Array.>} + * @const + * @expose + */ + ProtoBuf.MAP_KEY_TYPES = [ + ProtoBuf.TYPES["int32"], + ProtoBuf.TYPES["sint32"], + ProtoBuf.TYPES["sfixed32"], + ProtoBuf.TYPES["uint32"], + ProtoBuf.TYPES["fixed32"], + ProtoBuf.TYPES["int64"], + ProtoBuf.TYPES["sint64"], + ProtoBuf.TYPES["sfixed64"], + ProtoBuf.TYPES["uint64"], + ProtoBuf.TYPES["fixed64"], + ProtoBuf.TYPES["bool"], + ProtoBuf.TYPES["string"], + ProtoBuf.TYPES["bytes"] + ]; + + /** + * Minimum field id. + * @type {number} + * @const + * @expose + */ + ProtoBuf.ID_MIN = 1; + + /** + * Maximum field id. + * @type {number} + * @const + * @expose + */ + ProtoBuf.ID_MAX = 0x1FFFFFFF; + + /** + * 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; + + /** + * By default, messages are populated with default values if a field is not present on the wire. To disable + * this behavior, set this setting to `false`. + * @type {boolean} + * @expose + */ + ProtoBuf.populateDefaults = true; + + /** + * @alias ProtoBuf.Util + * @expose + */ + ProtoBuf.Util = (function() { + "use strict"; /** - * The ProtoBuf namespace. - * @exports ProtoBuf + * ProtoBuf utilities. + * @exports ProtoBuf.Util * @namespace - * @expose */ - var ProtoBuf = {}; + var Util = {}; /** - * ProtoBuf.js version. - * @type {string} + * Flag if running in node or not. + * @type {boolean} * @const * @expose */ - ProtoBuf.VERSION = "3.8.2"; + Util.IS_NODE = !!( + typeof process === 'object' && process+'' === '[object process]' && !process['browser'] + ); /** - * Wire types. - * @type {Object.} - * @const + * Constructs a XMLHttpRequest object. + * @return {XMLHttpRequest} + * @throws {Error} If XMLHttpRequest is not supported * @expose */ - ProtoBuf.WIRE_TYPES = {}; + 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} - * @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 + Util.fetch = function(path, callback) { + if (callback && typeof callback != 'function') + callback = null; + if (Util.IS_NODE) { + var fs = require("fs"); + if (callback) { + fs.readFile(path, function(err, data) { + if (err) + callback(null); + else + callback(""+data); + }); + } else + try { + return fs.readFileSync(path); + } catch (e) { + return null; + } + } else { + var xhr = Util.XHR(); + xhr.open('GET', path, callback ? true : false); + // xhr.setRequestHeader('User-Agent', 'XMLHTTP/1.0'); + xhr.setRequestHeader('Accept', 'text/plain'); + if (typeof xhr.overrideMimeType === 'function') xhr.overrideMimeType('text/plain'); + if (callback) { + xhr.onreadystatechange = function() { + if (xhr.readyState != 4) return; + if (/* remote */ xhr.status == 200 || /* local */ (xhr.status == 0 && typeof xhr.responseText === 'string')) + callback(xhr.responseText); + else + callback(null); + }; + if (xhr.readyState == 4) + return; + xhr.send(null); + } else { + xhr.send(null); + if (/* remote */ xhr.status == 200 || /* local */ (xhr.status == 0 && typeof xhr.responseText === 'string')) + return xhr.responseText; + return null; + } } }; /** - * Minimum field id. - * @type {number} - * @const + * Converts a string to camel case. + * @param {string} str + * @returns {string} * @expose */ - ProtoBuf.ID_MIN = 1; + Util.toCamelCase = function(str) { + return str.replace(/_([a-zA-Z])/g, function ($0, $1) { + return $1.toUpperCase(); + }); + }; + + return Util; + })(); + + /** + * Language expressions. + * @type {!Object.} + * @expose + */ + ProtoBuf.Lang = { + + // Characters always ending a statement + DELIM: /[\s\{\}=;:\[\],'"\(\)<>]/g, + + // Field rules + RULE: /^(?:required|optional|repeated|map)$/, + + // Field types + TYPE: /^(?:double|float|int32|uint32|sint32|int64|uint64|sint64|fixed32|sfixed32|fixed64|sfixed64|bool|string|bytes)$/, + + // Names + NAME: /^[a-zA-Z_][a-zA-Z_0-9]*$/, + + // Type definitions + TYPEDEF: /^[a-zA-Z][a-zA-Z_0-9]*$/, + + // Type references + TYPEREF: /^(?:\.?[a-zA-Z_][a-zA-Z_0-9]*)+$/, + + // Fully qualified type references + FQTYPEREF: /^(?:\.[a-zA-Z][a-zA-Z_0-9]*)+$/, + + // All numbers + NUMBER: /^-?(?:[1-9][0-9]*|0|0[xX][0-9a-fA-F]+|0[0-7]+|([0-9]*(\.[0-9]*)?([Ee][+-]?[0-9]+)?)|inf|nan)$/, + + // Decimal numbers + NUMBER_DEC: /^(?:[1-9][0-9]*|0)$/, + + // Hexadecimal numbers + NUMBER_HEX: /^0[xX][0-9a-fA-F]+$/, + + // Octal numbers + NUMBER_OCT: /^0[0-7]+$/, + + // Floating point numbers + NUMBER_FLT: /^([0-9]*(\.[0-9]*)?([Ee][+-]?[0-9]+)?|inf|nan)$/, + + // Booleans + BOOL: /^(?:true|false)$/i, + + // Id numbers + ID: /^(?:[1-9][0-9]*|0|0[xX][0-9a-fA-F]+|0[0-7]+)$/, + + // Negative id numbers (enum values) + NEGID: /^\-?(?:[1-9][0-9]*|0|0[xX][0-9a-fA-F]+|0[0-7]+)$/, + + // Whitespaces + WHITESPACE: /\s/, + + // All strings + STRING: /(?:"([^"\\]*(?:\\.[^"\\]*)*)")|(?:'([^'\\]*(?:\\.[^'\\]*)*)')/g, + + // Double quoted strings + STRING_DQ: /(?:"([^"\\]*(?:\\.[^"\\]*)*)")/g, + + // Single quoted strings + STRING_SQ: /(?:'([^'\\]*(?:\\.[^'\\]*)*)')/g + }; + + /** + * @alias ProtoBuf.DotProto + * @expose + */ + ProtoBuf.DotProto = (function(ProtoBuf, Lang) { + "use strict"; /** - * Maximum field id. - * @type {number} - * @const - * @expose + * Utilities to parse .proto files. + * @exports ProtoBuf.DotProto + * @namespace */ - ProtoBuf.ID_MAX = 0x1FFFFFFF; + var DotProto = {}; /** - * @type {!function(new: ByteBuffer, ...[*])} - * @expose + * Constructs a new Tokenizer. + * @exports ProtoBuf.DotProto.Tokenizer + * @class prototype tokenizer + * @param {string} proto Proto to tokenize + * @constructor */ - 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(); - }; + var Tokenizer = function(proto) { /** - * ProtoBuf utilities. - * @exports ProtoBuf.Util - * @namespace - */ - var Util = {}; - - /** - * Flag if running in node (fs is available) or not. - * @type {boolean} - * @const + * Source to parse. + * @type {string} * @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) {} + this.source = proto+""; /** - * Constructs a XMLHttpRequest object. - * @return {XMLHttpRequest} - * @throws {Error} If XMLHttpRequest is not supported + * Current index. + * @type {number} * @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 */ - Util.isArray = Array.isArray || function(obj) { - return Object.prototype.toString.call(obj) === "[object Array]"; - }; + this.stack = []; - return Util; - })(); - - /** - * Language expressions. - * @type {!Object.} - * @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 + /** + * Opening character of the current string read, if any. + * @type {?string} + * @private + */ + this._stringOpen = null; }; /** - * @alias ProtoBuf.DotProto + * @alias ProtoBuf.DotProto.Tokenizer.prototype + * @inner + */ + var TokenizerPrototype = Tokenizer.prototype; + + /** + * Reads a string beginning at the current index. + * @return {string} + * @private + */ + TokenizerPrototype._readString = function() { + var re = this._stringOpen === '"' + ? Lang.STRING_DQ + : Lang.STRING_SQ; + re.lastIndex = this.index - 1; // Include the open quote + var match = re.exec(this.source); + if (!match) + throw Error("unterminated string"); + this.index = re.lastIndex; + this.stack.push(this._stringOpen); + this._stringOpen = null; + return match[1]; + }; + + /** + * Gets the next token and advances by one. + * @return {?string} Token or `null` on EOF * @expose */ - ProtoBuf.DotProto = (function(ProtoBuf, Lang) { - "use strict"; + TokenizerPrototype.next = function() { + if (this.stack.length > 0) + return this.stack.shift(); + if (this.index >= this.source.length) + return null; + if (this._stringOpen !== null) + return this._readString(); - /** - * Utilities to parse .proto files. - * @exports ProtoBuf.DotProto - * @namespace - */ - var DotProto = {}; + var repeat, + prev, + next; + do { + repeat = false; - /** - * 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) + // Strip white spaces + while (Lang.WHITESPACE.test(next = this.source.charAt(this.index))) { + if (next === '\n') + ++this.line; + if (++this.index === this.source.length) return null; - this.stack.push(token); } - return this.stack[0]; - }; + + // Strip comments + if (this.source.charAt(this.index) === '/') { + ++this.index; + if (this.source.charAt(this.index) === '/') { // Line + while (this.source.charAt(++this.index) !== '\n') + if (this.index == this.source.length) + return null; + ++this.index; + ++this.line; + repeat = true; + } else if ((next = this.source.charAt(this.index)) === '*') { /* Block */ + do { + if (next === '\n') + ++this.line; + if (++this.index === this.source.length) + return null; + prev = next; + next = this.source.charAt(this.index); + } while (prev !== '*' || next !== '/'); + ++this.index; + repeat = true; + } else + return '/'; + } + } 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) + while(end < this.source.length && !Lang.DELIM.test(this.source.charAt(end))) + ++end; + var token = this.source.substring(this.index, this.index = end); + if (token === '"' || token === "'") + this._stringOpen = token; + return token; + }; + + /** + * Peeks for the next token. + * @return {?string} Token or `null` on EOF + * @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]; + }; + + /** + * Skips a specific token and throws if it differs. + * @param {string} expected Expected token + * @throws {Error} If the actual token differs + */ + TokenizerPrototype.skip = function(expected) { + var actual = this.next(); + if (actual !== expected) + throw Error("illegal '"+actual+"', '"+expected+"' expected"); + }; + + /** + * Omits an optional token. + * @param {string} expected Expected optional token + * @returns {boolean} `true` if the token exists + */ + TokenizerPrototype.omit = function(expected) { + if (this.peek() === expected) { + this.next(); + return true; + } + return false; + }; + + /** + * 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} source Source + * @constructor + */ + var Parser = function(source) { /** - * Returns a string representation of this object. - * @return {string} String representation as of "Tokenizer(index/length)" + * Tokenizer. + * @type {!ProtoBuf.DotProto.Tokenizer} * @expose */ - TokenizerPrototype.toString = function() { - return "Tokenizer("+this.index+"/"+this.source.length+" at line "+this.line+")"; + this.tn = new Tokenizer(source); + + /** + * Whether parsing proto3 or not. + * @type {boolean} + */ + this.proto3 = false; + }; + + /** + * @alias ProtoBuf.DotProto.Parser.prototype + * @inner + */ + var ParserPrototype = Parser.prototype; + + /** + * Parses the source. + * @returns {!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": [] + // "syntax": undefined }; - - /** - * @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()) { + var token, + head = true; + try { + 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); + throw Error("unexpected 'package'"); + token = this.tn.next(); + if (!Lang.TYPEREF.test(token)) + throw Error("illegal package name: " + token); + this.tn.skip(";"); + topLevel["package"] = token; break; case 'import': if (!head) - throw Error("Unexpected import at line "+this.tn.line); - topLevel.imports.push(this._parseImport(token)); + throw Error("unexpected 'import'"); + token = this.tn.peek(); + if (token === "public") // ignored + this.tn.next(); + token = this._readString(); + this.tn.skip(";"); + topLevel["imports"].push(token); + break; + case 'syntax': + if (!head) + throw Error("unexpected 'syntax'"); + this.tn.skip("="); + if ((topLevel["syntax"] = this._readString()) === "proto3") + this.proto3 = true; + this.tn.skip(";"); break; case 'message': - this._parseMessage(topLevel, null, token); + this._parseMessage(topLevel, null); head = false; break; case 'enum': - this._parseEnum(topLevel, token); + this._parseEnum(topLevel); head = false; break; case 'option': - if (!head) - throw Error("Unexpected option at line "+this.tn.line); - this._parseOption(topLevel, token); + this._parseOption(topLevel); break; case 'service': - this._parseService(topLevel, token); + this._parseService(topLevel); break; case 'extend': - this._parseExtend(topLevel, token); - break; - case 'syntax': - this._parseIgnoredStatement(topLevel, token); + this._parseExtend(topLevel); break; default: - throw Error("Unexpected token at line "+this.tn.line+": "+token); + throw Error("unexpected '" + token + "'"); } } - delete topLevel["name"]; - return topLevel; - }; + } catch (e) { + e.message = "Parse error at line "+this.tn.line+": " + e.message; + throw e; + } + 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 the specified source. + * @returns {!Object} + * @throws {Error} If the source cannot be parsed + * @expose + */ + Parser.parse = function(source) { + return new Parser(source).parse(); + }; - /** - * 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; - }; + // ----- Conversion ------ - /** - * 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; - }; + /** + * Converts a numerical string to an id. + * @param {string} value + * @param {boolean=} mayBeNegative + * @returns {number} + * @inner + */ + function mkId(value, mayBeNegative) { + var id = -1, + sign = 1; + if (value.charAt(0) == '-') { + sign = -1; + value = value.substring(1); + } + if (Lang.NUMBER_DEC.test(value)) + id = parseInt(value); + else if (Lang.NUMBER_HEX.test(value)) + id = parseInt(value.substring(2), 16); + else if (Lang.NUMBER_OCT.test(value)) + id = parseInt(value.substring(1), 8); + else + throw Error("illegal id value: " + (sign < 0 ? '-' : '') + value); + id = (sign*id)|0; // Force to 32bit + if (!mayBeNegative && id < 0) + throw Error("illegal id value: " + (sign < 0 ? '-' : '') + value); + 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; - }; + /** + * Converts a numerical string to a number. + * @param {string} val + * @returns {number} + * @inner + */ + function mkNumber(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 (val === 'inf') + return sign * Infinity; + else if (val === 'nan') + return NaN; + else if (Lang.NUMBER_FLT.test(val)) + return sign * parseFloat(val); + throw Error("illegal number value: " + (sign < 0 ? '-' : '') + val); + } - /** - * 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) { + // ----- Reading ------ + + /** + * Reads a string. + * @returns {string} + * @private + */ + ParserPrototype._readString = function() { + var value = "", + token, + delim; + do { + delim = this.tn.next(); + if (delim !== "'" && delim !== '"') + throw Error("illegal string delimiter: "+delim); + value += this.tn.next(); + this.tn.skip(delim); 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; - }; + } while (token === '"' || token === '"'); // multi line? + return value; + }; - /** - * 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) { + /** + * Reads a value. + * @param {boolean=} mayBeTypeRef + * @returns {number|boolean|string} + * @private + */ + ParserPrototype._readValue = function(mayBeTypeRef) { + var token = this.tn.peek(), + value; + if (token === '"' || token === "'") + return this._readString(); + this.tn.next(); + if (Lang.NUMBER.test(token)) + return mkNumber(token); + if (Lang.BOOL.test(token)) + return (token.toLowerCase() === 'true'); + if (mayBeTypeRef && Lang.TYPEREF.test(token)) + return token; + throw Error("illegal value: "+token); + + }; + + // ----- Parsing constructs ----- + + /** + * Parses a namespace option. + * @param {!Object} parent Parent definition + * @param {boolean=} isList + * @private + */ + ParserPrototype._parseOption = function(parent, isList) { + var token = this.tn.next(), + custom = false; + if (token === '(') { + custom = true; 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; + } + 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)) // FIXME: Why should that not be a valid typeref? + throw Error("illegal option name: "+token); + var name = token; + if (custom) { // (my_method_option).foo, (my_method_option), some_method_option, (foo.my_option).bar + this.tn.skip(')'); + name = '('+name+')'; token = this.tn.peek(); - if (token === Lang.STRINGOPEN || token === Lang.STRINGOPEN_SQ) - value = this._parseString(); - else { + if (Lang.FQTYPEREF.test(token)) { + name += token; 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; - }; + } + this.tn.skip('='); + this._parseOptionValue(parent, name); + if (!isList) + this.tn.skip(";"); + }; - /** - * 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); - }; + /** + * Sets an option on the specified options object. + * @param {!Object.} options + * @param {string} name + * @param {string|number|boolean} value + * @inner + */ + function setOption(options, name, value) { + if (typeof options[name] === 'undefined') + options[name] = value; + else { + if (!Array.isArray(options[name])) + options[name] = [ options[name] ]; + options[name].push(value); + } + } - /** - * 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); + /** + * Parses an option value. + * @param {!Object} parent + * @param {string} name + * @private + */ + ParserPrototype._parseOptionValue = function(parent, name) { + var token = this.tn.peek(); + if (token !== '{') { // Plain value + setOption(parent["options"], name, this._readValue(true)); + } else { // Aggregate options + this.tn.skip("{"); + while ((token = this.tn.next()) !== '}') { + if (!Lang.NAME.test(token)) + throw Error("illegal option name: " + name + "." + token); + if (this.tn.omit(":")) + setOption(parent["options"], name + "." + token, this._readValue(true)); else - throw Error("Illegal token in message "+msg.name+" at line "+this.tn.line+": "+token); - } while (true); - parent["messages"].push(msg); - return msg; - }; + this._parseOptionValue(parent, name + "." + token); + } + } + }; - /** - * 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"] = {}; + /** + * Parses a service definition. + * @param {!Object} parent Parent definition + * @private + */ + ParserPrototype._parseService = function(parent) { + var 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": {} + }; + this.tn.skip("{"); + while ((token = this.tn.next()) !== '}') { + if (token === "option") + this._parseOption(svc); + else if (token === 'rpc') + this._parseServiceRPC(svc); + else + throw Error("illegal service token: "+token); + } + this.tn.omit(";"); + parent["services"].push(svc); + }; + + /** + * Parses a RPC service definition of the form ['rpc', name, (request), 'returns', (response)]. + * @param {!Object} svc Service definition + * @private + */ + ParserPrototype._parseServiceRPC = function(svc) { + var type = "rpc", token = this.tn.next(); - if (token === "group") { + if (!Lang.NAME.test(token)) + throw Error("illegal rpc service method name: "+token); + var name = token; + var method = { + "request": null, + "response": null, + "request_stream": false, + "response_stream": false, + "options": {} + }; + this.tn.skip("("); + token = this.tn.next(); + if (token.toLowerCase() === "stream") { + method["request_stream"] = true; + token = this.tn.next(); + } + if (!Lang.TYPEREF.test(token)) + throw Error("illegal rpc service request type: "+token); + method["request"] = token; + this.tn.skip(")"); + token = this.tn.next(); + if (token.toLowerCase() !== "returns") + throw Error("illegal rpc service request type delimiter: "+token); + this.tn.skip("("); + token = this.tn.next(); + if (token.toLowerCase() === "stream") { + method["response_stream"] = true; + token = this.tn.next(); + } + method["response"] = token; + this.tn.skip(")"); + token = this.tn.peek(); + if (token === '{') { + this.tn.next(); + while ((token = this.tn.next()) !== '}') { + if (token === 'option') + this._parseOption(method); + else + throw Error("illegal rpc service token: " + token); + } + this.tn.omit(";"); + } else + this.tn.skip(";"); + 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 + * @returns {!Object} + * @private + */ + ParserPrototype._parseMessage = function(parent, fld) { + var isGroup = !!fld, + token = this.tn.next(); + var msg = { + "name": "", + "fields": [], + "enums": [], + "messages": [], + "options": {}, + "oneofs": {} + // "extensions": undefined + }; + if (!Lang.NAME.test(token)) + throw Error("illegal "+(isGroup ? "group" : "message")+" name: "+token); + msg["name"] = token; + if (isGroup) { + this.tn.skip("="); + fld["id"] = mkId(this.tn.next()); + msg["isGroup"] = true; + } + token = this.tn.peek(); + if (token === '[' && fld) + this._parseFieldOptions(fld); + this.tn.skip("{"); + while ((token = this.tn.next()) !== '}') { + if (Lang.RULE.test(token)) + this._parseMessageField(msg, token); + else if (token === "oneof") + this._parseMessageOneOf(msg); + else if (token === "enum") + this._parseEnum(msg); + else if (token === "message") + this._parseMessage(msg); + else if (token === "option") + this._parseOption(msg); + else if (token === "extensions") + this._parseExtensions(msg); + else if (token === "extend") + this._parseExtend(msg); + else if (Lang.TYPEREF.test(token)) { + if (!this.proto3) + throw Error("illegal field rule: "+token); + this._parseMessageField(msg, "optional", token); + } else + throw Error("illegal message token: "+token); + } + this.tn.omit(";"); + parent["messages"].push(msg); + return msg; + }; + + /** + * Parses a message field. + * @param {!Object} msg Message definition + * @param {string} rule Field rule + * @param {string=} type Field type if already known (never known for maps) + * @returns {!Object} Field descriptor + * @private + */ + ParserPrototype._parseMessageField = function(msg, rule, type) { + if (!Lang.RULE.test(rule)) + throw Error("illegal message field rule: "+rule); + var fld = { + "rule": rule, + "type": "", + "name": "", + "options": {}, + "id": 0 + }; + var token; + if (rule === "map") { + + if (type) + throw Error("illegal type: " + type); + this.tn.skip('<'); + token = this.tn.next(); + if (!Lang.TYPE.test(token) && !Lang.TYPEREF.test(token)) + throw Error("illegal message field type: " + token); + fld["keytype"] = token; + this.tn.skip(','); + token = this.tn.next(); + if (!Lang.TYPE.test(token) && !Lang.TYPEREF.test(token)) + throw Error("illegal message field: " + token); + fld["type"] = token; + this.tn.skip('>'); + token = this.tn.next(); + if (!Lang.NAME.test(token)) + throw Error("illegal message field name: " + token); + fld["name"] = token; + this.tn.skip("="); + fld["id"] = mkId(this.tn.next()); + token = this.tn.peek(); + if (token === '[') + this._parseFieldOptions(fld); + this.tn.skip(";"); + + } else { + + type = typeof type !== 'undefined' ? type : this.tn.next(); + + if (type === "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); + var grp = this._parseMessage(msg, fld); if (!/^[A-Z]/.test(grp["name"])) - throw Error('Group names must start with a capital letter'); + throw Error('illegal group name: '+grp["name"]); fld["type"] = grp["name"]; fld["name"] = grp["name"].toLowerCase(); - token = this.tn.peek(); - if (token === Lang.END) - this.tn.next(); + this.tn.omit(";"); + } 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; + + if (!Lang.TYPE.test(type) && !Lang.TYPEREF.test(type)) + throw Error("illegal message field type: " + type); + fld["type"] = type; token = this.tn.next(); if (!Lang.NAME.test(token)) - throw Error("Illegal field name in message "+msg.name+" at line "+this.tn.line+": "+token); + throw Error("illegal message field name: " + 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); + this.tn.skip("="); + fld["id"] = mkId(this.tn.next()); + token = this.tn.peek(); + if (token === "[") + this._parseFieldOptions(fld); + this.tn.skip(";"); + } - 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); + } + msg["fields"].push(fld); + return fld; + }; /** - * @alias ProtoBuf.Reflect + * Parses a message oneof. + * @param {!Object} msg Message definition + * @private + */ + ParserPrototype._parseMessageOneOf = function(msg) { + var token = this.tn.next(); + if (!Lang.NAME.test(token)) + throw Error("illegal oneof name: "+token); + var name = token, + fld; + var fields = []; + this.tn.skip("{"); + while ((token = this.tn.next()) !== "}") { + fld = this._parseMessageField(msg, "optional", token); + fld["oneof"] = name; + fields.push(fld["id"]); + } + this.tn.omit(";"); + msg["oneofs"][name] = fields; + }; + + /** + * Parses a set of field option definitions. + * @param {!Object} fld Field definition + * @private + */ + ParserPrototype._parseFieldOptions = function(fld) { + this.tn.skip("["); + var token, + first = true; + while ((token = this.tn.peek()) !== ']') { + if (!first) + this.tn.skip(","); + this._parseOption(fld, true); + first = false; + } + this.tn.next(); + }; + + /** + * Parses an enum. + * @param {!Object} msg Message definition + * @private + */ + ParserPrototype._parseEnum = function(msg) { + var enm = { + "name": "", + "values": [], + "options": {} + }; + var token = this.tn.next(); + if (!Lang.NAME.test(token)) + throw Error("illegal name: "+token); + enm["name"] = token; + this.tn.skip("{"); + while ((token = this.tn.next()) !== '}') { + if (token === "option") + this._parseOption(enm); + else { + if (!Lang.NAME.test(token)) + throw Error("illegal name: "+token); + this.tn.skip("="); + var val = { + "name": token, + "id": mkId(this.tn.next(), true) + }; + token = this.tn.peek(); + if (token === "[") + this._parseFieldOptions({ "options": {} }); + this.tn.skip(";"); + enm["values"].push(val); + } + } + this.tn.omit(";"); + msg["enums"].push(enm); + }; + + /** + * Parses an extensions statement. + * @param {!Object} msg Message object + * @private + */ + ParserPrototype._parseExtensions = function(msg) { + var token = this.tn.next(), + range = []; + if (token === "min") + range.push(ProtoBuf.ID_MIN); + else if (token === "max") + range.push(ProtoBuf.ID_MAX); + else + range.push(mkNumber(token)); + this.tn.skip("to"); + token = this.tn.next(); + if (token === "min") + range.push(ProtoBuf.ID_MIN); + else if (token === "max") + range.push(ProtoBuf.ID_MAX); + else + range.push(mkNumber(token)); + this.tn.skip(";"); + msg["extensions"] = range; + }; + + /** + * Parses an extend block. + * @param {!Object} parent Parent object + * @private + */ + ParserPrototype._parseExtend = function(parent) { + var token = this.tn.next(); + if (!Lang.TYPEREF.test(token)) + throw Error("illegal extend reference: "+token); + var ext = { + "ref": token, + "fields": [] + }; + this.tn.skip("{"); + while ((token = this.tn.next()) !== '}') { + if (Lang.RULE.test(token)) + this._parseMessageField(ext, token); + else if (Lang.TYPEREF.test(token)) { + if (!this.proto3) + throw Error("illegal field rule: "+token); + this._parseMessageField(ext, "optional", token); + } else + throw Error("illegal extend token: "+token); + } + this.tn.omit(";"); + parent["messages"].push(ext); + return ext; + }; + + // ----- General ----- + + /** + * Returns a string representation of this parser. + * @returns {string} + */ + ParserPrototype.toString = function() { + return "Parser at line "+this.tn.line; + }; + + /** + * @alias ProtoBuf.DotProto.Parser * @expose */ - ProtoBuf.Reflect = (function(ProtoBuf) { - "use strict"; + 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) { /** - * 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" + * Builder reference. + * @type {!ProtoBuf.Builder} * @expose */ - TPrototype.fqn = function() { - var name = this.name, - ptr = this; - do { + 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 + * @param {string?} syntax The syntax level of this definition (e.g., proto3) + * @constructor + * @extends ProtoBuf.Reflect.T + */ + var Namespace = function(builder, parent, name, options, syntax) { + T.call(this, builder, parent, name); + + /** + * @override + */ + this.className = "Namespace"; + + /** + * Children inside the namespace. + * @type {!Array.} + */ + this.children = []; + + /** + * Options. + * @type {!Object.} + */ + this.options = options || {}; + + /** + * Syntax level (e.g., proto2 or proto3). + * @type {!string} + */ + this.syntax = syntax || "proto2"; + }; + + /** + * @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} qn Qualified name to resolve + * @param {boolean=} excludeNonNamespace Excludes non-namespace types, defaults to `false` + * @return {?ProtoBuf.Reflect.Namespace} The resolved type or null if not found + * @expose + */ + NamespacePrototype.resolve = function(qn, excludeNonNamespace) { + var part = typeof qn === 'string' ? qn.split(".") : qn, + ptr = this, + i = 0; + if (part[i] === "") { // Fully qualified name, e.g. ".My.Message' + while (ptr.parent !== null) 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 Element implementation that checks and converts values for a + * particular field type, as appropriate. + * + * An Element represents a single value: either the value of a singular field, + * or a value contained in one entry of a repeated field or map field. This + * class does not implement these higher-level concepts; it only encapsulates + * the low-level typechecking and conversion. + * + * @exports ProtoBuf.Reflect.Element + * @param {{name: string, wireType: number}} type Resolved data type + * @param {ProtoBuf.Reflect.T|null} resolvedType Resolved type, if relevant + * (e.g. submessage field). + * @param {boolean} isMapKey Is this element a Map key? The value will be + * converted to string form if so. + * @param {string} syntax Syntax level of defining message type, e.g., + * proto2 or proto3. + * @constructor + */ + var Element = function(type, resolvedType, isMapKey, syntax) { /** - * Builds the namespace and returns the runtime counterpart. - * @return {Object.} Runtime namespace - * @expose + * Element type, as a string (e.g., int32). + * @type {{name: string, wireType: number}} */ - NamespacePrototype.build = function() { - /** @dict */ - var ns = {}; - var children = this.children; - for (var i=0, k=children.length, child; 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"); } - if (Object.defineProperty) - Object.defineProperty(ns, "$options", { "value": this.buildOpt() }); - return ns; - }; - /** - * Builds the namespace's '$options' property. - * @return {Object.} - */ - NamespacePrototype.buildOpt = function() { - var opt = {}, - keys = Object.keys(this.options); - for (var i=0, k=keys.length; i 4294967295 || value < 0) + fail(typeof value, "not in range for uint32") + return value; + } else { + // proto2 requires enum values to be valid. + fail(value, "not a valid enum value"); + } + } + // Embedded message + case ProtoBuf.TYPES["group"]: + case ProtoBuf.TYPES["message"]: { + if (!value || typeof value !== 'object') + fail(typeof value, "object expected"); + if (value instanceof this.resolvedType.clazz) + return value; + if (value instanceof ProtoBuf.Builder.Message) { + // Mismatched type: Convert to object (see: https://github.com/dcodeIO/ProtoBuf.js/issues/180) + var obj = {}; + for (var i in value) + if (value.hasOwnProperty(i)) + obj[i] = value[i]; + value = obj; + } + // Else let's try to construct one from a key-value object + return new (this.resolvedType.clazz)(value); // May throw for a hundred of reasons + } + } + + // We should never end here + throw Error("[INTERNAL] Illegal value for "+this.toString(true)+": "+value+" (undefined type "+this.type+")"); + }; + + /** + * Calculates the byte length of an element on the wire. + * @param {number} id Field number + * @param {*} value Field value + * @returns {number} Byte length + * @throws {Error} If the value cannot be calculated + * @expose + */ + ElementPrototype.calculateLength = function(id, value) { + if (value === null) return 0; // Nothing to encode + // Tag has already been written + var n; + switch (this.type) { + case ProtoBuf.TYPES["int32"]: + return value < 0 ? ByteBuffer.calculateVarint64(value) : ByteBuffer.calculateVarint32(value); + case ProtoBuf.TYPES["uint32"]: + return ByteBuffer.calculateVarint32(value); + case ProtoBuf.TYPES["sint32"]: + return ByteBuffer.calculateVarint32(ByteBuffer.zigZagEncode32(value)); + case ProtoBuf.TYPES["fixed32"]: + case ProtoBuf.TYPES["sfixed32"]: + case ProtoBuf.TYPES["float"]: + return 4; + case ProtoBuf.TYPES["int64"]: + case ProtoBuf.TYPES["uint64"]: + return ByteBuffer.calculateVarint64(value); + case ProtoBuf.TYPES["sint64"]: + return ByteBuffer.calculateVarint64(ByteBuffer.zigZagEncode64(value)); + case ProtoBuf.TYPES["fixed64"]: + case ProtoBuf.TYPES["sfixed64"]: + return 8; + case ProtoBuf.TYPES["bool"]: + return 1; + case ProtoBuf.TYPES["enum"]: + return ByteBuffer.calculateVarint32(value); + case ProtoBuf.TYPES["double"]: + return 8; + case ProtoBuf.TYPES["string"]: + n = ByteBuffer.calculateUTF8Bytes(value); + return ByteBuffer.calculateVarint32(n) + n; + case ProtoBuf.TYPES["bytes"]: + if (value.remaining() < 0) + throw Error("Illegal value for "+this.toString(true)+": "+value.remaining()+" bytes remaining"); + return ByteBuffer.calculateVarint32(value.remaining()) + value.remaining(); + case ProtoBuf.TYPES["message"]: + n = this.resolvedType.calculate(value); + return ByteBuffer.calculateVarint32(n) + n; + case ProtoBuf.TYPES["group"]: + n = this.resolvedType.calculate(value); + return n + ByteBuffer.calculateVarint32((id << 3) | ProtoBuf.WIRE_TYPES.ENDGROUP); + } + // We should never end here + throw Error("[INTERNAL] Illegal value to encode in "+this.toString(true)+": "+value+" (unknown type)"); + }; + + /** + * Encodes a value to the specified buffer. Does not encode the key. + * @param {number} id Field number + * @param {*} value Field value + * @param {ByteBuffer} buffer ByteBuffer to encode to + * @return {ByteBuffer} The ByteBuffer for chaining + * @throws {Error} If the value cannot be encoded + * @expose + */ + ElementPrototype.encodeValue = function(id, value, buffer) { + if (value === null) return buffer; // Nothing to encode + // Tag has already been written + + switch (this.type) { + // 32bit signed varint + case ProtoBuf.TYPES["int32"]: + // "If you use int32 or int64 as the type for a negative number, the resulting varint is always ten bytes + // long – it is, effectively, treated like a very large unsigned integer." (see #122) + if (value < 0) + buffer.writeVarint64(value); + else + buffer.writeVarint32(value); + break; + + // 32bit unsigned varint + case ProtoBuf.TYPES["uint32"]: + buffer.writeVarint32(value); + break; + + // 32bit varint zig-zag + case ProtoBuf.TYPES["sint32"]: + buffer.writeVarint32ZigZag(value); + break; + + // Fixed unsigned 32bit + case ProtoBuf.TYPES["fixed32"]: + buffer.writeUint32(value); + break; + + // Fixed signed 32bit + case ProtoBuf.TYPES["sfixed32"]: + buffer.writeInt32(value); + break; + + // 64bit varint as-is + case ProtoBuf.TYPES["int64"]: + case ProtoBuf.TYPES["uint64"]: + buffer.writeVarint64(value); // throws + break; + + // 64bit varint zig-zag + case ProtoBuf.TYPES["sint64"]: + buffer.writeVarint64ZigZag(value); // throws + break; + + // Fixed unsigned 64bit + case ProtoBuf.TYPES["fixed64"]: + buffer.writeUint64(value); // throws + break; + + // Fixed signed 64bit + case ProtoBuf.TYPES["sfixed64"]: + buffer.writeInt64(value); // throws + break; + + // Bool + case ProtoBuf.TYPES["bool"]: + if (typeof value === 'string') + buffer.writeVarint32(value.toLowerCase() === 'false' ? 0 : !!value); + else + buffer.writeVarint32(value ? 1 : 0); + break; + + // Constant enum value + case ProtoBuf.TYPES["enum"]: + buffer.writeVarint32(value); + break; + + // 32bit float + case ProtoBuf.TYPES["float"]: + buffer.writeFloat32(value); + break; + + // 64bit float + case ProtoBuf.TYPES["double"]: + buffer.writeFloat64(value); + break; + + // Length-delimited string + case ProtoBuf.TYPES["string"]: + buffer.writeVString(value); + break; + + // Length-delimited bytes + case ProtoBuf.TYPES["bytes"]: + if (value.remaining() < 0) + throw Error("Illegal value for "+this.toString(true)+": "+value.remaining()+" bytes remaining"); + var prevOffset = value.offset; + buffer.writeVarint32(value.remaining()); + buffer.append(value); + value.offset = prevOffset; + break; + + // Embedded message + case ProtoBuf.TYPES["message"]: + var bb = new ByteBuffer().LE(); + this.resolvedType.encode(value, bb); + buffer.writeVarint32(bb.offset); + buffer.append(bb.flip()); + break; + + // Legacy group + case ProtoBuf.TYPES["group"]: + this.resolvedType.encode(value, buffer); + buffer.writeVarint32((id << 3) | ProtoBuf.WIRE_TYPES.ENDGROUP); + break; + + default: + // We should never end here + throw Error("[INTERNAL] Illegal value to encode in "+this.toString(true)+": "+value+" (unknown type)"); + } + return buffer; + }; + + /** + * Decode one element value from the specified buffer. + * @param {ByteBuffer} buffer ByteBuffer to decode from + * @param {number} wireType The field wire type + * @param {number} id The field number + * @return {*} Decoded value + * @throws {Error} If the field cannot be decoded + * @expose + */ + ElementPrototype.decode = function(buffer, wireType, id) { + if (wireType != this.type.wireType) + throw Error("Unexpected wire type for element"); + + var value, nBytes; + 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, id); + } + + // We should never end here + throw Error("[INTERNAL] Illegal decode type"); + }; + + /** + * Converts a value from a string to the canonical element type. + * + * Legal only when isMapKey is true. + * + * @param {string} str The string value + * @returns {*} The value + */ + ElementPrototype.valueFromString = function(str) { + if (!this.isMapKey) { + throw Error("valueFromString() called on non-map-key element"); + } + + switch (this.type) { + case ProtoBuf.TYPES["int32"]: + case ProtoBuf.TYPES["sint32"]: + case ProtoBuf.TYPES["sfixed32"]: + case ProtoBuf.TYPES["uint32"]: + case ProtoBuf.TYPES["fixed32"]: + return this.verifyValue(parseInt(str)); + + case ProtoBuf.TYPES["int64"]: + case ProtoBuf.TYPES["sint64"]: + case ProtoBuf.TYPES["sfixed64"]: + case ProtoBuf.TYPES["uint64"]: + case ProtoBuf.TYPES["fixed64"]: + // Long-based fields support conversions from string already. + return this.verifyValue(str); + + case ProtoBuf.TYPES["bool"]: + return str === "true"; + + case ProtoBuf.TYPES["string"]: + return this.verifyValue(str); + + case ProtoBuf.TYPES["bytes"]: + return ByteBuffer.fromBinary(str); + } + }; + + /** + * Converts a value from the canonical element type to a string. + * + * It should be the case that `valueFromString(valueToString(val))` returns + * a value equivalent to `verifyValue(val)` for every legal value of `val` + * according to this element type. + * + * This may be used when the element must be stored or used as a string, + * e.g., as a map key on an Object. + * + * Legal only when isMapKey is true. + * + * @param {*} val The value + * @returns {string} The string form of the value. + */ + ElementPrototype.valueToString = function(value) { + if (!this.isMapKey) { + throw Error("valueToString() called on non-map-key element"); + } + + if (this.type === ProtoBuf.TYPES["bytes"]) { + return value.toString("binary"); + } else { + return value.toString(); + } + }; + + /** + * @alias ProtoBuf.Reflect.Element + * @expose + */ + Reflect.Element = Element; + + /** + * 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 + * @param {string?} syntax The syntax level of this definition (e.g., proto3) + * @constructor + * @extends ProtoBuf.Reflect.Namespace + */ + var Message = function(builder, parent, name, options, isGroup, syntax) { + Namespace.call(this, builder, parent, name, options, syntax); /** - * Gets the value assigned to the option with the specified name. - * @param {string=} name Returns the option value if specified, otherwise all options are returned. - * @return {*|Object.}null} Option value or NULL if there is no such option + * @override */ - NamespacePrototype.getOption = function(name) { - if (typeof name === 'undefined') - return this.options; - return typeof this.options[name] !== 'undefined' ? this.options[name] : null; - }; + this.className = "Message"; /** - * @alias ProtoBuf.Reflect.Namespace + * Extensions range. + * @type {!Array.} * @expose */ - Reflect.Namespace = Namespace; + this.extensions = [ProtoBuf.ID_MIN, ProtoBuf.ID_MAX]; /** - * 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 + * Runtime message class. + * @type {?function(new:ProtoBuf.Builder.Message)} * @expose */ - MessagePrototype.build = function(rebuild) { - if (this.clazz && !rebuild) - return this.clazz; + this.clazz = null; - // Create the runtime Message class in its own scope - var clazz = (function(ProtoBuf, T) { + /** + * Whether this is a legacy group or not. + * @type {boolean} + * @expose + */ + this.isGroup = !!isGroup; - var fields = T.getChildren(ProtoBuf.Reflect.Message.Field), - oneofs = T.getChildren(ProtoBuf.Reflect.Message.OneOf); + // The following cached collections are used to efficiently iterate over or look up fields when decoding. - /** - * 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); + /** + * Cached fields. + * @type {?Array.} + * @private + */ + this._fields = null; - // Create virtual oneof properties - for (var i=0, k=oneofs.length; i} + * @private + */ + this._fieldsById = null; - if (arguments.length > 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} + * @private + */ + this._fieldsByName = null; + }; - /** - * @alias ProtoBuf.Builder.Message.prototype - * @inner - */ - var MessagePrototype = Message.prototype = Object.create(ProtoBuf.Builder.Message.prototype); + /** + * @alias ProtoBuf.Reflect.Message.prototype + * @inner + */ + var MessagePrototype = Message.prototype = Object.create(Namespace.prototype); - /** - * Adds a value to a repeated field. - * @name ProtoBuf.Builder.Message#add - * @function - * @param {string} key Field name - * @param {*} value Value to add - * @param {boolean=} noAssert Whether to assert the value or not (asserts by default) - * @throws {Error} If the value cannot be added - * @expose - */ - MessagePrototype.add = function(key, value, noAssert) { - var field = T._fieldsByName[key]; - if (!noAssert) { - if (!field) - throw Error(this+"#"+key+" is undefined"); - if (!(field instanceof ProtoBuf.Reflect.Message.Field)) - throw Error(this+"#"+key+" is not a field: "+field.toString(true)); // May throw if it's an enum or embedded message - if (!field.repeated) - throw Error(this+"#"+key+" is not a repeated field"); - } - if (this[field.name] === null) - this[field.name] = []; - this[field.name].push(noAssert ? value : field.verifyValue(value, true)); - }; + /** + * 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; - /** - * Adds a value to a repeated field. This is an alias for {@link ProtoBuf.Builder.Message#add}. - * @name ProtoBuf.Builder.Message#$add - * @function - * @param {string} key Field name - * @param {*} value Value to add - * @param {boolean=} noAssert Whether to assert the value or not (asserts by default) - * @throws {Error} If the value cannot be added - * @expose - */ - MessagePrototype.$add = MessagePrototype.add; + // Create the runtime Message class in its own scope + var clazz = (function(ProtoBuf, T) { - /** - * Sets a field's value. - * @name ProtoBuf.Builder.Message#set - * @function - * @param {string} key Key - * @param {*} value Value to set - * @param {boolean=} noAssert Whether to not assert for an actual field / proper value type, defaults to `false` - * @returns {!ProtoBuf.Builder.Message} this - * @throws {Error} If the value cannot be set - * @expose - */ - MessagePrototype.set = function(key, value, noAssert) { - if (key && typeof key === 'object') { - for (var i in key) - if (key.hasOwnProperty(i)) - this.$set(i, key[i], noAssert); - return this; - } - var field = T._fieldsByName[key]; - if (!noAssert) { - if (!field) - throw Error(this+"#"+key+" is not a field: undefined"); - if (!(field instanceof ProtoBuf.Reflect.Message.Field)) - throw Error(this+"#"+key+" is not a field: "+field.toString(true)); - this[field.name] = (value = field.verifyValue(value)); // May throw - } else { - this[field.name] = value; - } - if (field.oneof) { - if (value !== null) { - if (this[field.oneof.name] !== null) - this[this[field.oneof.name]] = null; // Unset the previous (field name is the oneof field's value) - this[field.oneof.name] = field.name; - } else if (field.oneof.name === key) - this[field.oneof.name] = null; - } - return this; - }; + var fields = T.getChildren(ProtoBuf.Reflect.Message.Field), + oneofs = T.getChildren(ProtoBuf.Reflect.Message.OneOf); - /** - * Sets a field's value. This is an alias for [@link ProtoBuf.Builder.Message#set}. - * @name ProtoBuf.Builder.Message#$set - * @function - * @param {string} key Key - * @param {*} value Value to set - * @param {boolean=} noAssert Whether to not assert the value, defaults to `false` - * @throws {Error} If the value cannot be set - * @expose - */ - MessagePrototype.$set = MessagePrototype.set; + /** + * 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); - /** - * Gets a field's value. - * @name ProtoBuf.Builder.Message#get - * @function - * @param {string} key Key - * @param {boolean=} noAssert Whether to not assert for an actual field, defaults to `false` - * @return {*} Value - * @throws {Error} If there is no such field - * @expose - */ - MessagePrototype.get = function(key, noAssert) { - if (noAssert) - return this[key]; - var field = T._fieldsByName[key]; - if (!field || !(field instanceof ProtoBuf.Reflect.Message.Field)) - throw Error(this+"#"+key+" is not a field: undefined"); - if (!(field instanceof ProtoBuf.Reflect.Message.Field)) - throw Error(this+"#"+key+" is not a field: "+field.toString(true)); - return this[field.name]; - }; - - /** - * Gets a field's value. This is an alias for {@link ProtoBuf.Builder.Message#$get}. - * @name ProtoBuf.Builder.Message#$get - * @function - * @param {string} key Key - * @return {*} Value - * @throws {Error} If there is no such field - * @expose - */ - MessagePrototype.$get = MessagePrototype.get; - - // Getters and setters - - for (var i=0; i 0) { + var value; + // Set field values from a values object + if (arguments.length === 1 && values !== null && typeof values === 'object' && + /* not _another_ Message */ (typeof values.encode !== 'function' || values instanceof Message) && + /* not a repeated field */ !Array.isArray(values) && + /* not a Map */ !(values instanceof ProtoBuf.Map) && + /* not a ByteBuffer */ !ByteBuffer.isByteBuffer(values) && + /* not an ArrayBuffer */ !(values instanceof ArrayBuffer) && + /* not a Long */ !(ProtoBuf.Long && values instanceof ProtoBuf.Long)) { + this.$set(values); + } else // Set field values from arguments, in declaration order + for (i=0, k=arguments.length; i} keyOrObj String key or plain object holding multiple values + * @param {(*|boolean)=} value Value to set if key is a string, otherwise omitted + * @param {boolean=} noAssert Whether to not assert for an actual field / proper value type, defaults to `false` + * @returns {!ProtoBuf.Builder.Message} this + * @throws {Error} If the value cannot be set + * @expose + */ + MessagePrototype.set = function(keyOrObj, value, noAssert) { + if (keyOrObj && typeof keyOrObj === 'object') { + noAssert = value; + for (var ikey in keyOrObj) + if (keyOrObj.hasOwnProperty(ikey) && typeof (value = keyOrObj[ikey]) !== 'undefined') + this.$set(ikey, value, noAssert); + return this; + } + var field = T._fieldsByName[keyOrObj]; + if (!noAssert) { + if (!field) + throw Error(this+"#"+keyOrObj+" is not a field: undefined"); + if (!(field instanceof ProtoBuf.Reflect.Message.Field)) + throw Error(this+"#"+keyOrObj+" is not a field: "+field.toString(true)); + this[field.name] = (value = field.verifyValue(value)); // May throw + } else + this[keyOrObj] = value; + if (field && field.oneof) { // Field is part of an OneOf (not a virtual OneOf field) + var currentField = this[field.oneof.name]; // Virtual field references currently set field + if (value !== null) { + if (currentField !== null && currentField !== field.name) + this[currentField] = null; // Clear currently set field + this[field.oneof.name] = field.name; // Point virtual field at this field + } else if (/* value === null && */currentField === keyOrObj) + this[field.oneof.name] = null; // Clear virtual field (current field explicitly cleared) + } + return this; + }; + + /** + * Sets a field's value. This is an alias for [@link ProtoBuf.Builder.Message#set}. + * @name ProtoBuf.Builder.Message#$set + * @function + * @param {string|!Object.} keyOrObj String key or plain object holding multiple values + * @param {(*|boolean)=} value Value to set if key is a string, otherwise omitted + * @param {boolean=} noAssert Whether to not assert the value, defaults to `false` + * @throws {Error} If the value cannot be set + * @expose + */ + MessagePrototype.$set = MessagePrototype.set; + + /** + * Gets a field's value. + * @name ProtoBuf.Builder.Message#get + * @function + * @param {string} key Key + * @param {boolean=} noAssert Whether to not assert for an actual field, defaults to `false` + * @return {*} Value + * @throws {Error} If there is no such field + * @expose + */ + MessagePrototype.get = function(key, noAssert) { + if (noAssert) + return this[key]; + var field = T._fieldsByName[key]; + if (!field || !(field instanceof ProtoBuf.Reflect.Message.Field)) + throw Error(this+"#"+key+" is not a field: undefined"); + if (!(field instanceof ProtoBuf.Reflect.Message.Field)) + throw Error(this+"#"+key+" is not a field: "+field.toString(true)); + return this[field.name]; + }; + + /** + * Gets a field's value. This is an alias for {@link ProtoBuf.Builder.Message#$get}. + * @name ProtoBuf.Builder.Message#$get + * @function + * @param {string} key Key + * @return {*} Value + * @throws {Error} If there is no such field + * @expose + */ + MessagePrototype.$get = MessagePrototype.get; + + // Getters and setters + + for (var i=0; i} data Data payload + * @param {(!ByteBuffer|boolean)=} buffer ByteBuffer to encode to. Will create a new one and flip it if omitted. + * @param {boolean=} noVerify Whether to not verify field values, defaults to `false` + * @return {!ByteBuffer} Encoded message as a ByteBuffer + * @expose + */ + Message.encode = function(data, buffer, noVerify) { + return new Message(data).encode(buffer, noVerify); + }; - /** - * Encodes the message. - * @name ProtoBuf.Builder.Message#$encode - * @function - * @param {(!ByteBuffer|boolean)=} buffer ByteBuffer to encode to. Will create a new one and flip it if omitted. - * @param {boolean=} noVerify Whether to not verify field values, defaults to `false` - * @return {!ByteBuffer} Encoded message as a ByteBuffer - * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still - * returns the encoded ByteBuffer in the `encoded` property on the error. - * @expose - * @see ProtoBuf.Builder.Message#encode64 - * @see ProtoBuf.Builder.Message#encodeHex - * @see ProtoBuf.Builder.Message#encodeAB - */ - MessagePrototype.encode = function(buffer, noVerify) { - if (typeof buffer === 'boolean') - noVerify = buffer, - buffer = undefined; - var isNew = false; - if (!buffer) - buffer = new ByteBuffer(), - isNew = true; - var le = buffer.littleEndian; - try { - T.encode(this, buffer.LE(), noVerify); - return (isNew ? buffer.flip() : buffer).LE(le); - } catch (e) { - buffer.LE(le); - throw(e); + /** + * Calculates the byte length of the message. + * @name ProtoBuf.Builder.Message#calculate + * @function + * @returns {number} Byte length + * @throws {Error} If the message cannot be calculated or if required fields are missing. + * @expose + */ + MessagePrototype.calculate = function() { + return T.calculate(this); + }; + + /** + * Encodes the varint32 length-delimited message. + * @name ProtoBuf.Builder.Message#encodeDelimited + * @function + * @param {(!ByteBuffer|boolean)=} buffer ByteBuffer to encode to. Will create a new one and flip it if omitted. + * @return {!ByteBuffer} Encoded message as a ByteBuffer + * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still + * returns the encoded ByteBuffer in the `encoded` property on the error. + * @expose + */ + MessagePrototype.encodeDelimited = function(buffer) { + var isNew = false; + if (!buffer) + buffer = new ByteBuffer(), + isNew = true; + var enc = new ByteBuffer().LE(); + T.encode(this, enc).flip(); + buffer.writeVarint32(enc.remaining()); + buffer.append(enc); + return isNew ? buffer.flip() : buffer; + }; + + /** + * Directly encodes the message to an ArrayBuffer. + * @name ProtoBuf.Builder.Message#encodeAB + * @function + * @return {ArrayBuffer} Encoded message as ArrayBuffer + * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still + * returns the encoded ArrayBuffer in the `encoded` property on the error. + * @expose + */ + MessagePrototype.encodeAB = function() { + try { + return this.encode().toArrayBuffer(); + } catch (e) { + if (e["encoded"]) e["encoded"] = e["encoded"].toArrayBuffer(); + throw(e); + } + }; + + /** + * Returns the message as an ArrayBuffer. This is an alias for {@link ProtoBuf.Builder.Message#encodeAB}. + * @name ProtoBuf.Builder.Message#toArrayBuffer + * @function + * @return {ArrayBuffer} Encoded message as ArrayBuffer + * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still + * returns the encoded ArrayBuffer in the `encoded` property on the error. + * @expose + */ + MessagePrototype.toArrayBuffer = MessagePrototype.encodeAB; + + /** + * Directly encodes the message to a node Buffer. + * @name ProtoBuf.Builder.Message#encodeNB + * @function + * @return {!Buffer} + * @throws {Error} If the message cannot be encoded, not running under node.js or if required fields are + * missing. The later still returns the encoded node Buffer in the `encoded` property on the error. + * @expose + */ + MessagePrototype.encodeNB = function() { + try { + return this.encode().toBuffer(); + } catch (e) { + if (e["encoded"]) e["encoded"] = e["encoded"].toBuffer(); + throw(e); + } + }; + + /** + * Returns the message as a node Buffer. This is an alias for {@link ProtoBuf.Builder.Message#encodeNB}. + * @name ProtoBuf.Builder.Message#toBuffer + * @function + * @return {!Buffer} + * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still + * returns the encoded node Buffer in the `encoded` property on the error. + * @expose + */ + MessagePrototype.toBuffer = MessagePrototype.encodeNB; + + /** + * Directly encodes the message to a base64 encoded string. + * @name ProtoBuf.Builder.Message#encode64 + * @function + * @return {string} Base64 encoded string + * @throws {Error} If the underlying buffer cannot be encoded or if required fields are missing. The later + * still returns the encoded base64 string in the `encoded` property on the error. + * @expose + */ + MessagePrototype.encode64 = function() { + try { + return this.encode().toBase64(); + } catch (e) { + if (e["encoded"]) e["encoded"] = e["encoded"].toBase64(); + throw(e); + } + }; + + /** + * Returns the message as a base64 encoded string. This is an alias for {@link ProtoBuf.Builder.Message#encode64}. + * @name ProtoBuf.Builder.Message#toBase64 + * @function + * @return {string} Base64 encoded string + * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still + * returns the encoded base64 string in the `encoded` property on the error. + * @expose + */ + MessagePrototype.toBase64 = MessagePrototype.encode64; + + /** + * Directly encodes the message to a hex encoded string. + * @name ProtoBuf.Builder.Message#encodeHex + * @function + * @return {string} Hex encoded string + * @throws {Error} If the underlying buffer cannot be encoded or if required fields are missing. The later + * still returns the encoded hex string in the `encoded` property on the error. + * @expose + */ + MessagePrototype.encodeHex = function() { + try { + return this.encode().toHex(); + } catch (e) { + if (e["encoded"]) e["encoded"] = e["encoded"].toHex(); + throw(e); + } + }; + + /** + * Returns the message as a hex encoded string. This is an alias for {@link ProtoBuf.Builder.Message#encodeHex}. + * @name ProtoBuf.Builder.Message#toHex + * @function + * @return {string} Hex encoded string + * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still + * returns the encoded hex string in the `encoded` property on the error. + * @expose + */ + MessagePrototype.toHex = MessagePrototype.encodeHex; + + /** + * Clones a message object or field value to a raw object. + * @param {*} obj Object to clone + * @param {boolean} binaryAsBase64 Whether to include binary data as base64 strings or as a buffer otherwise + * @param {boolean} longsAsStrings Whether to encode longs as strings + * @param {!ProtoBuf.Reflect.T=} resolvedType The resolved field type if a field + * @returns {*} Cloned object + * @inner + */ + function cloneRaw(obj, binaryAsBase64, longsAsStrings, resolvedType) { + if (obj === null || typeof obj !== 'object') { + // Convert enum values to their respective names + if (resolvedType && resolvedType instanceof ProtoBuf.Reflect.Enum) { + var name = ProtoBuf.Reflect.Enum.getName(resolvedType.object, obj); + if (name !== null) + return name; } - }; - - /** - * Calculates the byte length of the message. - * @name ProtoBuf.Builder.Message#calculate - * @function - * @returns {number} Byte length - * @throws {Error} If the message cannot be calculated or if required fields are missing. - * @expose - */ - MessagePrototype.calculate = function() { - return T.calculate(this); - }; - - /** - * Encodes the varint32 length-delimited message. - * @name ProtoBuf.Builder.Message#encodeDelimited - * @function - * @param {(!ByteBuffer|boolean)=} buffer ByteBuffer to encode to. Will create a new one and flip it if omitted. - * @return {!ByteBuffer} Encoded message as a ByteBuffer - * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still - * returns the encoded ByteBuffer in the `encoded` property on the error. - * @expose - */ - MessagePrototype.encodeDelimited = function(buffer) { - var isNew = false; - if (!buffer) - buffer = new ByteBuffer(), - isNew = true; - var enc = new ByteBuffer().LE(); - T.encode(this, enc).flip(); - buffer.writeVarint32(enc.remaining()); - buffer.append(enc); - return isNew ? buffer.flip() : buffer; - }; - - /** - * Directly encodes the message to an ArrayBuffer. - * @name ProtoBuf.Builder.Message#encodeAB - * @function - * @return {ArrayBuffer} Encoded message as ArrayBuffer - * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still - * returns the encoded ArrayBuffer in the `encoded` property on the error. - * @expose - */ - MessagePrototype.encodeAB = function() { - try { - return this.encode().toArrayBuffer(); - } catch (e) { - if (e["encoded"]) e["encoded"] = e["encoded"].toArrayBuffer(); - throw(e); - } - }; - - /** - * Returns the message as an ArrayBuffer. This is an alias for {@link ProtoBuf.Builder.Message#encodeAB}. - * @name ProtoBuf.Builder.Message#toArrayBuffer - * @function - * @return {ArrayBuffer} Encoded message as ArrayBuffer - * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still - * returns the encoded ArrayBuffer in the `encoded` property on the error. - * @expose - */ - MessagePrototype.toArrayBuffer = MessagePrototype.encodeAB; - - /** - * Directly encodes the message to a node Buffer. - * @name ProtoBuf.Builder.Message#encodeNB - * @function - * @return {!Buffer} - * @throws {Error} If the message cannot be encoded, not running under node.js or if required fields are - * missing. The later still returns the encoded node Buffer in the `encoded` property on the error. - * @expose - */ - MessagePrototype.encodeNB = function() { - try { - return this.encode().toBuffer(); - } catch (e) { - if (e["encoded"]) e["encoded"] = e["encoded"].toBuffer(); - throw(e); - } - }; - - /** - * Returns the message as a node Buffer. This is an alias for {@link ProtoBuf.Builder.Message#encodeNB}. - * @name ProtoBuf.Builder.Message#toBuffer - * @function - * @return {!Buffer} - * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still - * returns the encoded node Buffer in the `encoded` property on the error. - * @expose - */ - MessagePrototype.toBuffer = MessagePrototype.encodeNB; - - /** - * Directly encodes the message to a base64 encoded string. - * @name ProtoBuf.Builder.Message#encode64 - * @function - * @return {string} Base64 encoded string - * @throws {Error} If the underlying buffer cannot be encoded or if required fields are missing. The later - * still returns the encoded base64 string in the `encoded` property on the error. - * @expose - */ - MessagePrototype.encode64 = function() { - try { - return this.encode().toBase64(); - } catch (e) { - if (e["encoded"]) e["encoded"] = e["encoded"].toBase64(); - throw(e); - } - }; - - /** - * Returns the message as a base64 encoded string. This is an alias for {@link ProtoBuf.Builder.Message#encode64}. - * @name ProtoBuf.Builder.Message#toBase64 - * @function - * @return {string} Base64 encoded string - * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still - * returns the encoded base64 string in the `encoded` property on the error. - * @expose - */ - MessagePrototype.toBase64 = MessagePrototype.encode64; - - /** - * Directly encodes the message to a hex encoded string. - * @name ProtoBuf.Builder.Message#encodeHex - * @function - * @return {string} Hex encoded string - * @throws {Error} If the underlying buffer cannot be encoded or if required fields are missing. The later - * still returns the encoded hex string in the `encoded` property on the error. - * @expose - */ - MessagePrototype.encodeHex = function() { - try { - return this.encode().toHex(); - } catch (e) { - if (e["encoded"]) e["encoded"] = e["encoded"].toHex(); - throw(e); - } - }; - - /** - * Returns the message as a hex encoded string. This is an alias for {@link ProtoBuf.Builder.Message#encodeHex}. - * @name ProtoBuf.Builder.Message#toHex - * @function - * @return {string} Hex encoded string - * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still - * returns the encoded hex string in the `encoded` property on the error. - * @expose - */ - MessagePrototype.toHex = MessagePrototype.encodeHex; - - /** - * Clones a message object to a raw object. - * @param {*} obj Object to clone - * @param {boolean} includeBinaryAsBase64 Whether to include binary data as base64 strings or not - * @returns {*} Cloned object - * @inner - */ - function cloneRaw(obj, includeBinaryAsBase64) { - var clone = {}; - for (var i in obj) - if (obj.hasOwnProperty(i)) { - if (obj[i] === null || typeof obj[i] !== 'object') - clone[i] = obj[i]; - else if (obj[i] instanceof ByteBuffer) { - if (includeBinaryAsBase64) - clone[i] = obj[i].toBase64(); - } else // is a non-null object - clone[i] = cloneRaw(obj[i], includeBinaryAsBase64); - } + // Pass-through string, number, boolean, null... + return obj; + } + // Convert ByteBuffers to raw buffer or strings + if (ByteBuffer.isByteBuffer(obj)) + return binaryAsBase64 ? obj.toBase64() : obj.toBuffer(); + // Convert Longs to proper objects or strings + if (ProtoBuf.Long.isLong(obj)) + return longsAsStrings ? obj.toString() : new ProtoBuf.Long(obj); + var clone; + // Clone arrays + if (Array.isArray(obj)) { + clone = []; + obj.forEach(function(v, k) { + clone[k] = cloneRaw(v, binaryAsBase64, longsAsStrings, resolvedType); + }); return clone; } - - /** - * Returns the message's raw payload. - * @param {boolean=} includeBinaryAsBase64 Whether to include binary data as base64 strings or not, defaults to `false` - * @returns {Object.} 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); + clone = {}; + // Convert maps to objects + if (obj instanceof ProtoBuf.Map) { + var it = obj.entries(); + for (var e = it.next(); !e.done; e = it.next()) + clone[obj.keyElem.valueToString(e.value[0])] = cloneRaw(e.value[1], binaryAsBase64, longsAsStrings, obj.valueElem.resolvedType); + return clone; + } + // Everything else is a non-null object + var type = obj.$type, + field = undefined; + for (var i in obj) + if (obj.hasOwnProperty(i)) { + if (type && (field = type.getChild(i))) + clone[i] = cloneRaw(obj[i], binaryAsBase64, longsAsStrings, field.resolvedType); + else + clone[i] = cloneRaw(obj[i], binaryAsBase64, longsAsStrings); } - }; - - /** - * 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} Raw payload + * @expose + */ + MessagePrototype.toRaw = function(binaryAsBase64, longsAsStrings) { + return cloneRaw(this, !!binaryAsBase64, !!longsAsStrings, this.$type); + }; - /** - * Encodes a runtime message's contents to the specified buffer. - * @param {!ProtoBuf.Builder.Message} message Runtime message to encode - * @param {ByteBuffer} buffer ByteBuffer to write to - * @param {boolean=} noVerify Whether to not verify field values, defaults to `false` - * @return {ByteBuffer} The ByteBuffer for chaining - * @throws {Error} If required fields are missing or the message cannot be encoded for another reason - * @expose - */ - MessagePrototype.encode = function(message, buffer, noVerify) { - var fieldMissing = null, - field; - for (var i=0, k=this._fields.length, val; 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; + /** + * 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 = ByteBuffer.isByteBuffer(buffer) ? 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); } - 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; + }; + + /** + * 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 = ByteBuffer.isByteBuffer(buffer) ? 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; } - 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; - } + 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; } - } - - // 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} + * 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 */ - this.required = rule === "required"; + Message.decode64 = function(str) { + return Message.decode(str, "base64"); + }; /** - * Message field repeated flag. - * @type {boolean} + * 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 */ - this.repeated = rule === "repeated"; + Message.decodeHex = function(str) { + return Message.decode(str, "hex"); + }; /** - * Message field type. Type reference string if unresolved, protobuf type if resolved. - * @type {string|{name: string, wireType: number}} + * Decodes the message from a JSON string. + * @name ProtoBuf.Builder.Message.decodeJSON + * @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. * @expose */ - this.type = type; + Message.decodeJSON = function(str) { + return new Message(JSON.parse(str)); + }; + + // Utility /** - * Resolved type reference inside the global namespace. - * @type {ProtoBuf.Reflect.T|null} + * Returns a string representation of this Message. + * @name ProtoBuf.Builder.Message#toString + * @function + * @return {string} String representation as of ".Fully.Qualified.MessageName" * @expose */ - this.resolvedType = null; + MessagePrototype.toString = function() { + return T.toString(); + }; + + // Properties /** - * Unique message field id. - * @type {number} + * Message options. + * @name ProtoBuf.Builder.Message.$options + * @type {Object.} * @expose */ - this.id = id; + var $optionsS; // cc needs this /** - * Message field options. - * @type {!Object.} - * @dict + * Message options. + * @name ProtoBuf.Builder.Message#$options + * @type {Object.} * @expose */ - this.options = options || {}; + var $options; /** - * Default value. - * @type {*} + * Reflection type. + * @name ProtoBuf.Builder.Message.$type + * @type {!ProtoBuf.Reflect.Message} * @expose */ - this.defaultValue = null; + var $typeS; /** - * Enclosing OneOf. - * @type {?ProtoBuf.Reflect.Message.OneOf} + * Reflection type. + * @name ProtoBuf.Builder.Message#$type + * @type {!ProtoBuf.Reflect.Message} * @expose */ - this.oneof = oneof || null; + var $type; - /** - * 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>> 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 if (field.map) { + var keyval = field.decode(wireType, buffer); + msg[field.name].set(keyval[0], keyval[1]); + } else { + msg[field.name] = field.decode(wireType, buffer); + if (field.oneof) { // Field is part of an OneOf (not a virtual OneOf field) + var currentField = msg[field.oneof.name]; // Virtual field references currently set field + if (currentField !== null && currentField !== field.name) + msg[currentField] = null; // Clear currently set field + msg[field.oneof.name] = field.name; // Point virtual field at this field + } + } + } + + // 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 + * @param {string?} syntax The syntax level of this definition (e.g., proto3) + * @constructor + * @extends ProtoBuf.Reflect.T + */ + var Field = function(builder, message, rule, keytype, type, name, id, options, oneof, syntax) { + T.call(this, builder, message, name); /** - * @alias ProtoBuf.Reflect.Enum + * @override + */ + this.className = "Message.Field"; + + /** + * Message field required flag. + * @type {boolean} * @expose */ - Reflect.Enum = Enum; + this.required = rule === "required"; /** - * Constructs a new Enum Value. - * @exports ProtoBuf.Reflect.Enum.Value - * @param {!ProtoBuf.Builder} builder Builder reference - * @param {!ProtoBuf.Reflect.Enum} enm Enum reference - * @param {string} name Field name - * @param {number} id Unique field id - * @constructor - * @extends ProtoBuf.Reflect.T - */ - var Value = function(builder, enm, name, id) { - T.call(this, builder, enm, name); - - /** - * @override - */ - this.className = "Enum.Value"; - - /** - * Unique enum value id. - * @type {number} - * @expose - */ - this.id = id; - }; - - // Extends T - Value.prototype = Object.create(T.prototype); - - /** - * @alias ProtoBuf.Reflect.Enum.Value + * Message field repeated flag. + * @type {boolean} * @expose */ - Reflect.Enum.Value = Value; + this.repeated = rule === "repeated"; /** - * An extension (field). - * @exports ProtoBuf.Reflect.Extension - * @constructor - * @param {!ProtoBuf.Builder} builder Builder reference - * @param {!ProtoBuf.Reflect.T} parent Parent object - * @param {string} name Object name - * @param {!ProtoBuf.Reflect.Message.Field} field Extension field - */ - var Extension = function(builder, parent, name, field) { - T.call(this, builder, parent, name); - - /** - * Extended message field. - * @type {!ProtoBuf.Reflect.Message.Field} - * @expose - */ - this.field = field; - }; - - // Extends T - Extension.prototype = Object.create(T.prototype); - - /** - * @alias ProtoBuf.Reflect.Extension + * Message field map flag. + * @type {boolean} * @expose */ - Reflect.Extension = Extension; + this.map = rule === "map"; /** - * Constructs a new Service. - * @exports ProtoBuf.Reflect.Service - * @param {!ProtoBuf.Builder} builder Builder reference - * @param {!ProtoBuf.Reflect.Namespace} root Root - * @param {string} name Service name - * @param {Object.=} 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 + * Message field key type. Type reference string if unresolved, protobuf + * type if resolved. Valid only if this.map === true, null otherwise. + * @type {string|{name: string, wireType: number}|null} * @expose */ - ServicePrototype.build = function(rebuild) { - if (this.clazz && !rebuild) - return this.clazz; + this.keyType = keytype || null; - // Create the runtime Service class in its own scope - return this.clazz = (function(ProtoBuf, T) { + /** + * Message field type. Type reference string if unresolved, protobuf type if + * resolved. In a map field, this is the value type. + * @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; + + /** + * Syntax level of this definition (e.g., proto3). + * @type {string} + * @expose + */ + this.syntax = syntax || 'proto2'; + + /** + * Original field name. + * @type {string} + * @expose + */ + this.originalName = this.name; // Used to revert camelcase transformation on naming collisions + + /** + * Element implementation. Created in build() after types are resolved. + * @type {ProtoBuf.Element} + * @expose + */ + this.element = null; + + /** + * Key element implementation, for map fields. Created in build() after + * types are resolved. + * @type {ProtoBuf.Element} + * @expose + */ + this.keyElement = null; + + // Convert field names to camel case notation if the override is set + if (this.builder.options['convertFieldsToCamelCase'] && !(this instanceof Message.ExtensionField)) + this.name = ProtoBuf.Util.toCamelCase(this.name); + }; + + /** + * @alias ProtoBuf.Reflect.Message.Field.prototype + * @inner + */ + var FieldPrototype = Field.prototype = Object.create(T.prototype); + + /** + * Builds the field. + * @override + * @expose + */ + FieldPrototype.build = function() { + this.element = new Element(this.type, this.resolvedType, false, this.syntax); + if (this.map) + this.keyElement = new Element(this.keyType, undefined, true, this.syntax); + + // In proto3, fields do not have field presence, and every field is set to + // its type's default value ("", 0, 0.0, or false). + if (this.syntax === 'proto3' && !this.repeated && !this.map) + this.defaultValue = Element.defaultFieldValue(this.type); + + // Otherwise, default values are present when explicitly specified + else if (typeof this.options['default'] !== 'undefined') + this.defaultValue = this.verifyValue(this.options['default']); + }; + + /** + * 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"); + if (this.syntax === 'proto3' && this.type !== ProtoBuf.TYPES["message"]) + fail(typeof value, "proto3 field without field presence cannot be null"); + return null; + } + var i; + if (this.repeated && !skipRepeated) { // Repeated values as arrays + if (!Array.isArray(value)) + value = [value]; + var res = []; + for (i=0; i 0; + + case ProtoBuf.TYPES["bytes"]: + return value.remaining() > 0; + + case ProtoBuf.TYPES["enum"]: + return value !== 0; + + case ProtoBuf.TYPES["message"]: + return value !== null; + default: + return true; + } + }; + + /** + * Encodes the specified field value to the specified buffer. + * @param {*} value Verified field value + * @param {ByteBuffer} buffer ByteBuffer to encode to + * @param {!ProtoBuf.Builder.Message} message Runtime message + * @return {ByteBuffer} The ByteBuffer for chaining + * @throws {Error} If the field cannot be encoded + * @expose + */ + FieldPrototype.encode = function(value, buffer, message) { + if (this.type === null || typeof this.type !== 'object') + throw Error("[INTERNAL] Unresolved type in "+this.toString(true)+": "+this.type); + if (value === null || (this.repeated && value.length == 0)) + return buffer; // Optional omitted + try { + if (this.repeated) { + var i; + // "Only repeated fields of primitive numeric types (types which use the varint, 32-bit, or 64-bit wire + // types) can be declared 'packed'." + if (this.options["packed"] && ProtoBuf.PACKABLE_WIRE_TYPES.indexOf(this.type.wireType) >= 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... + } + + // Handle maps. + if (this.map) { + // Read one (key, value) submessage, and return [key, value] + var key = Element.defaultFieldValue(this.keyType); + value = Element.defaultFieldValue(this.type); + + // Read the length + nBytes = buffer.readVarint32(); + if (buffer.remaining() < nBytes) + throw Error("Illegal number of bytes for "+this.toString(true)+": "+nBytes+" required but got only "+buffer.remaining()); + + // Get a sub-buffer of this key/value submessage + var msgbuf = buffer.clone(); + msgbuf.limit = msgbuf.offset + nBytes; + buffer.offset += nBytes; + + while (msgbuf.remaining() > 0) { + var tag = msgbuf.readVarint32(); + wireType = tag & 0x07; + var id = tag >>> 3; + if (id === 1) { + key = this.keyElement.decode(msgbuf, wireType, id); + } else if (id === 2) { + value = this.element.decode(msgbuf, wireType, id); + } else { + throw Error("Unexpected tag in map field key/value submessage"); + } + } + + return [key, value]; + } + + // Handle singular and non-packed repeated field values. + return this.element.decode(buffer, wireType, this.id); + }; + + /** + * @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, /* keytype = */ null, 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 + * @param {string?} syntax The syntax level (e.g., proto3) + * @constructor + * @extends ProtoBuf.Reflect.Namespace + */ + var Enum = function(builder, parent, name, options, syntax) { + Namespace.call(this, builder, parent, name, options, syntax); + + /** + * @override + */ + this.className = "Enum"; + + /** + * Runtime enum object. + * @type {Object.|null} + * @expose + */ + this.object = null; + }; + + /** + * Gets the string name of an enum value. + * @param {!ProtoBuf.Builder.Enum} enm Runtime enum + * @param {number} value Enum value + * @returns {?string} Name or `null` if not present + * @expose + */ + Enum.getName = function(enm, value) { + var keys = Object.keys(enm); + for (var i=0, key; i} + * @expose + */ + EnumPrototype.build = function(rebuild) { + if (this.object && !rebuild) + return this.object; + var enm = new ProtoBuf.Builder.Enum(), + 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); /** - * 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 + * Service implementation. + * @name ProtoBuf.Builder.Service#rpcImpl + * @type {!function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))} + * @expose */ - 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! - }; + 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); + /** + * @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 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 + */ - /** - * 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); + // Properties /** - * @override - */ - this.className = "Service.Method"; - - /** - * Options. - * @type {Object.} + * Service options. + * @name ProtoBuf.Builder.Service.$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); + var $optionsS; // cc needs this /** - * @override - */ - this.className = "Service.RPCMethod"; - - /** - * Request message name. - * @type {string} + * Service options. + * @name ProtoBuf.Builder.Service#$options + * @type {Object.} * @expose */ - this.requestName = request; + var $options; /** - * Response message name. - * @type {string} + * Reflection type. + * @name ProtoBuf.Builder.Service.$type + * @type {!ProtoBuf.Reflect.Service} * @expose */ - this.responseName = response; + var $typeS; /** - * Resolved request message type. - * @type {ProtoBuf.Reflect.Message} + * Reflection type. + * @name ProtoBuf.Builder.Service#$type + * @type {!ProtoBuf.Reflect.Service} * @expose */ - this.resolvedRequestType = null; + var $type; - /** - * Resolved response message type. - * @type {ProtoBuf.Reflect.Message} - * @expose - */ - this.resolvedResponseType = null; - }; + if (Object.defineProperty) + Object.defineProperty(Service, "$options", { "value": T.buildOpt() }), + Object.defineProperty(ServicePrototype, "$options", { "value": Service["$options"] }), + Object.defineProperty(Service, "$type", { "value": T }), + Object.defineProperty(ServicePrototype, "$type", { "value": T }); - // Extends Method - RPCMethod.prototype = Object.create(Method.prototype); + return Service; - /** - * @alias ProtoBuf.Reflect.Service.RPCMethod - * @expose - */ - Reflect.Service.RPCMethod = RPCMethod; - - return Reflect; - - })(ProtoBuf); + })(ProtoBuf, this); + }; /** - * @alias ProtoBuf.Builder + * @alias ProtoBuf.Reflect.Service * @expose */ - ProtoBuf.Builder = (function(ProtoBuf, Lang, Reflect) { - "use strict"; + Reflect.Service = Service; + + /** + * Abstract service method. + * @exports ProtoBuf.Reflect.Service.Method + * @param {!ProtoBuf.Builder} builder Builder reference + * @param {!ProtoBuf.Reflect.Service} svc Service + * @param {string} name Method name + * @param {Object.=} options Options + * @constructor + * @extends ProtoBuf.Reflect.T + */ + var Method = function(builder, svc, name, options) { + T.call(this, builder, svc, name); /** - * Constructs a new Builder. - * @exports ProtoBuf.Builder - * @class Provides the functionality to build protocol messages. - * @param {Object.=} options Options - * @constructor + * @override */ - 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 || {}; - }; + this.className = "Service.Method"; /** - * @alias ProtoBuf.Builder.prototype - * @inner - */ - var BuilderPrototype = Builder.prototype; - - /** - * Resets the pointer to the root namespace. + * Options. + * @type {Object.} * @expose */ - BuilderPrototype.reset = function() { - this.ptr = this.ns; - }; + 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 {boolean} request_stream Whether requests are streamed + * @param {boolean} response_stream Whether responses are streamed + * @param {Object.=} options Options + * @constructor + * @extends ProtoBuf.Reflect.Service.Method + */ + var RPCMethod = function(builder, svc, name, request, response, request_stream, response_stream, options) { + Method.call(this, builder, svc, name, options); /** - * 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 + * @override */ - 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 + * Request message name. + * @type {string} * @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 + * Whether responses are streamed + * @type {bool} * @expose */ - BuilderPrototype.create = function(defs) { - if (!defs) - return this; // Nothing to create - if (!ProtoBuf.Util.isArray(defs)) - defs = [defs]; - if (defs.length == 0) + this.responseStream = response_stream; + + /** + * 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; + + // ----- Definition tests ----- + + /** + * Tests if a definition most likely describes a message. + * @param {!Object} def + * @returns {boolean} + * @expose + */ + Builder.isMessage = function(def) { + // Messages require a string name + if (typeof def["name"] !== 'string') + return false; + // Messages do not contain values (enum) or rpc methods (service) + if (typeof def["values"] !== 'undefined' || typeof def["rpc"] !== 'undefined') + return false; + return true; + }; + + /** + * Tests if a definition most likely describes a message field. + * @param {!Object} def + * @returns {boolean} + * @expose + */ + Builder.isMessageField = function(def) { + // Message fields require a string rule, name and type and an id + if (typeof def["rule"] !== 'string' || typeof def["name"] !== 'string' || typeof def["type"] !== 'string' || typeof def["id"] === 'undefined') + return false; + return true; + }; + + /** + * Tests if a definition most likely describes an enum. + * @param {!Object} def + * @returns {boolean} + * @expose + */ + Builder.isEnum = function(def) { + // Enums require a string name + if (typeof def["name"] !== 'string') + return false; + // Enums require at least one value + if (typeof def["values"] === 'undefined' || !Array.isArray(def["values"]) || def["values"].length === 0) + return false; + return true; + }; + + /** + * Tests if a definition most likely describes a service. + * @param {!Object} def + * @returns {boolean} + * @expose + */ + Builder.isService = function(def) { + // Services require a string name and an rpc object + if (typeof def["name"] !== 'string' || typeof def["rpc"] !== 'object' || !def["rpc"]) + return false; + return true; + }; + + /** + * Tests if a definition most likely describes an extended message + * @param {!Object} def + * @returns {boolean} + * @expose + */ + Builder.isExtend = function(def) { + // Extends rquire a string ref + if (typeof def["ref"] !== 'string') + return false; + return true; + }; + + // ----- Building ----- + + /** + * Resets the pointer to the root namespace. + * @returns {!ProtoBuf.Builder} this + * @expose + */ + BuilderPrototype.reset = function() { + this.ptr = this.ns; + return this; + }; + + /** + * Defines a namespace on top of the current pointer position and places the pointer on it. + * @param {string} namespace + * @return {!ProtoBuf.Builder} this + * @expose + */ + BuilderPrototype.define = function(namespace) { + if (typeof namespace !== 'string' || !Lang.TYPEREF.test(namespace)) + throw Error("illegal namespace: "+namespace); + namespace.split(".").forEach(function(part) { + var ns = this.ptr.getChild(part); + if (ns === null) // Keep existing + this.ptr.addChild(ns = new Reflect.Namespace(this, this.ptr, part)); + this.ptr = ns; + }, this); + return this; + }; + + /** + * Creates the specified definitions at the current pointer position. + * @param {!Array.} defs Messages, enums or services to create + * @returns {!ProtoBuf.Builder} this + * @throws {Error} If a message definition is invalid + * @expose + */ + BuilderPrototype.create = function(defs) { + if (!defs) + return this; // Nothing to create + if (!Array.isArray(defs)) + defs = [defs]; + else { + if (defs.length === 0) return this; + defs = defs.slice(); + } - // 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; + // It's quite hard to keep track of scopes and memory here, so let's do this iteratively. + var stack = [defs]; + while (stack.length > 0) { + defs = stack.pop(); + + if (!Array.isArray(defs)) // Stack always contains entire namespaces + throw Error("not a valid namespace: "+JSON.stringify(defs)); + + while (defs.length > 0) { + var def = defs.shift(); // Namespaces always contain an array of messages, enums and services + + if (Builder.isMessage(def)) { + var obj = new Reflect.Message(this, this.ptr, def["name"], def["options"], def["isGroup"], def["syntax"]); + + // Create OneOfs + var oneofs = {}; + if (def["oneofs"]) + Object.keys(def["oneofs"]).forEach(function(name) { + obj.addChild(oneofs[name] = new Reflect.Message.OneOf(this, obj, name)); + }, this); + + // Create fields + if (def["fields"]) + def["fields"].forEach(function(fld) { + if (obj.getChild(fld["id"]|0) !== null) + throw Error("duplicate or invalid field id in "+obj.name+": "+fld['id']); + if (fld["options"] && typeof fld["options"] !== 'object') + throw Error("illegal field options in "+obj.name+"#"+fld["name"]); + var oneof = null; + if (typeof fld["oneof"] === 'string' && !(oneof = oneofs[fld["oneof"]])) + throw Error("illegal oneof in "+obj.name+"#"+fld["name"]+": "+fld["oneof"]); + fld = new Reflect.Message.Field(this, obj, fld["rule"], fld["keytype"], fld["type"], fld["name"], fld["id"], fld["options"], oneof, def["syntax"]); + if (oneof) + oneof.fields.push(fld); + obj.addChild(fld); + }, this); + + // Push children to stack + var subObj = []; + if (def["enums"]) + def["enums"].forEach(function(enm) { + subObj.push(enm); + }); + if (def["messages"]) + def["messages"].forEach(function(msg) { + subObj.push(msg); + }); + if (def["services"]) + def["services"].forEach(function(svc) { + subObj.push(svc); + }); + + // Set extension range + if (def["extensions"]) { + obj.extensions = def["extensions"]; + if (obj.extensions[0] < ProtoBuf.ID_MIN) + obj.extensions[0] = ProtoBuf.ID_MIN; + if (obj.extensions[1] > ProtoBuf.ID_MAX) + obj.extensions[1] = ProtoBuf.ID_MAX; } - // 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; + // Create on top of current namespace + this.ptr.addChild(obj); + 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; + + } else if (Builder.isEnum(def)) { + + obj = new Reflect.Enum(this, this.ptr, def["name"], def["options"], def["syntax"]); + def["values"].forEach(function(val) { + obj.addChild(new Reflect.Enum.Value(this, obj, val["name"], val["id"])); + }, this); + this.ptr.addChild(obj); + + } else if (Builder.isService(def)) { + + obj = new Reflect.Service(this, this.ptr, def["name"], def["options"]); + Object.keys(def["rpc"]).forEach(function(name) { + var mtd = def["rpc"][name]; + obj.addChild(new Reflect.Service.RPCMethod(this, obj, name, mtd["request"], mtd["response"], !!mtd["request_stream"], !!mtd["response_stream"], mtd["options"])); + }, this); + this.ptr.addChild(obj); + + } else if (Builder.isExtend(def)) { + + obj = this.ptr.resolve(def["ref"], true); + if (obj) { + def["fields"].forEach(function(fld) { + if (obj.getChild(fld['id']|0) !== null) + throw Error("duplicate extended field id in "+obj.name+": "+fld['id']); + if (fld['id'] < obj.extensions[0] || fld['id'] > obj.extensions[1]) + throw Error("illegal extended field id in "+obj.name+": "+fld['id']+" ("+obj.extensions.join(' to ')+" expected)"); + // Convert extension field names to camel case notation if the override is set + var name = fld["name"]; + if (this.options['convertFieldsToCamelCase']) + name = ProtoBuf.Util.toCamelCase(name); + // see #161: Extensions use their fully qualified name as their runtime key and... + var field = new Reflect.Message.ExtensionField(this, obj, fld["rule"], fld["type"], this.ptr.fqn()+'.'+name, fld["id"], fld["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, fld["name"], field); + field.extension = ext; + this.ptr.addChild(ext); + obj.addChild(field); + }, this); + + } 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; + obj = null; } - 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 + // Break goes here + defs = null; + this.ptr = this.ptr.parent; // Namespace done, continue at parent + } + this.resolved = false; // Require re-resolve + this.result = null; // Require re-build + return this; + }; + + /** + * Propagates syntax to all children. + * @param {!Object} parent + * @inner + */ + function propagateSyntax(parent) { + if (parent['messages']) { + parent['messages'].forEach(function(child) { + child["syntax"] = parent["syntax"]; + propagateSyntax(child); + }); + } + if (parent['enums']) { + parent['enums'].forEach(function(child) { + child["syntax"] = parent["syntax"]; + }); + } + } + + /** + * Imports another definition into this builder. + * @param {Object.} json Parsed import + * @param {(string|{root: string, file: string})=} filename Imported file name + * @returns {!ProtoBuf.Builder} this + * @throws {Error} If the definition or file cannot be imported + * @expose + */ + BuilderPrototype["import"] = function(json, filename) { + var delim = '/'; + + // Make sure to skip duplicate imports + + if (typeof filename === 'string') { + + if (ProtoBuf.Util.IS_NODE) + filename = require("path")['resolve'](filename); + if (this.files[filename] === true) + return this.reset(); + this.files[filename] = true; + + } else if (typeof filename === 'object') { // Object with root, file. + + var root = filename.root; + if (ProtoBuf.Util.IS_NODE) + root = require("path")['resolve'](root); + if (root.indexOf("\\") >= 0 || filename.file.indexOf("\\") >= 0) + delim = '\\'; + var fname = root + delim + filename.file; + if (this.files[fname] === true) + return this.reset(); + this.files[fname] = true; + } + + // Import imports + + if (json['imports'] && json['imports'].length > 0) { + var importRoot, + 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; - 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) { // Unix + importRoot = filename.replace(/\/[^\/]*$/, ""); + if (/* /file.proto */ importRoot === "") + importRoot = "/"; + } else if (filename.indexOf("\\") >= 0) { // Windows + importRoot = filename.replace(/\\[^\\]*$/, ""); + delim = '\\'; + } else + importRoot = "."; } - if (resetRoot) // Reset import root override when all imports are done - this.importRoot = null; - } - if (json['messages']) { - if (json['package']) - this.define(json['package'], json["options"]); - this.create(json['messages']); - this.reset(); - } - if (json['enums']) { - if (json['package']) - this.define(json['package'], json["options"]); - this.create(json['enums']); - this.reset(); - } - if (json['services']) { - if (json['package']) - this.define(json['package'], json["options"]); - this.create(json['services']); - this.reset(); - } - if (json['extends']) { - if (json['package']) - this.define(json['package'], json["options"]); - this.create(json['extends']); - this.reset(); - } - return this; - }; - /** - * Tests if a definition is a valid service definition. - * @param {Object} def Definition - * @return {boolean} true if valid, else false - * @expose - */ - Builder.isValidService = function(def) { - // Services require a string name and an rpc object - return !(typeof def["name"] !== 'string' || !Lang.NAME.test(def["name"]) || typeof def["rpc"] !== 'object'); - }; + } else + importRoot = null; - /** - * Tests if a definition is a valid extension. - * @param {Object} def Definition - * @returns {boolean} true if valid, else false - * @expose - */ - Builder.isValidExtend = function(def) { - if (typeof def["ref"] !== 'string' || !Lang.TYPEREF.test(def["ref"])) - return false; - var i; - if (typeof def["fields"] !== 'undefined') { - if (!ProtoBuf.Util.isArray(def["fields"])) - return false; - var ids = [], id; // IDs must be unique (does not yet test for the extended message's ids) - for (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)=} path Specifies what to return. If omitted, the entire namespace will be returned. + * @returns {!ProtoBuf.Builder.Message|!Object.} + * @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; + var part = typeof path === 'string' ? path.split(".") : path, + ptr = this.result; // Build namespace pointer (no hasChild etc.) + for (var i=0; i=} contents Initial contents + * @constructor + */ + var Map = function(field, contents) { + if (!field.map) + throw Error("field is not a map"); /** - * Similar to {@link ProtoBuf.Builder#build}, but looks up the internal reflection descriptor. - * @param {string=} path Specifies what to return. If omitted, the entire namespace wiil be returned. - * @return {ProtoBuf.Reflect.T} Reflection descriptor or `null` if not found + * The field corresponding to this map. + * @type {!ProtoBuf.Reflect.Field} */ - BuilderPrototype.lookup = function(path) { - return path ? this.ns.resolve(path) : this.ns; - }; + this.field = field; /** - * Returns a string representation of this object. - * @return {string} String representation as of "Builder" - * @expose + * Element instance corresponding to key type. + * @type {!ProtoBuf.Reflect.Element} */ - BuilderPrototype.toString = function() { - return "Builder"; - }; + this.keyElem = new Reflect.Element(field.keyType, null, true, field.syntax); - // Pseudo types documented in Reflect.js. - // Exist for the sole purpose of being able to "... instanceof ProtoBuf.Builder.Message" etc. - Builder.Message = function() {}; - Builder.Service = function() {}; + /** + * Element instance corresponding to value type. + * @type {!ProtoBuf.Reflect.Element} + */ + this.valueElem = new Reflect.Element(field.type, field.resolvedType, false, field.syntax); - return Builder; + /** + * Internal map: stores mapping of (string form of key) -> (key, value) + * pair. + * + * We provide map semantics for arbitrary key types, but we build on top + * of an Object, which has only string keys. In order to avoid the need + * to convert a string key back to its native type in many situations, + * we store the native key value alongside the value. Thus, we only need + * a one-way mapping from a key type to its string form that guarantees + * uniqueness and equality (i.e., str(K1) === str(K2) if and only if K1 + * === K2). + * + * @type {!Object} + */ + this.map = {}; - })(ProtoBuf, ProtoBuf.Lang, ProtoBuf.Reflect); + /** + * Returns the number of elements in the map. + */ + Object.defineProperty(this, "size", { + get: function() { return Object.keys(this.map).length; } + }); + // Fill initial contents from a raw object. + if (contents) { + var keys = Object.keys(contents); + for (var i = 0; i < keys.length; i++) { + var key = this.keyElem.valueFromString(keys[i]); + var val = this.valueElem.verifyValue(contents[keys[i]]); + this.map[this.keyElem.valueToString(key)] = + { key: key, value: val }; + } + } + }; + + var MapPrototype = Map.prototype; /** - * Loads a .proto string and returns the Builder. - * @param {string} proto .proto file contents - * @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 + * Helper: return an iterator over an array. + * @param {!Array<*>} arr the array + * @returns {!Object} an iterator + * @inner */ - ProtoBuf.loadProto = function(proto, builder, filename) { - if (typeof builder === 'string' || (builder && typeof builder["file"] === 'string' && typeof builder["root"] === 'string')) - filename = builder, - builder = undefined; - return ProtoBuf.loadJson((new ProtoBuf.DotProto.Parser(proto)).parse(), builder, filename); + function arrayIterator(arr) { + var idx = 0; + return { + next: function() { + if (idx < arr.length) + return { done: false, value: arr[idx++] }; + return { done: true }; + } + } + } + + /** + * Clears the map. + */ + MapPrototype.clear = function() { + this.map = {}; }; /** - * Loads a .proto string and returns the Builder. This is an alias of {@link ProtoBuf.loadProto}. - * @function - * @param {string} proto .proto file contents - * @param {(ProtoBuf.Builder|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 + * Deletes a particular key from the map. + * @returns {boolean} Whether any entry with this key was deleted. */ - ProtoBuf.protoFromString = ProtoBuf.loadProto; // Legacy - - /** - * Loads a .proto file and returns the Builder. - * @param {string|{root: string, file: string}} filename Path to proto 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.loadProtoFile = 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.loadProto(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.loadProto(contents, builder, filename); + MapPrototype["delete"] = function(key) { + var keyValue = this.keyElem.valueToString(this.keyElem.verifyValue(key)); + var hadKey = keyValue in this.map; + delete this.map[keyValue]; + return hadKey; }; /** - * Loads a .proto file and returns the Builder. This is an alias of {@link ProtoBuf.loadProtoFile}. - * @function - * @param {string|{root: string, file: string}} filename Path to proto 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 + * Returns an iterator over [key, value] pairs in the map. + * @returns {Object} The iterator */ - ProtoBuf.protoFromFile = ProtoBuf.loadProtoFile; // Legacy - - - /** - * Constructs a new empty Builder. - * @param {Object.=} 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); + MapPrototype.entries = function() { + var entries = []; + var strKeys = Object.keys(this.map); + for (var i = 0, entry; i < strKeys.length; i++) + entries.push([(entry=this.map[strKeys[i]]).key, entry.value]); + return arrayIterator(entries); }; /** - * 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 + * Returns an iterator over keys in the map. + * @returns {Object} The iterator */ - 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; + MapPrototype.keys = function() { + var keys = []; + var strKeys = Object.keys(this.map); + for (var i = 0; i < strKeys.length; i++) + keys.push(this.map[strKeys[i]].key); + return arrayIterator(keys); }; /** - * 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 + * Returns an iterator over values in the map. + * @returns {!Object} The iterator */ - 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); + MapPrototype.values = function() { + var values = []; + var strKeys = Object.keys(this.map); + for (var i = 0; i < strKeys.length; i++) + values.push(this.map[strKeys[i]].value); + return arrayIterator(values); }; - return ProtoBuf; - } + /** + * Iterates over entries in the map, calling a function on each. + * @param {function(this:*, *, *, *)} cb The callback to invoke with value, key, and map arguments. + * @param {Object=} thisArg The `this` value for the callback + */ + MapPrototype.forEach = function(cb, thisArg) { + var strKeys = Object.keys(this.map); + for (var i = 0, entry; i < strKeys.length; i++) + cb.call(thisArg, (entry=this.map[strKeys[i]]).value, entry.key, this); + }; - /* 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"]); + /** + * Sets a key in the map to the given value. + * @param {*} key The key + * @param {*} value The value + * @returns {!ProtoBuf.Map} The map instance + */ + MapPrototype.set = function(key, value) { + var keyValue = this.keyElem.verifyValue(key); + var valValue = this.valueElem.verifyValue(value); + this.map[this.keyElem.valueToString(keyValue)] = + { key: keyValue, value: valValue }; + return this; + }; -})(this); + /** + * Gets the value corresponding to a key in the map. + * @param {*} key The key + * @returns {*|undefined} The value, or `undefined` if key not present + */ + MapPrototype.get = function(key) { + var keyValue = this.keyElem.valueToString(this.keyElem.verifyValue(key)); + if (!(keyValue in this.map)) + return undefined; + return this.map[keyValue].value; + }; + + /** + * Determines whether the given key is present in the map. + * @param {*} key The key + * @returns {boolean} `true` if the key is present + */ + MapPrototype.has = function(key) { + var keyValue = this.keyElem.valueToString(this.keyElem.verifyValue(key)); + return (keyValue in this.map); + }; + + return Map; + })(ProtoBuf, ProtoBuf.Reflect); + + + /** + * Loads a .proto string and returns the Builder. + * @param {string} proto .proto file contents + * @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.loadProto = function(proto, builder, filename) { + if (typeof builder === 'string' || (builder && typeof builder["file"] === 'string' && typeof builder["root"] === 'string')) + filename = builder, + builder = undefined; + return ProtoBuf.loadJson(ProtoBuf.DotProto.Parser.parse(proto), builder, filename); + }; + + /** + * Loads a .proto string and returns the Builder. This is an alias of {@link ProtoBuf.loadProto}. + * @function + * @param {string} proto .proto file contents + * @param {(ProtoBuf.Builder|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.protoFromString = ProtoBuf.loadProto; // Legacy + + /** + * Loads a .proto file and returns the Builder. + * @param {string|{root: string, file: string}} filename Path to proto 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.loadProtoFile = 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.loadProto(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.loadProto(contents, builder, filename); + }; + + /** + * Loads a .proto file and returns the Builder. This is an alias of {@link ProtoBuf.loadProtoFile}. + * @function + * @param {string|{root: string, file: string}} filename Path to proto 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.protoFromFile = ProtoBuf.loadProtoFile; // Legacy + + + /** + * Constructs a new empty Builder. + * @param {Object.=} 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; +}); diff --git a/js/modules/refresh_sender_certificate.js b/js/modules/refresh_sender_certificate.js index 0cfb22147..70d4cd586 100644 --- a/js/modules/refresh_sender_certificate.js +++ b/js/modules/refresh_sender_certificate.js @@ -35,7 +35,7 @@ function initialize({ events, storage, navigator, logger }) { const now = Date.now(); const certificate = storage.get('senderCertificate'); if (!certificate) { - setTimeoutForNextRun(now); + setTimeoutForNextRun(scheduledTime || now); return; } @@ -83,7 +83,7 @@ function initialize({ events, storage, navigator, logger }) { scheduleNextRotation(); } catch (error) { logger.error( - 'refreshSenderCertificate: Get failed. Trying again in two minutes...', + 'refreshSenderCertificate: Get failed. Trying again in five minutes...', error && error.stack ? error.stack : error ); diff --git a/js/modules/stickers.js b/js/modules/stickers.js index 50544aecb..f15307ebc 100644 --- a/js/modules/stickers.js +++ b/js/modules/stickers.js @@ -21,7 +21,7 @@ const BLESSED_PACKS = { const { isNumber, pick, reject, groupBy, values } = require('lodash'); const pMap = require('p-map'); -const Queue = require('p-queue'); +const Queue = require('p-queue').default; const qs = require('qs'); const { makeLookup } = require('../../ts/util/makeLookup'); diff --git a/js/rotate_signed_prekey_listener.js b/js/rotate_signed_prekey_listener.js index 7c578085f..76b9ce24e 100644 --- a/js/rotate_signed_prekey_listener.js +++ b/js/rotate_signed_prekey_listener.js @@ -27,9 +27,9 @@ setTimeoutForNextRun(); } catch (error) { window.log.error( - 'rotateSignedPrekey() failed. Trying again in five seconds' + 'rotateSignedPrekey() failed. Trying again in five minutes' ); - setTimeout(setTimeoutForNextRun, 5000); + setTimeout(setTimeoutForNextRun, 5 * 60 * 1000); } } diff --git a/package.json b/package.json index beec2bd2a..395f800a1 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "node-gyp": "5.0.3", "os-locale": "2.1.0", "p-map": "2.1.0", - "p-queue": "5.0.0", + "p-queue": "6.2.1", "pify": "3.0.0", "protobufjs": "6.8.6", "proxy-agent": "3.0.3", @@ -244,7 +244,7 @@ "tslint-microsoft-contrib": "6.2.0", "tslint-react": "3.6.0", "typed-scss-modules": "0.0.11", - "typescript": "3.3.3333", + "typescript": "3.7.4", "webpack": "4.39.2", "webpack-cli": "3.3.7", "webpack-dev-server": "3.8.0" diff --git a/preload.js b/preload.js index 11765cf2f..6f3c5b083 100644 --- a/preload.js +++ b/preload.js @@ -11,9 +11,6 @@ const { remote } = electron; const { app } = remote; const { systemPreferences } = remote.require('electron'); -const browserWindow = remote.getCurrentWindow(); -window.isFocused = () => browserWindow.isFocused(); - // Waiting for clients to implement changes on receive side window.TIMESTAMP_VALIDATION = false; window.PAD_ALL_ATTACHMENTS = false; @@ -267,7 +264,7 @@ window.getGuid = require('uuid/v4'); window.React = require('react'); window.ReactDOM = require('react-dom'); window.moment = require('moment'); -window.PQueue = require('p-queue'); +window.PQueue = require('p-queue').default; const Signal = require('./js/modules/signal'); const i18n = require('./js/modules/i18n'); diff --git a/sticker-creator/components/StickerGrid.tsx b/sticker-creator/components/StickerGrid.tsx index 962bb8270..35802a608 100644 --- a/sticker-creator/components/StickerGrid.tsx +++ b/sticker-creator/components/StickerGrid.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import * as PQueue from 'p-queue'; +import PQueue from 'p-queue'; import { SortableContainer, SortableElement, diff --git a/sticker-creator/preload.js b/sticker-creator/preload.js index 6bfc014e2..9ff76bd96 100644 --- a/sticker-creator/preload.js +++ b/sticker-creator/preload.js @@ -17,7 +17,7 @@ window.PROTO_ROOT = '../../protos'; window.getEnvironment = () => config.environment; window.getVersion = () => config.version; window.getGuid = require('uuid/v4'); -window.PQueue = require('p-queue'); +window.PQueue = require('p-queue').default; window.localeMessages = ipc.sendSync('locale-data'); diff --git a/ts/components/Lightbox.tsx b/ts/components/Lightbox.tsx index feb75e7eb..69c107cc6 100644 --- a/ts/components/Lightbox.tsx +++ b/ts/components/Lightbox.tsx @@ -188,10 +188,6 @@ export class Lightbox extends React.Component { public readonly focusRef = React.createRef(); public previousFocus: any; - public state = { - videoTime: undefined, - }; - public componentDidMount() { this.previousFocus = document.activeElement; @@ -299,7 +295,7 @@ export class Lightbox extends React.Component { ) : null} - {isViewOnce && is.number(videoTime) ? ( + {isViewOnce && videoTime && is.number(videoTime) ? (
{formatDuration(videoTime)}
diff --git a/ts/components/emoji/EmojiButton.tsx b/ts/components/emoji/EmojiButton.tsx index 693bda164..d8df446f2 100644 --- a/ts/components/emoji/EmojiButton.tsx +++ b/ts/components/emoji/EmojiButton.tsx @@ -10,7 +10,7 @@ import { } from './EmojiPicker'; import { LocalizerType } from '../../types/Util'; -export type EmojiPickDataType = EmojiPickDataType; +export { EmojiPickDataType }; export type OwnProps = { readonly i18n: LocalizerType; diff --git a/ts/components/emoji/EmojiPicker.tsx b/ts/components/emoji/EmojiPicker.tsx index 754b99345..a4555d175 100644 --- a/ts/components/emoji/EmojiPicker.tsx +++ b/ts/components/emoji/EmojiPicker.tsx @@ -91,7 +91,7 @@ export const EmojiPicker = React.memo( const debounceSearchChange = React.useMemo( () => - debounce(query => { + debounce((query: string) => { setSearchText(query); setScrollToRow(0); }, 200), diff --git a/ts/shims/textsecure.ts b/ts/shims/textsecure.ts index 8ae9eac0a..cc0bbf581 100644 --- a/ts/shims/textsecure.ts +++ b/ts/shims/textsecure.ts @@ -37,12 +37,15 @@ interface ShimmedWindow extends Window { ConversationController: ConversationControllerType; } +const unknownWindow = window as unknown; +const shimmedWindow = unknownWindow as ShimmedWindow; + export function sendStickerPackSync( packId: string, packKey: string, installed: boolean ) { - const { ConversationController, textsecure, log } = window as ShimmedWindow; + const { ConversationController, textsecure, log } = shimmedWindow; const ourNumber = textsecure.storage.user.getNumber(); const { wrap, sendOptions } = ConversationController.prepareForSend( ourNumber, diff --git a/yarn.lock b/yarn.lock index 9f71d638d..0c9ea41ad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6595,6 +6595,11 @@ eventemitter3@^3.1.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA== +eventemitter3@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb" + integrity sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg== + events@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" @@ -11670,12 +11675,13 @@ p-map@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" -p-queue@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-5.0.0.tgz#80f1741d5e78a6fa72fce889406481baa5617a3c" - integrity sha512-6QfeouDf236N+MAxHch0CVIy8o/KBnmhttKjxZoOkUlzqU+u9rZgEyXH3OdckhTgawbqf5rpzmyR+07+Lv0+zg== +p-queue@6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.2.1.tgz#809a832046451b2240a0a8e48b4fa18192b22b64" + integrity sha512-wV8yC/rkuWpgu9LGKJIb48OynYSrE6lVl2Bx6r8WjbyVKrFAzzQ/QevAvwnDjlD+mLt8xy0LTDOU1freOvMTCg== dependencies: - eventemitter3 "^3.1.0" + eventemitter3 "^4.0.0" + p-timeout "^3.1.0" p-retry@^3.0.1: version "3.0.1" @@ -11690,6 +11696,13 @@ p-timeout@^2.0.1: dependencies: p-finally "^1.0.0" +p-timeout@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" + p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" @@ -16151,10 +16164,10 @@ typeface-inter@3.10.0: resolved "https://registry.yarnpkg.com/typeface-inter/-/typeface-inter-3.10.0.tgz#04a55d62e2dc3f60db3afab5d8a547e067692bc6" integrity sha512-WuXE+TaJLB8pdMuvIVY3LfT5UQqndR8+Js0xfhNpdXlsEx0Abwd1bzg4w4YWl2eoOmmLYrRpx6UJJ7a7/q6wZQ== -typescript@3.3.3333: - version "3.3.3333" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.3333.tgz#171b2c5af66c59e9431199117a3bcadc66fdcfd6" - integrity sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw== +typescript@3.7.4: + version "3.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.4.tgz#1743a5ec5fef6a1fa9f3e4708e33c81c73876c19" + integrity sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw== ua-parser-js@^0.7.18: version "0.7.19"