(function () { /* Imports */ var Meteor = Package.meteor.Meteor; var check = Package.check.check; var Match = Package.check.Match; var Random = Package.random.Random; var EJSON = Package.ejson.EJSON; var _ = Package.underscore._; var Tracker = Package.tracker.Tracker; var Deps = Package.tracker.Deps; var Retry = Package.retry.Retry; /* Package-scope variables */ var DDPCommon; (function(){ /////////////////////////////////////////////////////////////////////////////////////////////////////// // // // packages/ddp-common/namespace.js // // // /////////////////////////////////////////////////////////////////////////////////////////////////////// // /** // 1 * @namespace DDPCommon // 2 * @summary Namespace for DDPCommon-related methods/classes. Shared between // 3 * `ddp-client` and `ddp-server`, where the ddp-client is the implementation // 4 * of a ddp client for both client AND server; and the ddp server is the // 5 * implementation of the livedata server and stream server. Common // 6 * functionality shared between both can be shared under this namespace // 7 */ // 8 DDPCommon = {}; // 9 // 10 /////////////////////////////////////////////////////////////////////////////////////////////////////// }).call(this); (function(){ /////////////////////////////////////////////////////////////////////////////////////////////////////// // // // packages/ddp-common/heartbeat.js // // // /////////////////////////////////////////////////////////////////////////////////////////////////////// // // Heartbeat options: // 1 // heartbeatInterval: interval to send pings, in milliseconds. // 2 // heartbeatTimeout: timeout to close the connection if a reply isn't // 3 // received, in milliseconds. // 4 // sendPing: function to call to send a ping on the connection. // 5 // onTimeout: function to call to close the connection. // 6 // 7 DDPCommon.Heartbeat = function (options) { // 8 var self = this; // 9 // 10 self.heartbeatInterval = options.heartbeatInterval; // 11 self.heartbeatTimeout = options.heartbeatTimeout; // 12 self._sendPing = options.sendPing; // 13 self._onTimeout = options.onTimeout; // 14 self._seenPacket = false; // 15 // 16 self._heartbeatIntervalHandle = null; // 17 self._heartbeatTimeoutHandle = null; // 18 }; // 19 // 20 _.extend(DDPCommon.Heartbeat.prototype, { // 21 stop: function () { // 22 var self = this; // 23 self._clearHeartbeatIntervalTimer(); // 24 self._clearHeartbeatTimeoutTimer(); // 25 }, // 26 // 27 start: function () { // 28 var self = this; // 29 self.stop(); // 30 self._startHeartbeatIntervalTimer(); // 31 }, // 32 // 33 _startHeartbeatIntervalTimer: function () { // 34 var self = this; // 35 self._heartbeatIntervalHandle = Meteor.setInterval( // 36 _.bind(self._heartbeatIntervalFired, self), // 37 self.heartbeatInterval // 38 ); // 39 }, // 40 // 41 _startHeartbeatTimeoutTimer: function () { // 42 var self = this; // 43 self._heartbeatTimeoutHandle = Meteor.setTimeout( // 44 _.bind(self._heartbeatTimeoutFired, self), // 45 self.heartbeatTimeout // 46 ); // 47 }, // 48 // 49 _clearHeartbeatIntervalTimer: function () { // 50 var self = this; // 51 if (self._heartbeatIntervalHandle) { // 52 Meteor.clearInterval(self._heartbeatIntervalHandle); // 53 self._heartbeatIntervalHandle = null; // 54 } // 55 }, // 56 // 57 _clearHeartbeatTimeoutTimer: function () { // 58 var self = this; // 59 if (self._heartbeatTimeoutHandle) { // 60 Meteor.clearTimeout(self._heartbeatTimeoutHandle); // 61 self._heartbeatTimeoutHandle = null; // 62 } // 63 }, // 64 // 65 // The heartbeat interval timer is fired when we should send a ping. // 66 _heartbeatIntervalFired: function () { // 67 var self = this; // 68 // don't send ping if we've seen a packet since we last checked, // 69 // *or* if we have already sent a ping and are awaiting a timeout. // 70 // That shouldn't happen, but it's possible if // 71 // `self.heartbeatInterval` is smaller than // 72 // `self.heartbeatTimeout`. // 73 if (! self._seenPacket && ! self._heartbeatTimeoutHandle) { // 74 self._sendPing(); // 75 // Set up timeout, in case a pong doesn't arrive in time. // 76 self._startHeartbeatTimeoutTimer(); // 77 } // 78 self._seenPacket = false; // 79 }, // 80 // 81 // The heartbeat timeout timer is fired when we sent a ping, but we // 82 // timed out waiting for the pong. // 83 _heartbeatTimeoutFired: function () { // 84 var self = this; // 85 self._heartbeatTimeoutHandle = null; // 86 self._onTimeout(); // 87 }, // 88 // 89 messageReceived: function () { // 90 var self = this; // 91 // Tell periodic checkin that we have seen a packet, and thus it // 92 // does not need to send a ping this cycle. // 93 self._seenPacket = true; // 94 // If we were waiting for a pong, we got it. // 95 if (self._heartbeatTimeoutHandle) { // 96 self._clearHeartbeatTimeoutTimer(); // 97 } // 98 } // 99 }); // 100 // 101 /////////////////////////////////////////////////////////////////////////////////////////////////////// }).call(this); (function(){ /////////////////////////////////////////////////////////////////////////////////////////////////////// // // // packages/ddp-common/utils.js // // // /////////////////////////////////////////////////////////////////////////////////////////////////////// // DDPCommon.SUPPORTED_DDP_VERSIONS = [ '1', 'pre2', 'pre1' ]; // 1 // 2 DDPCommon.parseDDP = function (stringMessage) { // 3 try { // 4 var msg = JSON.parse(stringMessage); // 5 } catch (e) { // 6 Meteor._debug("Discarding message with invalid JSON", stringMessage); // 7 return null; // 8 } // 9 // DDP messages must be objects. // 10 if (msg === null || typeof msg !== 'object') { // 11 Meteor._debug("Discarding non-object DDP message", stringMessage); // 12 return null; // 13 } // 14 // 15 // massage msg to get it into "abstract ddp" rather than "wire ddp" format. // 16 // 17 // switch between "cleared" rep of unsetting fields and "undefined" // 18 // rep of same // 19 if (_.has(msg, 'cleared')) { // 20 if (!_.has(msg, 'fields')) // 21 msg.fields = {}; // 22 _.each(msg.cleared, function (clearKey) { // 23 msg.fields[clearKey] = undefined; // 24 }); // 25 delete msg.cleared; // 26 } // 27 // 28 _.each(['fields', 'params', 'result'], function (field) { // 29 if (_.has(msg, field)) // 30 msg[field] = EJSON._adjustTypesFromJSONValue(msg[field]); // 31 }); // 32 // 33 return msg; // 34 }; // 35 // 36 DDPCommon.stringifyDDP = function (msg) { // 37 var copy = EJSON.clone(msg); // 38 // swizzle 'changed' messages from 'fields undefined' rep to 'fields // 39 // and cleared' rep // 40 if (_.has(msg, 'fields')) { // 41 var cleared = []; // 42 _.each(msg.fields, function (value, key) { // 43 if (value === undefined) { // 44 cleared.push(key); // 45 delete copy.fields[key]; // 46 } // 47 }); // 48 if (!_.isEmpty(cleared)) // 49 copy.cleared = cleared; // 50 if (_.isEmpty(copy.fields)) // 51 delete copy.fields; // 52 } // 53 // adjust types to basic // 54 _.each(['fields', 'params', 'result'], function (field) { // 55 if (_.has(copy, field)) // 56 copy[field] = EJSON._adjustTypesToJSONValue(copy[field]); // 57 }); // 58 if (msg.id && typeof msg.id !== 'string') { // 59 throw new Error("Message id is not a string"); // 60 } // 61 return JSON.stringify(copy); // 62 }; // 63 // 64 // 65 /////////////////////////////////////////////////////////////////////////////////////////////////////// }).call(this); (function(){ /////////////////////////////////////////////////////////////////////////////////////////////////////// // // // packages/ddp-common/method_invocation.js // // // /////////////////////////////////////////////////////////////////////////////////////////////////////// // // Instance name is this because it is usually referred to as this inside a // 1 // method definition // 2 /** // 3 * @summary The state for a single invocation of a method, referenced by this // 4 * inside a method definition. // 5 * @param {Object} options // 6 * @instanceName this // 7 */ // 8 DDPCommon.MethodInvocation = function (options) { // 9 var self = this; // 10 // 11 // true if we're running not the actual method, but a stub (that is, // 12 // if we're on a client (which may be a browser, or in the future a // 13 // server connecting to another server) and presently running a // 14 // simulation of a server-side method for latency compensation // 15 // purposes). not currently true except in a client such as a browser, // 16 // since there's usually no point in running stubs unless you have a // 17 // zero-latency connection to the user. // 18 // 19 /** // 20 * @summary Access inside a method invocation. Boolean value, true if this invocation is a stub. // 21 * @locus Anywhere // 22 * @name isSimulation // 23 * @memberOf DDPCommon.MethodInvocation // 24 * @instance // 25 * @type {Boolean} // 26 */ // 27 this.isSimulation = options.isSimulation; // 28 // 29 // call this function to allow other method invocations (from the // 30 // same client) to continue running without waiting for this one to // 31 // complete. // 32 this._unblock = options.unblock || function () {}; // 33 this._calledUnblock = false; // 34 // 35 // current user id // 36 // 37 /** // 38 * @summary The id of the user that made this method call, or `null` if no user was logged in. // 39 * @locus Anywhere // 40 * @name userId // 41 * @memberOf DDPCommon.MethodInvocation // 42 * @instance // 43 */ // 44 this.userId = options.userId; // 45 // 46 // sets current user id in all appropriate server contexts and // 47 // reruns subscriptions // 48 this._setUserId = options.setUserId || function () {}; // 49 // 50 // On the server, the connection this method call came in on. // 51 // 52 /** // 53 * @summary Access inside a method invocation. The [connection](#meteor_onconnection) that this method was received on. `null` if the method is not associated with a connection, eg. a server initiated method call. * @locus Server // 55 * @name connection // 56 * @memberOf DDPCommon.MethodInvocation // 57 * @instance // 58 */ // 59 this.connection = options.connection; // 60 // 61 // The seed for randomStream value generation // 62 this.randomSeed = options.randomSeed; // 63 // 64 // This is set by RandomStream.get; and holds the random stream state // 65 this.randomStream = null; // 66 }; // 67 // 68 _.extend(DDPCommon.MethodInvocation.prototype, { // 69 /** // 70 * @summary Call inside a method invocation. Allow subsequent method from this client to begin running in a new fiber. * @locus Server // 72 * @memberOf DDPCommon.MethodInvocation // 73 * @instance // 74 */ // 75 unblock: function () { // 76 var self = this; // 77 self._calledUnblock = true; // 78 self._unblock(); // 79 }, // 80 // 81 /** // 82 * @summary Set the logged in user. // 83 * @locus Server // 84 * @memberOf DDPCommon.MethodInvocation // 85 * @instance // 86 * @param {String | null} userId The value that should be returned by `userId` on this connection. */ // 88 setUserId: function(userId) { // 89 var self = this; // 90 if (self._calledUnblock) // 91 throw new Error("Can't call setUserId in a method after calling unblock"); // 92 self.userId = userId; // 93 self._setUserId(userId); // 94 } // 95 }); // 96 // 97 // 98 /////////////////////////////////////////////////////////////////////////////////////////////////////// }).call(this); (function(){ /////////////////////////////////////////////////////////////////////////////////////////////////////// // // // packages/ddp-common/random_stream.js // // // /////////////////////////////////////////////////////////////////////////////////////////////////////// // // RandomStream allows for generation of pseudo-random values, from a seed. // 1 // // 2 // We use this for consistent 'random' numbers across the client and server. // 3 // We want to generate probably-unique IDs on the client, and we ideally want // 4 // the server to generate the same IDs when it executes the method. // 5 // // 6 // For generated values to be the same, we must seed ourselves the same way, // 7 // and we must keep track of the current state of our pseudo-random generators. // 8 // We call this state the scope. By default, we use the current DDP method // 9 // invocation as our scope. DDP now allows the client to specify a randomSeed. // 10 // If a randomSeed is provided it will be used to seed our random sequences. // 11 // In this way, client and server method calls will generate the same values. // 12 // // 13 // We expose multiple named streams; each stream is independent // 14 // and is seeded differently (but predictably from the name). // 15 // By using multiple streams, we support reordering of requests, // 16 // as long as they occur on different streams. // 17 // // 18 // @param options {Optional Object} // 19 // seed: Array or value - Seed value(s) for the generator. // 20 // If an array, will be used as-is // 21 // If a value, will be converted to a single-value array // 22 // If omitted, a random array will be used as the seed. // 23 DDPCommon.RandomStream = function (options) { // 24 var self = this; // 25 // 26 this.seed = [].concat(options.seed || randomToken()); // 27 // 28 this.sequences = {}; // 29 }; // 30 // 31 // Returns a random string of sufficient length for a random seed. // 32 // This is a placeholder function; a similar function is planned // 33 // for Random itself; when that is added we should remove this function, // 34 // and call Random's randomToken instead. // 35 function randomToken() { // 36 return Random.hexString(20); // 37 }; // 38 // 39 // Returns the random stream with the specified name, in the specified // 40 // scope. If a scope is passed, then we use that to seed a (not // 41 // cryptographically secure) PRNG using the fast Alea algorithm. If // 42 // scope is null (or otherwise falsey) then we use a generated seed. // 43 // // 44 // However, scope will normally be the current DDP method invocation, // 45 // so we'll use the stream with the specified name, and we should get // 46 // consistent values on the client and server sides of a method call. // 47 DDPCommon.RandomStream.get = function (scope, name) { // 48 if (!name) { // 49 name = "default"; // 50 } // 51 if (!scope) { // 52 // There was no scope passed in; the sequence won't actually be // 53 // reproducible. but make it fast (and not cryptographically // 54 // secure) anyways, so that the behavior is similar to what you'd // 55 // get by passing in a scope. // 56 return Random.insecure; // 57 } // 58 var randomStream = scope.randomStream; // 59 if (!randomStream) { // 60 scope.randomStream = randomStream = new DDPCommon.RandomStream({ // 61 seed: scope.randomSeed // 62 }); // 63 } // 64 return randomStream._sequence(name); // 65 }; // 66 // 67 // 68 // Creates a randomSeed for passing to a method call. // 69 // Note that we take enclosing as an argument, // 70 // though we expect it to be DDP._CurrentInvocation.get() // 71 // However, we often evaluate makeRpcSeed lazily, and thus the relevant // 72 // invocation may not be the one currently in scope. // 73 // If enclosing is null, we'll use Random and values won't be repeatable. // 74 DDPCommon.makeRpcSeed = function (enclosing, methodName) { // 75 var stream = DDPCommon.RandomStream.get(enclosing, '/rpc/' + methodName); // 76 return stream.hexString(20); // 77 }; // 78 // 79 _.extend(DDPCommon.RandomStream.prototype, { // 80 // Get a random sequence with the specified name, creating it if does not exist. // 81 // New sequences are seeded with the seed concatenated with the name. // 82 // By passing a seed into Random.create, we use the Alea generator. // 83 _sequence: function (name) { // 84 var self = this; // 85 // 86 var sequence = self.sequences[name] || null; // 87 if (sequence === null) { // 88 var sequenceSeed = self.seed.concat(name); // 89 for (var i = 0; i < sequenceSeed.length; i++) { // 90 if (_.isFunction(sequenceSeed[i])) { // 91 sequenceSeed[i] = sequenceSeed[i](); // 92 } // 93 } // 94 self.sequences[name] = sequence = Random.createWithSeeds.apply(null, sequenceSeed); // 95 } // 96 return sequence; // 97 } // 98 }); // 99 // 100 /////////////////////////////////////////////////////////////////////////////////////////////////////// }).call(this); /* Exports */ if (typeof Package === 'undefined') Package = {}; Package['ddp-common'] = { DDPCommon: DDPCommon }; })(); //# sourceMappingURL=ddp-common.js.map