UNPKG

rocket.chat.mqtt

Version:

It's a MQTT Server, using redis to scale horizontally.

788 lines (720 loc) 22.4 kB
module.exports = (function () { "use strict"; var Meteor = { _noYieldsAllowed:function nope(f) { return f(); }}; var EJSON, EJSONTest, i, Base64, root = {}; var _ = require("underscore"); // Base 64 encoding var BASE_64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var BASE_64_VALS = {}; for (var i = 0; i < BASE_64_CHARS.length; i++) { BASE_64_VALS[BASE_64_CHARS.charAt(i)] = i; }; Base64 = {}; Base64.encode = function (array) { if (typeof array === "string") { var str = array; array = Base64.newBinary(str.length); for (var i = 0; i < str.length; i++) { var ch = str.charCodeAt(i); if (ch > 0xFF) { throw new Error( "Not ascii. Base64.encode can only take ascii strings."); } array[i] = ch; } } var answer = []; var a = null; var b = null; var c = null; var d = null; for (var i = 0; i < array.length; i++) { switch (i % 3) { case 0: a = (array[i] >> 2) & 0x3F; b = (array[i] & 0x03) << 4; break; case 1: b = b | (array[i] >> 4) & 0xF; c = (array[i] & 0xF) << 2; break; case 2: c = c | (array[i] >> 6) & 0x03; d = array[i] & 0x3F; answer.push(getChar(a)); answer.push(getChar(b)); answer.push(getChar(c)); answer.push(getChar(d)); a = null; b = null; c = null; d = null; break; } } if (a != null) { answer.push(getChar(a)); answer.push(getChar(b)); if (c == null) answer.push('='); else answer.push(getChar(c)); if (d == null) answer.push('='); } return answer.join(""); }; var getChar = function (val) { return BASE_64_CHARS.charAt(val); }; var getVal = function (ch) { if (ch === '=') { return -1; } return BASE_64_VALS[ch]; }; // XXX This is a weird place for this to live, but it's used both by // this package and 'ejson', and we can't put it in 'ejson' without // introducing a circular dependency. It should probably be in its own // package or as a helper in a package that both 'base64' and 'ejson' // use. Base64.newBinary = function (len) { if (typeof Uint8Array === 'undefined' || typeof ArrayBuffer === 'undefined') { var ret = []; for (var i = 0; i < len; i++) { ret.push(0); } ret.$Uint8ArrayPolyfill = true; return ret; } return new Uint8Array(new ArrayBuffer(len)); }; Base64.decode = function (str) { var len = Math.floor((str.length*3)/4); if (str.charAt(str.length - 1) == '=') { len--; if (str.charAt(str.length - 2) == '=') len--; } var arr = Base64.newBinary(len); var one = null; var two = null; var three = null; var j = 0; for (var i = 0; i < str.length; i++) { var c = str.charAt(i); var v = getVal(c); switch (i % 4) { case 0: if (v < 0) throw new Error('invalid base64 string'); one = v << 2; break; case 1: if (v < 0) throw new Error('invalid base64 string'); one = one | (v >> 4); arr[j++] = one; two = (v & 0x0F) << 4; break; case 2: if (v >= 0) { two = two | (v >> 2); arr[j++] = two; three = (v & 0x03) << 6; } break; case 3: if (v >= 0) { arr[j++] = three | v; } break; } } return arr; }; /** * @namespace * @summary Namespace for EJSON functions */ EJSON = {}; EJSONTest = {}; // Custom type interface definition /** * @class CustomType * @instanceName customType * @memberOf EJSON * @summary The interface that a class must satisfy to be able to become an * EJSON custom type via EJSON.addType. */ /** * @function typeName * @memberOf EJSON.CustomType * @summary Return the tag used to identify this type. This must match the tag used to register this type with [`EJSON.addType`](#ejson_add_type). * @locus Anywhere * @instance */ /** * @function toJSONValue * @memberOf EJSON.CustomType * @summary Serialize this instance into a JSON-compatible value. * @locus Anywhere * @instance */ /** * @function clone * @memberOf EJSON.CustomType * @summary Return a value `r` such that `this.equals(r)` is true, and modifications to `r` do not affect `this` and vice versa. * @locus Anywhere * @instance */ /** * @function equals * @memberOf EJSON.CustomType * @summary Return `true` if `other` has a value equal to `this`; `false` otherwise. * @locus Anywhere * @param {Object} other Another object to compare this to. * @instance */ var customTypes = {}; // Add a custom type, using a method of your choice to get to and // from a basic JSON-able representation. The factory argument // is a function of JSON-able --> your object // The type you add must have: // - A toJSONValue() method, so that Meteor can serialize it // - a typeName() method, to show how to look it up in our type table. // It is okay if these methods are monkey-patched on. // EJSON.clone will use toJSONValue and the given factory to produce // a clone, but you may specify a method clone() that will be // used instead. // Similarly, EJSON.equals will use toJSONValue to make comparisons, // but you may provide a method equals() instead. /** * @summary Add a custom datatype to EJSON. * @locus Anywhere * @param {String} name A tag for your custom type; must be unique among custom data types defined in your project, and must match the result of your type's `typeName` method. * @param {Function} factory A function that deserializes a JSON-compatible value into an instance of your type. This should match the serialization performed by your type's `toJSONValue` method. */ EJSON.addType = function (name, factory) { if (_.has(customTypes, name)) throw new Error("Type " + name + " already present"); customTypes[name] = factory; }; var isInfOrNan = function (obj) { return _.isNaN(obj) || obj === Infinity || obj === -Infinity; }; var builtinConverters = [ { // Date matchJSONValue: function (obj) { return _.has(obj, '$date') && _.size(obj) === 1; }, matchObject: function (obj) { return obj instanceof Date; }, toJSONValue: function (obj) { return {$date: obj.getTime()}; }, fromJSONValue: function (obj) { return new Date(obj.$date); } }, { // NaN, Inf, -Inf. (These are the only objects with typeof !== 'object' // which we match.) matchJSONValue: function (obj) { return _.has(obj, '$InfNaN') && _.size(obj) === 1; }, matchObject: isInfOrNan, toJSONValue: function (obj) { var sign; if (_.isNaN(obj)) sign = 0; else if (obj === Infinity) sign = 1; else sign = -1; return {$InfNaN: sign}; }, fromJSONValue: function (obj) { return obj.$InfNaN/0; } }, { // Binary matchJSONValue: function (obj) { return _.has(obj, '$binary') && _.size(obj) === 1; }, matchObject: function (obj) { return typeof Uint8Array !== 'undefined' && obj instanceof Uint8Array || (obj && _.has(obj, '$Uint8ArrayPolyfill')); }, toJSONValue: function (obj) { return {$binary: Base64.encode(obj)}; }, fromJSONValue: function (obj) { return Base64.decode(obj.$binary); } }, { // Escaping one level matchJSONValue: function (obj) { return _.has(obj, '$escape') && _.size(obj) === 1; }, matchObject: function (obj) { if (_.isEmpty(obj) || _.size(obj) > 2) { return false; } return _.any(builtinConverters, function (converter) { return converter.matchJSONValue(obj); }); }, toJSONValue: function (obj) { var newObj = {}; _.each(obj, function (value, key) { newObj[key] = EJSON.toJSONValue(value); }); return {$escape: newObj}; }, fromJSONValue: function (obj) { var newObj = {}; _.each(obj.$escape, function (value, key) { newObj[key] = EJSON.fromJSONValue(value); }); return newObj; } }, { // Custom matchJSONValue: function (obj) { return _.has(obj, '$type') && _.has(obj, '$value') && _.size(obj) === 2; }, matchObject: function (obj) { return EJSON._isCustomType(obj); }, toJSONValue: function (obj) { var jsonValue = Meteor._noYieldsAllowed(function () { return obj.toJSONValue(); }); return {$type: obj.typeName(), $value: jsonValue}; }, fromJSONValue: function (obj) { var typeName = obj.$type; if (!_.has(customTypes, typeName)) throw new Error("Custom EJSON type " + typeName + " is not defined"); var converter = customTypes[typeName]; return Meteor._noYieldsAllowed(function () { return converter(obj.$value); }); } } ]; EJSON._isCustomType = function (obj) { return obj && typeof obj.toJSONValue === 'function' && typeof obj.typeName === 'function' && _.has(customTypes, obj.typeName()); }; EJSON._getTypes = function () { return customTypes; }; EJSON._getConverters = function () { return builtinConverters; }; // for both arrays and objects, in-place modification. var adjustTypesToJSONValue = EJSON._adjustTypesToJSONValue = function (obj) { // Is it an atom that we need to adjust? if (obj === null) return null; var maybeChanged = toJSONValueHelper(obj); if (maybeChanged !== undefined) return maybeChanged; // Other atoms are unchanged. if (typeof obj !== 'object') return obj; // Iterate over array or object structure. _.each(obj, function (value, key) { if (typeof value !== 'object' && value !== undefined && !isInfOrNan(value)) return; // continue var changed = toJSONValueHelper(value); if (changed) { obj[key] = changed; return; // on to the next key } // if we get here, value is an object but not adjustable // at this level. recurse. adjustTypesToJSONValue(value); }); return obj; }; // Either return the JSON-compatible version of the argument, or undefined (if // the item isn't itself replaceable, but maybe some fields in it are) var toJSONValueHelper = function (item) { for (var i = 0; i < builtinConverters.length; i++) { var converter = builtinConverters[i]; if (converter.matchObject(item)) { return converter.toJSONValue(item); } } return undefined; }; /** * @summary Serialize an EJSON-compatible value into its plain JSON representation. * @locus Anywhere * @param {EJSON} val A value to serialize to plain JSON. */ EJSON.toJSONValue = function (item) { var changed = toJSONValueHelper(item); if (changed !== undefined) return changed; if (typeof item === 'object') { item = EJSON.clone(item); adjustTypesToJSONValue(item); } return item; }; // for both arrays and objects. Tries its best to just // use the object you hand it, but may return something // different if the object you hand it itself needs changing. // var adjustTypesFromJSONValue = EJSON._adjustTypesFromJSONValue = function (obj) { if (obj === null) return null; var maybeChanged = fromJSONValueHelper(obj); if (maybeChanged !== obj) return maybeChanged; // Other atoms are unchanged. if (typeof obj !== 'object') return obj; _.each(obj, function (value, key) { if (typeof value === 'object') { var changed = fromJSONValueHelper(value); if (value !== changed) { obj[key] = changed; return; } // if we get here, value is an object but not adjustable // at this level. recurse. adjustTypesFromJSONValue(value); } }); return obj; }; // Either return the argument changed to have the non-json // rep of itself (the Object version) or the argument itself. // DOES NOT RECURSE. For actually getting the fully-changed value, use // EJSON.fromJSONValue var fromJSONValueHelper = function (value) { if (typeof value === 'object' && value !== null) { if (_.size(value) <= 2 && _.all(value, function (v, k) { return typeof k === 'string' && k.substr(0, 1) === '$'; })) { for (var i = 0; i < builtinConverters.length; i++) { var converter = builtinConverters[i]; if (converter.matchJSONValue(value)) { return converter.fromJSONValue(value); } } } } return value; }; /** * @summary Deserialize an EJSON value from its plain JSON representation. * @locus Anywhere * @param {JSONCompatible} val A value to deserialize into EJSON. */ EJSON.fromJSONValue = function (item) { var changed = fromJSONValueHelper(item); if (changed === item && typeof item === 'object') { item = EJSON.clone(item); adjustTypesFromJSONValue(item); return item; } else { return changed; } }; /** * @summary Serialize a value to a string. For EJSON values, the serialization fully represents the value. For non-EJSON values, serializes the same way as `JSON.stringify`. * @locus Anywhere * @param {EJSON} val A value to stringify. * @param {Object} [options] * @param {Boolean | Integer | String} options.indent Indents objects and arrays for easy readability. When `true`, indents by 2 spaces; when an integer, indents by that number of spaces; and when a string, uses the string as the indentation pattern. * @param {Boolean} options.canonical When `true`, stringifies keys in an object in sorted order. */ EJSON.stringify = function (item, options) { var json = EJSON.toJSONValue(item); if (options && (options.canonical || options.indent)) { return EJSON._canonicalStringify(json, options); } else { return JSON.stringify(json); } }; /** * @summary Parse a string into an EJSON value. Throws an error if the string is not valid EJSON. * @locus Anywhere * @param {String} str A string to parse into an EJSON value. */ EJSON.parse = function (item) { if (typeof item !== 'string') throw new Error("EJSON.parse argument should be a string"); return EJSON.fromJSONValue(JSON.parse(item)); }; /** * @summary Returns true if `x` is a buffer of binary data, as returned from [`EJSON.newBinary`](#ejson_new_binary). * @param {Object} x The variable to check. * @locus Anywhere */ EJSON.isBinary = function (obj) { return !!((typeof Uint8Array !== 'undefined' && obj instanceof Uint8Array) || (obj && obj.$Uint8ArrayPolyfill)); }; /** * @summary Return true if `a` and `b` are equal to each other. Return false otherwise. Uses the `equals` method on `a` if present, otherwise performs a deep comparison. * @locus Anywhere * @param {EJSON} a * @param {EJSON} b * @param {Object} [options] * @param {Boolean} options.keyOrderSensitive Compare in key sensitive order, if supported by the JavaScript implementation. For example, `{a: 1, b: 2}` is equal to `{b: 2, a: 1}` only when `keyOrderSensitive` is `false`. The default is `false`. */ EJSON.equals = function (a, b, options) { var i; var keyOrderSensitive = !!(options && options.keyOrderSensitive); if (a === b) return true; if (_.isNaN(a) && _.isNaN(b)) return true; // This differs from the IEEE spec for NaN equality, b/c we don't want // anything ever with a NaN to be poisoned from becoming equal to anything. if (!a || !b) // if either one is falsy, they'd have to be === to be equal return false; if (!(typeof a === 'object' && typeof b === 'object')) return false; if (a instanceof Date && b instanceof Date) return a.valueOf() === b.valueOf(); if (EJSON.isBinary(a) && EJSON.isBinary(b)) { if (a.length !== b.length) return false; for (i = 0; i < a.length; i++) { if (a[i] !== b[i]) return false; } return true; } if (typeof (a.equals) === 'function') return a.equals(b, options); if (typeof (b.equals) === 'function') return b.equals(a, options); if (a instanceof Array) { if (!(b instanceof Array)) return false; if (a.length !== b.length) return false; for (i = 0; i < a.length; i++) { if (!EJSON.equals(a[i], b[i], options)) return false; } return true; } // fallback for custom types that don't implement their own equals switch (EJSON._isCustomType(a) + EJSON._isCustomType(b)) { case 1: return false; case 2: return EJSON.equals(EJSON.toJSONValue(a), EJSON.toJSONValue(b)); } // fall back to structural equality of objects var ret; if (keyOrderSensitive) { var bKeys = []; _.each(b, function (val, x) { bKeys.push(x); }); i = 0; ret = _.all(a, function (val, x) { if (i >= bKeys.length) { return false; } if (x !== bKeys[i]) { return false; } if (!EJSON.equals(val, b[bKeys[i]], options)) { return false; } i++; return true; }); return ret && i === bKeys.length; } else { i = 0; ret = _.all(a, function (val, key) { if (!_.has(b, key)) { return false; } if (!EJSON.equals(val, b[key], options)) { return false; } i++; return true; }); return ret && _.size(b) === i; } }; /** * @summary Return a deep copy of `val`. * @locus Anywhere * @param {EJSON} val A value to copy. */ EJSON.clone = function (v) { var ret; if (typeof v !== "object") return v; if (v === null) return null; // null has typeof "object" if (v instanceof Date) return new Date(v.getTime()); // RegExps are not really EJSON elements (eg we don't define a serialization // for them), but they're immutable anyway, so we can support them in clone. if (v instanceof RegExp) return v; if (EJSON.isBinary(v)) { ret = EJSON.newBinary(v.length); for (var i = 0; i < v.length; i++) { ret[i] = v[i]; } return ret; } // XXX: Use something better than underscore's isArray if (_.isArray(v) || _.isArguments(v)) { // For some reason, _.map doesn't work in this context on Opera (weird test // failures). ret = []; for (i = 0; i < v.length; i++) ret[i] = EJSON.clone(v[i]); return ret; } // handle general user-defined typed Objects if they have a clone method if (typeof v.clone === 'function') { return v.clone(); } // handle other custom types if (EJSON._isCustomType(v)) { return EJSON.fromJSONValue(EJSON.clone(EJSON.toJSONValue(v)), true); } // handle other objects ret = {}; _.each(v, function (value, key) { ret[key] = EJSON.clone(value); }); return ret; }; /** * @summary Allocate a new buffer of binary data that EJSON can serialize. * @locus Anywhere * @param {Number} size The number of bytes of binary data to allocate. */ // EJSON.newBinary is the public documented API for this functionality, // but the implementation is in the 'base64' package to avoid // introducing a circular dependency. (If the implementation were here, // then 'base64' would have to use EJSON.newBinary, and 'ejson' would // also have to use 'base64'.) EJSON.newBinary = Base64.newBinary; // Based on json2.js from https://github.com/douglascrockford/JSON-js // // json2.js // 2012-10-08 // // Public Domain. // // NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. function quote(string) { return JSON.stringify(string); } var str = function (key, holder, singleIndent, outerIndent, canonical) { // Produce a string from holder[key]. var i; // The loop counter. var k; // The member key. var v; // The member value. var length; var innerIndent = outerIndent; var partial; var value = holder[key]; // What happens next depends on the value's type. switch (typeof value) { case 'string': return quote(value); case 'number': // JSON numbers must be finite. Encode non-finite numbers as null. return isFinite(value) ? String(value) : 'null'; case 'boolean': return String(value); // If the type is 'object', we might be dealing with an object or an array or // null. case 'object': // Due to a specification blunder in ECMAScript, typeof null is 'object', // so watch out for that case. if (!value) { return 'null'; } // Make an array to hold the partial results of stringifying this object value. innerIndent = outerIndent + singleIndent; partial = []; // Is the value an array? if (_.isArray(value) || _.isArguments(value)) { // The value is an array. Stringify every element. Use null as a placeholder // for non-JSON values. length = value.length; for (i = 0; i < length; i += 1) { partial[i] = str(i, value, singleIndent, innerIndent, canonical) || 'null'; } // Join all of the elements together, separated with commas, and wrap them in // brackets. if (partial.length === 0) { v = '[]'; } else if (innerIndent) { v = '[\n' + innerIndent + partial.join(',\n' + innerIndent) + '\n' + outerIndent + ']'; } else { v = '[' + partial.join(',') + ']'; } return v; } // Iterate through all of the keys in the object. var keys = _.keys(value); if (canonical) keys = keys.sort(); _.each(keys, function (k) { v = str(k, value, singleIndent, innerIndent, canonical); if (v) { partial.push(quote(k) + (innerIndent ? ': ' : ':') + v); } }); // Join all of the member texts together, separated with commas, // and wrap them in braces. if (partial.length === 0) { v = '{}'; } else if (innerIndent) { v = '{\n' + innerIndent + partial.join(',\n' + innerIndent) + '\n' + outerIndent + '}'; } else { v = '{' + partial.join(',') + '}'; } return v; } } // If the JSON object does not yet have a stringify method, give it one. EJSON._canonicalStringify = function (value, options) { // Make a fake root object containing our value under the key of ''. // Return the result of stringifying the value. options = _.extend({ indent: "", canonical: false }, options); if (options.indent === true) { options.indent = " "; } else if (typeof options.indent === 'number') { var newIndent = ""; for (var i = 0; i < options.indent; i++) { newIndent += ' '; } options.indent = newIndent; } return str('', {'': value}, options.indent, "", options.canonical); }; return EJSON; }).call(this);