UNPKG

jsforce2

Version:

Salesforce API Library for JavaScript

1,488 lines (1,262 loc) 270 kB
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.jsforce||(g.jsforce = {}));g=(g.modules||(g.modules = {}));g=(g.api||(g.api = {}));g.Streaming = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ /** * Faye Client extensions: https://faye.jcoglan.com/browser/extensions.html * * For use with Streaming.prototype.createClient() **/ var StreamingExtension = {}; /** * Constructor for an auth failure detector extension * * Based on new feature released with Salesforce Spring '18: * https://releasenotes.docs.salesforce.com/en-us/spring18/release-notes/rn_messaging_cometd_auth_validation.htm?edition=&impact= * * Example triggering error message: * * ``` * { * "ext":{ * "sfdc":{"failureReason":"401::Authentication invalid"}, * "replay":true}, * "advice":{"reconnect":"none"}, * "channel":"/meta/handshake", * "error":"403::Handshake denied", * "successful":false * } * ``` * * Example usage: * * ```javascript * const conn = new jsforce.Connection({ … }); * * const channel = "/event/My_Event__e"; * * // Exit the Node process when auth fails * const exitCallback = () => process.exit(1); * const authFailureExt = new jsforce.StreamingExtension.AuthFailure(exitCallback); * * const fayeClient = conn.streaming.createClient([ authFailureExt ]); * * const subscription = fayeClient.subscribe(channel, data => { * console.log('topic received data', data); * }); * * subscription.cancel(); * ``` * * @param {Function} failureCallback - Invoked when authentication becomes invalid */ StreamingExtension.AuthFailure = function(failureCallback) { this.incoming = function(message, callback) { if ( (message.channel === '/meta/connect' || message.channel === '/meta/handshake') && message.advice && message.advice.reconnect == 'none' ) { failureCallback(message); } else { callback(message); } } }; /** * Constructor for a durable streaming replay extension * * Modified from original Salesforce demo source code: * https://github.com/developerforce/SalesforceDurableStreamingDemo/blob/3d4a56eac956f744ad6c22e6a8141b6feb57abb9/staticresources/cometdReplayExtension.resource * * Example usage: * * ```javascript * const conn = new jsforce.Connection({ … }); * * const channel = "/event/My_Event__e"; * const replayId = -2; // -2 is all retained events * * const replayExt = new jsforce.StreamingExtension.Replay(channel, replayId); * * const fayeClient = conn.streaming.createClient([ replayExt ]); * * const subscription = fayeClient.subscribe(channel, data => { * console.log('topic received data', data); * }); * * subscription.cancel(); * ``` */ StreamingExtension.Replay = function(channel, replayId) { var REPLAY_FROM_KEY = "replay"; var _extensionEnabled = replayId != null ? true : false; var _replay = replayId; var _channel = channel; this.setExtensionEnabled = function(extensionEnabled) { _extensionEnabled = extensionEnabled; } this.setReplay = function (replay) { _replay = parseInt(replay, 10); } this.setChannel = function(channel) { _channel = channel; } this.incoming = function(message, callback) { if (message.channel === '/meta/handshake') { if (message.ext && message.ext[REPLAY_FROM_KEY] == true) { _extensionEnabled = true; } } else if (message.channel === _channel && message.data && message.data.event && message.data.event.replayId) { _replay = message.data.event.replayId; } callback(message); } this.outgoing = function(message, callback) { if (message.channel === '/meta/subscribe' && message.subscription === _channel) { if (_extensionEnabled) { if (!message.ext) { message.ext = {}; } var replayFromMap = {}; replayFromMap[_channel] = _replay; // add "ext : { "replay" : { CHANNEL : REPLAY_VALUE }}" to subscribe message message.ext[REPLAY_FROM_KEY] = replayFromMap; } } callback(message); }; }; module.exports = StreamingExtension; },{}],2:[function(require,module,exports){ /** * @file Manages Streaming APIs * @author Shinichi Tomita <shinichi.tomita@gmail.com> */ 'use strict'; var events = window.jsforce.require('events'), inherits = window.jsforce.require('inherits'), _ = window.jsforce.require('lodash/core'), Faye = require('faye'), StreamingExtension = require('./streaming-extension'), jsforce = window.jsforce.require('./core'); /** * Streaming API topic class * * @class Streaming~Topic * @param {Streaming} steaming - Streaming API object * @param {String} name - Topic name */ var Topic = function(streaming, name) { this._streaming = streaming; this.name = name; }; /** * @typedef {Object} Streaming~StreamingMessage * @prop {Object} event * @prop {Object} event.type - Event type * @prop {Record} sobject - Record information */ /** * Subscribe listener to topic * * @method Streaming~Topic#subscribe * @param {Callback.<Streaming~StreamingMesasge>} listener - Streaming message listener * @returns {Subscription} - Faye subscription object */ Topic.prototype.subscribe = function(listener) { return this._streaming.subscribe(this.name, listener); }; /** * Unsubscribe listener from topic * * @method Streaming~Topic#unsubscribe * @param {Callback.<Streaming~StreamingMesasge>} listener - Streaming message listener * @returns {Streaming~Topic} */ Topic.prototype.unsubscribe = function(listener) { this._streaming.unsubscribe(this.name, listener); return this; }; /*--------------------------------------------*/ /** * Streaming API Generic Streaming Channel * * @class Streaming~Channel * @param {Streaming} steaming - Streaming API object * @param {String} name - Channel name (starts with "/u/") */ var Channel = function(streaming, name) { this._streaming = streaming; this._name = name; }; /** * Subscribe to channel * * @param {Callback.<Streaming~StreamingMessage>} listener - Streaming message listener * @returns {Subscription} - Faye subscription object */ Channel.prototype.subscribe = function(listener) { return this._streaming.subscribe(this._name, listener); }; Channel.prototype.unsubscribe = function(listener) { this._streaming.unsubscribe(this._name, listener); return this; }; Channel.prototype.push = function(events, callback) { var isArray = _.isArray(events); events = isArray ? events : [ events ]; var conn = this._streaming._conn; if (!this._id) { this._id = conn.sobject('StreamingChannel').findOne({ Name: this._name }, 'Id') .then(function(rec) { return rec.Id }); } return this._id.then(function(id) { var channelUrl = '/sobjects/StreamingChannel/' + id + '/push'; return conn.requestPost(channelUrl, { pushEvents: events }); }).then(function(rets) { return isArray ? rets : rets[0]; }).thenCall(callback); }; /*--------------------------------------------*/ /** * Streaming API class * * @class * @extends events.EventEmitter * @param {Connection} conn - Connection object */ var Streaming = function(conn) { this._conn = conn; }; inherits(Streaming, events.EventEmitter); /** @private **/ Streaming.prototype._createClient = function(forChannelName, extensions) { // forChannelName is advisory, for an API workaround. It does not restrict or select the channel. var needsReplayFix = typeof forChannelName === 'string' && forChannelName.indexOf('/u/') === 0; var endpointUrl = [ this._conn.instanceUrl, // special endpoint "/cometd/replay/xx.x" is only available in 36.0. // See https://releasenotes.docs.salesforce.com/en-us/summer16/release-notes/rn_api_streaming_classic_replay.htm "cometd" + (needsReplayFix === true && this._conn.version === "36.0" ? "/replay" : ""), this._conn.version ].join('/'); var fayeClient = new Faye.Client(endpointUrl, {}); fayeClient.setHeader('Authorization', 'OAuth '+this._conn.accessToken); if (extensions instanceof Array) { extensions.forEach(function(extension) { fayeClient.addExtension(extension); }); } if (fayeClient._dispatcher.getConnectionTypes().indexOf('callback-polling') === -1) { // prevent streaming API server error fayeClient._dispatcher.selectTransport('long-polling'); fayeClient._dispatcher._transport.batching = false; } return fayeClient; }; /** @private **/ Streaming.prototype._getFayeClient = function(channelName) { var isGeneric = channelName.indexOf('/u/') === 0; var clientType = isGeneric ? 'generic' : 'pushTopic'; if (!this._fayeClients || !this._fayeClients[clientType]) { this._fayeClients = this._fayeClients || {}; this._fayeClients[clientType] = this._createClient(channelName); } return this._fayeClients[clientType]; }; /** * Get named topic * * @param {String} name - Topic name * @returns {Streaming~Topic} */ Streaming.prototype.topic = function(name) { this._topics = this._topics || {}; var topic = this._topics[name] = this._topics[name] || new Topic(this, name); return topic; }; /** * Get Channel for Id * @param {String} channelId - Id of StreamingChannel object * @returns {Streaming~Channel} */ Streaming.prototype.channel = function(channelId) { return new Channel(this, channelId); }; /** * Subscribe topic/channel * * @param {String} name - Topic name * @param {Callback.<Streaming~StreamingMessage>} listener - Streaming message listener * @returns {Subscription} - Faye subscription object */ Streaming.prototype.subscribe = function(name, listener) { var channelName = name.indexOf('/') === 0 ? name : '/topic/' + name; var fayeClient = this._getFayeClient(channelName); return fayeClient.subscribe(channelName, listener); }; /** * Unsubscribe topic * * @param {String} name - Topic name * @param {Callback.<Streaming~StreamingMessage>} listener - Streaming message listener * @returns {Streaming} */ Streaming.prototype.unsubscribe = function(name, listener) { var channelName = name.indexOf('/') === 0 ? name : '/topic/' + name; var fayeClient = this._getFayeClient(channelName); fayeClient.unsubscribe(channelName, listener); return this; }; /** * Create a Streaming client, optionally with extensions * * See Faye docs for implementation details: https://faye.jcoglan.com/browser/extensions.html * * Example usage: * * ```javascript * // Establish a Salesforce connection. (Details elided) * const conn = new jsforce.Connection({ … }); * * const fayeClient = conn.streaming.createClient(); * * const subscription = fayeClient.subscribe(channel, data => { * console.log('topic received data', data); * }); * * subscription.cancel(); * ``` * * Example with extensions, using Replay & Auth Failure extensions in a server-side Node.js app: * * ```javascript * // Establish a Salesforce connection. (Details elided) * const conn = new jsforce.Connection({ … }); * * const channel = "/event/My_Event__e"; * const replayId = -2; // -2 is all retained events * * const exitCallback = () => process.exit(1); * const authFailureExt = new jsforce.StreamingExtension.AuthFailure(exitCallback); * * const replayExt = new jsforce.StreamingExtension.Replay(channel, replayId); * * const fayeClient = conn.streaming.createClient([ * authFailureExt, * replayExt * ]); * * const subscription = fayeClient.subscribe(channel, data => { * console.log('topic received data', data); * }); * * subscription.cancel(); * ``` * * @param {Array} Extensions - Optional, extensions to apply to the Faye client * @returns {FayeClient} - Faye client object */ Streaming.prototype.createClient = function(extensions) { return this._createClient(null, extensions); }; /*--------------------------------------------*/ /* * Register hook in connection instantiation for dynamically adding this API module features */ jsforce.on('connection:new', function(conn) { conn.streaming = new Streaming(conn); }); /* * */ jsforce.StreamingExtension = StreamingExtension; module.exports = Streaming; },{"./streaming-extension":1,"faye":5}],3:[function(require,module,exports){ "use strict"; // rawAsap provides everything we need except exception management. var rawAsap = require("./raw"); // RawTasks are recycled to reduce GC churn. var freeTasks = []; // We queue errors to ensure they are thrown in right order (FIFO). // Array-as-queue is good enough here, since we are just dealing with exceptions. var pendingErrors = []; var requestErrorThrow = rawAsap.makeRequestCallFromTimer(throwFirstError); function throwFirstError() { if (pendingErrors.length) { throw pendingErrors.shift(); } } /** * Calls a task as soon as possible after returning, in its own event, with priority * over other events like animation, reflow, and repaint. An error thrown from an * event will not interrupt, nor even substantially slow down the processing of * other events, but will be rather postponed to a lower priority event. * @param {{call}} task A callable object, typically a function that takes no * arguments. */ module.exports = asap; function asap(task) { var rawTask; if (freeTasks.length) { rawTask = freeTasks.pop(); } else { rawTask = new RawTask(); } rawTask.task = task; rawAsap(rawTask); } // We wrap tasks with recyclable task objects. A task object implements // `call`, just like a function. function RawTask() { this.task = null; } // The sole purpose of wrapping the task is to catch the exception and recycle // the task object after its single use. RawTask.prototype.call = function () { try { this.task.call(); } catch (error) { if (asap.onerror) { // This hook exists purely for testing purposes. // Its name will be periodically randomized to break any code that // depends on its existence. asap.onerror(error); } else { // In a web browser, exceptions are not fatal. However, to avoid // slowing down the queue of pending tasks, we rethrow the error in a // lower priority turn. pendingErrors.push(error); requestErrorThrow(); } } finally { this.task = null; freeTasks[freeTasks.length] = this; } }; },{"./raw":4}],4:[function(require,module,exports){ (function (global){ "use strict"; // Use the fastest means possible to execute a task in its own turn, with // priority over other events including IO, animation, reflow, and redraw // events in browsers. // // An exception thrown by a task will permanently interrupt the processing of // subsequent tasks. The higher level `asap` function ensures that if an // exception is thrown by a task, that the task queue will continue flushing as // soon as possible, but if you use `rawAsap` directly, you are responsible to // either ensure that no exceptions are thrown from your task, or to manually // call `rawAsap.requestFlush` if an exception is thrown. module.exports = rawAsap; function rawAsap(task) { if (!queue.length) { requestFlush(); flushing = true; } // Equivalent to push, but avoids a function call. queue[queue.length] = task; } var queue = []; // Once a flush has been requested, no further calls to `requestFlush` are // necessary until the next `flush` completes. var flushing = false; // `requestFlush` is an implementation-specific method that attempts to kick // off a `flush` event as quickly as possible. `flush` will attempt to exhaust // the event queue before yielding to the browser's own event loop. var requestFlush; // The position of the next task to execute in the task queue. This is // preserved between calls to `flush` so that it can be resumed if // a task throws an exception. var index = 0; // If a task schedules additional tasks recursively, the task queue can grow // unbounded. To prevent memory exhaustion, the task queue will periodically // truncate already-completed tasks. var capacity = 1024; // The flush function processes all tasks that have been scheduled with // `rawAsap` unless and until one of those tasks throws an exception. // If a task throws an exception, `flush` ensures that its state will remain // consistent and will resume where it left off when called again. // However, `flush` does not make any arrangements to be called again if an // exception is thrown. function flush() { while (index < queue.length) { var currentIndex = index; // Advance the index before calling the task. This ensures that we will // begin flushing on the next task the task throws an error. index = index + 1; queue[currentIndex].call(); // Prevent leaking memory for long chains of recursive calls to `asap`. // If we call `asap` within tasks scheduled by `asap`, the queue will // grow, but to avoid an O(n) walk for every task we execute, we don't // shift tasks off the queue after they have been executed. // Instead, we periodically shift 1024 tasks off the queue. if (index > capacity) { // Manually shift all values starting at the index back to the // beginning of the queue. for (var scan = 0, newLength = queue.length - index; scan < newLength; scan++) { queue[scan] = queue[scan + index]; } queue.length -= index; index = 0; } } queue.length = 0; index = 0; flushing = false; } // `requestFlush` is implemented using a strategy based on data collected from // every available SauceLabs Selenium web driver worker at time of writing. // https://docs.google.com/spreadsheets/d/1mG-5UYGup5qxGdEMWkhP6BWCz053NUb2E1QoUTU16uA/edit#gid=783724593 // Safari 6 and 6.1 for desktop, iPad, and iPhone are the only browsers that // have WebKitMutationObserver but not un-prefixed MutationObserver. // Must use `global` or `self` instead of `window` to work in both frames and web // workers. `global` is a provision of Browserify, Mr, Mrs, or Mop. /* globals self */ var scope = typeof global !== "undefined" ? global : self; var BrowserMutationObserver = scope.MutationObserver || scope.WebKitMutationObserver; // MutationObservers are desirable because they have high priority and work // reliably everywhere they are implemented. // They are implemented in all modern browsers. // // - Android 4-4.3 // - Chrome 26-34 // - Firefox 14-29 // - Internet Explorer 11 // - iPad Safari 6-7.1 // - iPhone Safari 7-7.1 // - Safari 6-7 if (typeof BrowserMutationObserver === "function") { requestFlush = makeRequestCallFromMutationObserver(flush); // MessageChannels are desirable because they give direct access to the HTML // task queue, are implemented in Internet Explorer 10, Safari 5.0-1, and Opera // 11-12, and in web workers in many engines. // Although message channels yield to any queued rendering and IO tasks, they // would be better than imposing the 4ms delay of timers. // However, they do not work reliably in Internet Explorer or Safari. // Internet Explorer 10 is the only browser that has setImmediate but does // not have MutationObservers. // Although setImmediate yields to the browser's renderer, it would be // preferrable to falling back to setTimeout since it does not have // the minimum 4ms penalty. // Unfortunately there appears to be a bug in Internet Explorer 10 Mobile (and // Desktop to a lesser extent) that renders both setImmediate and // MessageChannel useless for the purposes of ASAP. // https://github.com/kriskowal/q/issues/396 // Timers are implemented universally. // We fall back to timers in workers in most engines, and in foreground // contexts in the following browsers. // However, note that even this simple case requires nuances to operate in a // broad spectrum of browsers. // // - Firefox 3-13 // - Internet Explorer 6-9 // - iPad Safari 4.3 // - Lynx 2.8.7 } else { requestFlush = makeRequestCallFromTimer(flush); } // `requestFlush` requests that the high priority event queue be flushed as // soon as possible. // This is useful to prevent an error thrown in a task from stalling the event // queue if the exception handled by Node.js’s // `process.on("uncaughtException")` or by a domain. rawAsap.requestFlush = requestFlush; // To request a high priority event, we induce a mutation observer by toggling // the text of a text node between "1" and "-1". function makeRequestCallFromMutationObserver(callback) { var toggle = 1; var observer = new BrowserMutationObserver(callback); var node = document.createTextNode(""); observer.observe(node, {characterData: true}); return function requestCall() { toggle = -toggle; node.data = toggle; }; } // The message channel technique was discovered by Malte Ubl and was the // original foundation for this library. // http://www.nonblocking.io/2011/06/windownexttick.html // Safari 6.0.5 (at least) intermittently fails to create message ports on a // page's first load. Thankfully, this version of Safari supports // MutationObservers, so we don't need to fall back in that case. // function makeRequestCallFromMessageChannel(callback) { // var channel = new MessageChannel(); // channel.port1.onmessage = callback; // return function requestCall() { // channel.port2.postMessage(0); // }; // } // For reasons explained above, we are also unable to use `setImmediate` // under any circumstances. // Even if we were, there is another bug in Internet Explorer 10. // It is not sufficient to assign `setImmediate` to `requestFlush` because // `setImmediate` must be called *by name* and therefore must be wrapped in a // closure. // Never forget. // function makeRequestCallFromSetImmediate(callback) { // return function requestCall() { // setImmediate(callback); // }; // } // Safari 6.0 has a problem where timers will get lost while the user is // scrolling. This problem does not impact ASAP because Safari 6.0 supports // mutation observers, so that implementation is used instead. // However, if we ever elect to use timers in Safari, the prevalent work-around // is to add a scroll event listener that calls for a flush. // `setTimeout` does not call the passed callback if the delay is less than // approximately 7 in web workers in Firefox 8 through 18, and sometimes not // even then. function makeRequestCallFromTimer(callback) { return function requestCall() { // We dispatch a timeout with a specified delay of 0 for engines that // can reliably accommodate that request. This will usually be snapped // to a 4 milisecond delay, but once we're flushing, there's no delay // between events. var timeoutHandle = setTimeout(handleTimer, 0); // However, since this timer gets frequently dropped in Firefox // workers, we enlist an interval handle that will try to fire // an event 20 times per second until it succeeds. var intervalHandle = setInterval(handleTimer, 50); function handleTimer() { // Whichever timer succeeds will cancel both timers and // execute the callback. clearTimeout(timeoutHandle); clearInterval(intervalHandle); callback(); } }; } // This is for `asap.js` only. // Its name will be periodically randomized to break any code that depends on // its existence. rawAsap.makeRequestCallFromTimer = makeRequestCallFromTimer; // ASAP was originally a nextTick shim included in Q. This was factored out // into this ASAP package. It was later adapted to RSVP which made further // amendments. These decisions, particularly to marginalize MessageChannel and // to capture the MutationObserver implementation in a closure, were integrated // back into ASAP proper. // https://github.com/tildeio/rsvp.js/blob/cddf7232546a9cf858524b75cde6f9edf72620a7/lib/rsvp/asap.js }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{}],5:[function(require,module,exports){ 'use strict'; var constants = require('./util/constants'), Logging = require('./mixins/logging'); var Faye = { VERSION: constants.VERSION, Client: require('./protocol/client'), Scheduler: require('./protocol/scheduler') }; Logging.wrapper = Faye; module.exports = Faye; },{"./mixins/logging":7,"./protocol/client":11,"./protocol/scheduler":17,"./util/constants":29}],6:[function(require,module,exports){ (function (global){ 'use strict'; var Promise = require('../util/promise'); module.exports = { then: function(callback, errback) { var self = this; if (!this._promise) this._promise = new Promise(function(resolve, reject) { self._resolve = resolve; self._reject = reject; }); if (arguments.length === 0) return this._promise; else return this._promise.then(callback, errback); }, callback: function(callback, context) { return this.then(function(value) { callback.call(context, value) }); }, errback: function(callback, context) { return this.then(null, function(reason) { callback.call(context, reason) }); }, timeout: function(seconds, message) { this.then(); var self = this; this._timer = global.setTimeout(function() { self._reject(message); }, seconds * 1000); }, setDeferredStatus: function(status, value) { if (this._timer) global.clearTimeout(this._timer); this.then(); if (status === 'succeeded') this._resolve(value); else if (status === 'failed') this._reject(value); else delete this._promise; } }; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"../util/promise":34}],7:[function(require,module,exports){ 'use strict'; var toJSON = require('../util/to_json'); var Logging = { LOG_LEVELS: { fatal: 4, error: 3, warn: 2, info: 1, debug: 0 }, writeLog: function(messageArgs, level) { var logger = Logging.logger || (Logging.wrapper || Logging).logger; if (!logger) return; var args = Array.prototype.slice.apply(messageArgs), banner = '[Faye', klass = this.className, message = args.shift().replace(/\?/g, function() { try { return toJSON(args.shift()); } catch (error) { return '[Object]'; } }); if (klass) banner += '.' + klass; banner += '] '; if (typeof logger[level] === 'function') logger[level](banner + message); else if (typeof logger === 'function') logger(banner + message); } }; for (var key in Logging.LOG_LEVELS) (function(level) { Logging[level] = function() { this.writeLog(arguments, level); }; })(key); module.exports = Logging; },{"../util/to_json":36}],8:[function(require,module,exports){ 'use strict'; var extend = require('../util/extend'), EventEmitter = require('../util/event_emitter'); var Publisher = { countListeners: function(eventType) { return this.listeners(eventType).length; }, bind: function(eventType, listener, context) { var slice = Array.prototype.slice, handler = function() { listener.apply(context, slice.call(arguments)) }; this._listeners = this._listeners || []; this._listeners.push([eventType, listener, context, handler]); return this.on(eventType, handler); }, unbind: function(eventType, listener, context) { this._listeners = this._listeners || []; var n = this._listeners.length, tuple; while (n--) { tuple = this._listeners[n]; if (tuple[0] !== eventType) continue; if (listener && (tuple[1] !== listener || tuple[2] !== context)) continue; this._listeners.splice(n, 1); this.removeListener(eventType, tuple[3]); } } }; extend(Publisher, EventEmitter.prototype); Publisher.trigger = Publisher.emit; module.exports = Publisher; },{"../util/event_emitter":32,"../util/extend":33}],9:[function(require,module,exports){ (function (global){ 'use strict'; module.exports = { addTimeout: function(name, delay, callback, context) { this._timeouts = this._timeouts || {}; if (this._timeouts.hasOwnProperty(name)) return; var self = this; this._timeouts[name] = global.setTimeout(function() { delete self._timeouts[name]; callback.call(context); }, 1000 * delay); }, removeTimeout: function(name) { this._timeouts = this._timeouts || {}; var timeout = this._timeouts[name]; if (!timeout) return; global.clearTimeout(timeout); delete this._timeouts[name]; }, removeAllTimeouts: function() { this._timeouts = this._timeouts || {}; for (var name in this._timeouts) this.removeTimeout(name); } }; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{}],10:[function(require,module,exports){ 'use strict'; var Class = require('../util/class'), extend = require('../util/extend'), Publisher = require('../mixins/publisher'), Grammar = require('./grammar'); var Channel = Class({ initialize: function(name) { this.id = this.name = name; }, push: function(message) { this.trigger('message', message); }, isUnused: function() { return this.countListeners('message') === 0; } }); extend(Channel.prototype, Publisher); extend(Channel, { HANDSHAKE: '/meta/handshake', CONNECT: '/meta/connect', SUBSCRIBE: '/meta/subscribe', UNSUBSCRIBE: '/meta/unsubscribe', DISCONNECT: '/meta/disconnect', META: 'meta', SERVICE: 'service', expand: function(name) { var segments = this.parse(name), channels = ['/**', name]; var copy = segments.slice(); copy[copy.length - 1] = '*'; channels.push(this.unparse(copy)); for (var i = 1, n = segments.length; i < n; i++) { copy = segments.slice(0, i); copy.push('**'); channels.push(this.unparse(copy)); } return channels; }, isValid: function(name) { return Grammar.CHANNEL_NAME.test(name) || Grammar.CHANNEL_PATTERN.test(name); }, parse: function(name) { if (!this.isValid(name)) return null; return name.split('/').slice(1); }, unparse: function(segments) { return '/' + segments.join('/'); }, isMeta: function(name) { var segments = this.parse(name); return segments ? (segments[0] === this.META) : null; }, isService: function(name) { var segments = this.parse(name); return segments ? (segments[0] === this.SERVICE) : null; }, isSubscribable: function(name) { if (!this.isValid(name)) return null; return !this.isMeta(name) && !this.isService(name); }, Set: Class({ initialize: function() { this._channels = {}; }, getKeys: function() { var keys = []; for (var key in this._channels) keys.push(key); return keys; }, remove: function(name) { delete this._channels[name]; }, hasSubscription: function(name) { return this._channels.hasOwnProperty(name); }, subscribe: function(names, subscription) { var name; for (var i = 0, n = names.length; i < n; i++) { name = names[i]; var channel = this._channels[name] = this._channels[name] || new Channel(name); channel.bind('message', subscription); } }, unsubscribe: function(name, subscription) { var channel = this._channels[name]; if (!channel) return false; channel.unbind('message', subscription); if (channel.isUnused()) { this.remove(name); return true; } else { return false; } }, distributeMessage: function(message) { var channels = Channel.expand(message.channel); for (var i = 0, n = channels.length; i < n; i++) { var channel = this._channels[channels[i]]; if (channel) channel.trigger('message', message); } } }) }); module.exports = Channel; },{"../mixins/publisher":8,"../util/class":28,"../util/extend":33,"./grammar":15}],11:[function(require,module,exports){ (function (global){ 'use strict'; var asap = require('asap'), Class = require('../util/class'), Promise = require('../util/promise'), URI = require('../util/uri'), array = require('../util/array'), browser = require('../util/browser'), constants = require('../util/constants'), extend = require('../util/extend'), validateOptions = require('../util/validate_options'), Deferrable = require('../mixins/deferrable'), Logging = require('../mixins/logging'), Publisher = require('../mixins/publisher'), Channel = require('./channel'), Dispatcher = require('./dispatcher'), Error = require('./error'), Extensible = require('./extensible'), Publication = require('./publication'), Subscription = require('./subscription'); var Client = Class({ className: 'Client', UNCONNECTED: 1, CONNECTING: 2, CONNECTED: 3, DISCONNECTED: 4, HANDSHAKE: 'handshake', RETRY: 'retry', NONE: 'none', CONNECTION_TIMEOUT: 60, DEFAULT_ENDPOINT: '/bayeux', INTERVAL: 0, initialize: function(endpoint, options) { this.info('New client created for ?', endpoint); options = options || {}; validateOptions(options, ['interval', 'timeout', 'endpoints', 'proxy', 'retry', 'scheduler', 'websocketExtensions', 'tls', 'ca']); this._channels = new Channel.Set(); this._dispatcher = Dispatcher.create(this, endpoint || this.DEFAULT_ENDPOINT, options); this._messageId = 0; this._state = this.UNCONNECTED; this._responseCallbacks = {}; this._advice = { reconnect: this.RETRY, interval: 1000 * (options.interval || this.INTERVAL), timeout: 1000 * (options.timeout || this.CONNECTION_TIMEOUT) }; this._dispatcher.timeout = this._advice.timeout / 1000; this._dispatcher.bind('message', this._receiveMessage, this); if (browser.Event && global.onbeforeunload !== undefined) browser.Event.on(global, 'beforeunload', function() { if (array.indexOf(this._dispatcher._disabled, 'autodisconnect') < 0) this.disconnect(); }, this); }, addWebsocketExtension: function(extension) { return this._dispatcher.addWebsocketExtension(extension); }, disable: function(feature) { return this._dispatcher.disable(feature); }, setHeader: function(name, value) { return this._dispatcher.setHeader(name, value); }, // Request // MUST include: * channel // * version // * supportedConnectionTypes // MAY include: * minimumVersion // * ext // * id // // Success Response Failed Response // MUST include: * channel MUST include: * channel // * version * successful // * supportedConnectionTypes * error // * clientId MAY include: * supportedConnectionTypes // * successful * advice // MAY include: * minimumVersion * version // * advice * minimumVersion // * ext * ext // * id * id // * authSuccessful handshake: function(callback, context) { if (this._advice.reconnect === this.NONE) return; if (this._state !== this.UNCONNECTED) return; this._state = this.CONNECTING; var self = this; this.info('Initiating handshake with ?', URI.stringify(this._dispatcher.endpoint)); this._dispatcher.selectTransport(constants.MANDATORY_CONNECTION_TYPES); this._sendMessage({ channel: Channel.HANDSHAKE, version: constants.BAYEUX_VERSION, supportedConnectionTypes: this._dispatcher.getConnectionTypes() }, {}, function(response) { if (response.successful) { this._state = this.CONNECTED; this._dispatcher.clientId = response.clientId; this._dispatcher.selectTransport(response.supportedConnectionTypes); this.info('Handshake successful: ?', this._dispatcher.clientId); this.subscribe(this._channels.getKeys(), true); if (callback) asap(function() { callback.call(context) }); } else { this.info('Handshake unsuccessful'); global.setTimeout(function() { self.handshake(callback, context) }, this._dispatcher.retry * 1000); this._state = this.UNCONNECTED; } }, this); }, // Request Response // MUST include: * channel MUST include: * channel // * clientId * successful // * connectionType * clientId // MAY include: * ext MAY include: * error // * id * advice // * ext // * id // * timestamp connect: function(callback, context) { if (this._advice.reconnect === this.NONE) return; if (this._state === this.DISCONNECTED) return; if (this._state === this.UNCONNECTED) return this.handshake(function() { this.connect(callback, context) }, this); this.callback(callback, context); if (this._state !== this.CONNECTED) return; this.info('Calling deferred actions for ?', this._dispatcher.clientId); this.setDeferredStatus('succeeded'); this.setDeferredStatus('unknown'); if (this._connectRequest) return; this._connectRequest = true; this.info('Initiating connection for ?', this._dispatcher.clientId); this._sendMessage({ channel: Channel.CONNECT, clientId: this._dispatcher.clientId, connectionType: this._dispatcher.connectionType }, {}, this._cycleConnection, this); }, // Request Response // MUST include: * channel MUST include: * channel // * clientId * successful // MAY include: * ext * clientId // * id MAY include: * error // * ext // * id disconnect: function() { if (this._state !== this.CONNECTED) return; this._state = this.DISCONNECTED; this.info('Disconnecting ?', this._dispatcher.clientId); var promise = new Publication(); this._sendMessage({ channel: Channel.DISCONNECT, clientId: this._dispatcher.clientId }, {}, function(response) { if (response.successful) { this._dispatcher.close(); promise.setDeferredStatus('succeeded'); } else { promise.setDeferredStatus('failed', Error.parse(response.error)); } }, this); this.info('Clearing channel listeners for ?', this._dispatcher.clientId); this._channels = new Channel.Set(); return promise; }, // Request Response // MUST include: * channel MUST include: * channel // * clientId * successful // * subscription * clientId // MAY include: * ext * subscription // * id MAY include: * error // * advice // * ext // * id // * timestamp subscribe: function(channel, callback, context) { if (channel instanceof Array) return array.map(channel, function(c) { return this.subscribe(c, callback, context); }, this); var subscription = new Subscription(this, channel, callback, context), force = (callback === true), hasSubscribe = this._channels.hasSubscription(channel); if (hasSubscribe && !force) { this._channels.subscribe([channel], subscription); subscription.setDeferredStatus('succeeded'); return subscription; } this.connect(function() { this.info('Client ? attempting to subscribe to ?', this._dispatcher.clientId, channel); if (!force) this._channels.subscribe([channel], subscription); this._sendMessage({ channel: Channel.SUBSCRIBE, clientId: this._dispatcher.clientId, subscription: channel }, {}, function(response) { if (!response.successful) { subscription.setDeferredStatus('failed', Error.parse(response.error)); return this._channels.unsubscribe(channel, subscription); } var channels = [].concat(response.subscription); this.info('Subscription acknowledged for ? to ?', this._dispatcher.clientId, channels); subscription.setDeferredStatus('succeeded'); }, this); }, this); return subscription; }, // Request Response // MUST include: * channel MUST include: * channel // * clientId * successful // * subscription * clientId // MAY include: * ext * subscription // * id MAY include: * error // * advice // * ext // * id // * timestamp unsubscribe: function(channel, subscription) { if (channel instanceof Array) return array.map(channel, function(c) { return this.unsubscribe(c, subscription); }, this); var dead = this._channels.unsubscribe(channel, subscription); if (!dead) return; this.connect(function() { this.info('Client ? attempting to unsubscribe from ?', this._dispatcher.clientId, channel); this._sendMessage({ channel: Channel.UNSUBSCRIBE, clientId: this._dispatcher.clientId, subscription: channel }, {}, function(response) { if (!response.successful) return; var channels = [].concat(response.subscription); this.info('Unsubscription acknowledged for ? from ?', this._dispatcher.clientId, channels); }, this); }, this); }, // Request Response // MUST include: * channel MUST include: * channel // * data * successful // MAY include: * clientId MAY include: * id // * id * error // * ext * ext publish: function(channel, data, options) { validateOptions(options || {}, ['attempts', 'deadline']); var publication = new Publication(); this.connect(function() { this.info('Client ? queueing published message to ?: ?', this._dispatcher.clientId, channel, data); this._sendMessage({ channel: channel, data: data, clientId: this._dispatcher.clientId }, options, function(response) { if (response.successful) publication.setDeferredStatus('succeeded'); else publication.setDeferredStatus('failed', Error.parse(response.error)); }, this); }, this); return publication; }, _sendMessage: function(message, options, callback, context) { message.id = this._generateMessageId(); var timeout = this._advice.timeout ? 1.2 * this._advice.timeout / 1000 : 1.2 * this._dispatcher.retry; this.pipeThroughExtensions('outgoing', message, null, function(message) { if (!message) return; if (callback) this._responseCallbacks[message.id] = [callback, context]; this._dispatcher.sendMessage(message, timeout, options || {}); }, this); }, _generateMessageId: function() { this._messageId += 1; if (this._messageId >= Math.pow(2,32)) this._messageId = 0; return this._messageId.toString(36); }, _receiveMessage: function(message) { var id = message.id, callback; if (message.successful !== undefined) { callback = this._responseCallbacks[id]; delete this._responseCallbacks[id]; } this.pipeThroughExtensions('incoming', message, null, function(message) { if (!message) return; if (message.advice) this._handleAdvice(message.advice); this._deliverMessage(message); if (callback) callback[0].call(callback[1], message); }, this); }, _handleAdvice: function(advice) { extend(this._advice, advice); this._dispatcher.timeout = this._advice.timeout / 1000; if (this._advice.reconnect === this.HANDSHAKE && this._state !== this.DISCONNECTED) { this._state = this.UNCONNECTED; this._dispatcher.clientId = null; this._cycleConnection(); } }, _deliverMessage: function(message) { if (!message.channel || message.data === undefined) return; this.info('Client ? calling listeners for ? with ?', this._dispatcher.clientId, message.channel, message.data); this._channels.distributeMessage(message); }, _cycleConnection: function() { if (this._connectRequest) { this._connectRequest = null; this.info('Closed connection for ?', this._dispatcher.clientId); } var self = this; global.setTimeout(function() { self.connect() }, this._advice.interval); } }); extend(Client.prototype, Deferrable); extend(Client.prototype, Publisher); extend(Client.prototype, Logging); extend(Client.prototype, Extensible); module.exports = Client; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"../mixins/deferrable":6,"../mixins/logging":7,"../mixins/publisher":8,"../util/array":26,"../util/browser":27,"../util/class":28,"../util/constants":29,"../util/extend":33,"../util/promise":34,"../util/uri":37,"../util/validate_options":38,"./channel":10,"./dispatcher":12,"./error":13,"./extensible":14,"./publication":16,"./subscription":18,"asap":3}],12:[function(require,module,exports){ (function (global){ 'use strict'; var Class = require('../util/class'), URI = require('../util/uri'), cookies = require('../util/cookies'), extend = require('../util/extend'), Logging = require('../mixins/logging'), Publisher = require('../mixins/publisher'), Transport = require('../transport'), Scheduler = require('./scheduler'); var Dispatcher = Class({ className: 'Dispatcher', MAX_REQUEST_SIZE: 2048, DEFAULT_RETRY: 5, UP: 1, DOWN: 2, initialize: function(client, endpoint, options) { this._client = client; this.endpoint = URI.parse(endpoint); this._alternates = options.endpoints || {}; this.cookies = cookies.CookieJar && new cookies.CookieJar(); this._disabled = []; this._envelopes = {}; this.headers = {}; this.retry = options.retry || this.DEFAULT_RETRY; this._scheduler = options.scheduler || Scheduler; this._state = 0; this.transports = {}; this.wsExtensions = []; this.proxy = options.proxy || {}; if (typeof this._proxy === 'string') this._proxy = {origin: this._proxy}; var exts = options.websocketExtensions; if (exts) { exts = [].concat(exts); for (var i = 0, n = exts.length; i < n; i++) this.addWebsocketExtension(exts[i]); } this.tls = options.tls || {}; this.tls.ca = this.tls.ca || options.ca; for (var type in this._alternates) this._alternates[type] = URI.parse(this._alternates[type]); this.maxRequestSize = this.MAX_REQUEST_SIZE; }, endpointFor: function(connectionType) { return this._alternates[connectionType] || this.endpoint; }, addWebsocketExtension: function(extension) { this.wsExtensions.push(extension); }, disable: function(feature) { this._disabled.push(feature); }, setHeader: function(name, value) { thi