jsforce2
Version:
Salesforce API Library for JavaScript
1,488 lines (1,262 loc) • 270 kB
JavaScript
(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