diff --git a/assets/scripts/vendor/pusher.js b/assets/scripts/vendor/pusher.js index f90aee04..6bf133ac 100644 --- a/assets/scripts/vendor/pusher.js +++ b/assets/scripts/vendor/pusher.js @@ -1,4009 +1,119 @@ /*! - * Pusher JavaScript Library v2.2.0 - * http://pusherapp.com/ + * Pusher JavaScript Library v2.2.2 + * http://pusher.com/ * - * Copyright 2013, Pusher + * Copyright 2014, Pusher * Released under the MIT licence. */ -;(function() { - function Pusher(app_key, options) { - checkAppKey(app_key); - options = options || {}; - - var self = this; - - this.key = app_key; - this.config = Pusher.Util.extend( - Pusher.getGlobalConfig(), - options.cluster ? Pusher.getClusterConfig(options.cluster) : {}, - options - ); - - this.channels = new Pusher.Channels(); - this.global_emitter = new Pusher.EventsDispatcher(); - this.sessionID = Math.floor(Math.random() * 1000000000); - - this.timeline = new Pusher.Timeline(this.key, this.sessionID, { - cluster: this.config.cluster, - features: Pusher.Util.getClientFeatures(), - params: this.config.timelineParams || {}, - limit: 50, - level: Pusher.Timeline.INFO, - version: Pusher.VERSION - }); - if (!this.config.disableStats) { - this.timelineSender = new Pusher.TimelineSender(this.timeline, { - host: this.config.statsHost, - path: "/timeline/v2/jsonp" - }); - } - - var getStrategy = function(options) { - var config = Pusher.Util.extend({}, self.config, options); - return Pusher.StrategyBuilder.build( - Pusher.getDefaultStrategy(config), config - ); - }; - - this.connection = new Pusher.ConnectionManager( - this.key, - Pusher.Util.extend( - { getStrategy: getStrategy, - timeline: this.timeline, - activityTimeout: this.config.activity_timeout, - pongTimeout: this.config.pong_timeout, - unavailableTimeout: this.config.unavailable_timeout - }, - this.config, - { encrypted: this.isEncrypted() } - ) - ); - - this.connection.bind('connected', function() { - self.subscribeAll(); - if (self.timelineSender) { - self.timelineSender.send(self.connection.isEncrypted()); - } - }); - this.connection.bind('message', function(params) { - var internal = (params.event.indexOf('pusher_internal:') === 0); - if (params.channel) { - var channel = self.channel(params.channel); - if (channel) { - channel.handleEvent(params.event, params.data); - } - } - // Emit globaly [deprecated] - if (!internal) { - self.global_emitter.emit(params.event, params.data); - } - }); - this.connection.bind('disconnected', function() { - self.channels.disconnect(); - }); - this.connection.bind('error', function(err) { - Pusher.warn('Error', err); - }); - - Pusher.instances.push(this); - this.timeline.info({ instances: Pusher.instances.length }); - - if (Pusher.isReady) { - self.connect(); - } - } - var prototype = Pusher.prototype; - - Pusher.instances = []; - Pusher.isReady = false; - - // To receive log output provide a Pusher.log function, for example - // Pusher.log = function(m){console.log(m)} - Pusher.debug = function() { - if (!Pusher.log) { - return; - } - Pusher.log(Pusher.Util.stringify.apply(this, arguments)); - }; - - Pusher.warn = function() { - var message = Pusher.Util.stringify.apply(this, arguments); - if (window.console) { - if (window.console.warn) { - window.console.warn(message); - } else if (window.console.log) { - window.console.log(message); - } - } - if (Pusher.log) { - Pusher.log(message); - } - }; - - Pusher.ready = function() { - Pusher.isReady = true; - for (var i = 0, l = Pusher.instances.length; i < l; i++) { - Pusher.instances[i].connect(); - } - }; - - prototype.channel = function(name) { - return this.channels.find(name); - }; - - prototype.allChannels = function() { - return this.channels.all(); - }; - - prototype.connect = function() { - this.connection.connect(); - - if (this.timelineSender) { - if (!this.timelineSenderTimer) { - var encrypted = this.connection.isEncrypted(); - var timelineSender = this.timelineSender; - this.timelineSenderTimer = new Pusher.PeriodicTimer(60000, function() { - timelineSender.send(encrypted); - }); - } - } - }; - - prototype.disconnect = function() { - this.connection.disconnect(); - - if (this.timelineSenderTimer) { - this.timelineSenderTimer.ensureAborted(); - this.timelineSenderTimer = null; - } - }; - - prototype.bind = function(event_name, callback) { - this.global_emitter.bind(event_name, callback); - return this; - }; - - prototype.bind_all = function(callback) { - this.global_emitter.bind_all(callback); - return this; - }; - - prototype.subscribeAll = function() { - var channelName; - for (channelName in this.channels.channels) { - if (this.channels.channels.hasOwnProperty(channelName)) { - this.subscribe(channelName); - } - } - }; - - prototype.subscribe = function(channel_name) { - var channel = this.channels.add(channel_name, this); - if (this.connection.state === 'connected') { - channel.subscribe(); - } - return channel; - }; - - prototype.unsubscribe = function(channel_name) { - var channel = this.channels.remove(channel_name); - if (this.connection.state === 'connected') { - channel.unsubscribe(); - } - }; - - prototype.send_event = function(event_name, data, channel) { - return this.connection.send_event(event_name, data, channel); - }; - - prototype.isEncrypted = function() { - if (Pusher.Util.getDocument().location.protocol === "https:") { - return true; - } else { - return Boolean(this.config.encrypted); - } - }; - - function checkAppKey(key) { - if (key === null || key === undefined) { - Pusher.warn( - 'Warning', 'You must pass your app key when you instantiate Pusher.' - ); - } - } - - Pusher.HTTP = {}; - - this.Pusher = Pusher; -}).call(this); - -;(function() { - // We need to bind clear functions this way to avoid exceptions on IE8 - function clearTimeout(timer) { - window.clearTimeout(timer); - } - function clearInterval(timer) { - window.clearInterval(timer); - } - - function GenericTimer(set, clear, delay, callback) { - var self = this; - - this.clear = clear; - this.timer = set(function() { - if (self.timer !== null) { - self.timer = callback(self.timer); - } - }, delay); - } - var prototype = GenericTimer.prototype; - - /** Returns whether the timer is still running. - * - * @return {Boolean} - */ - prototype.isRunning = function() { - return this.timer !== null; - }; - - /** Aborts a timer when it's running. */ - prototype.ensureAborted = function() { - if (this.timer) { - // Clear function is already bound - this.clear(this.timer); - this.timer = null; - } - }; - - /** Cross-browser compatible one-off timer abstraction. - * - * @param {Number} delay - * @param {Function} callback - */ - Pusher.Timer = function(delay, callback) { - return new GenericTimer(setTimeout, clearTimeout, delay, function(timer) { - callback(); - return null; - }); - }; - /** Cross-browser compatible periodic timer abstraction. - * - * @param {Number} delay - * @param {Function} callback - */ - Pusher.PeriodicTimer = function(delay, callback) { - return new GenericTimer(setInterval, clearInterval, delay, function(timer) { - callback(); - return timer; - }); - }; -}).call(this); - -;(function() { - Pusher.Util = { - now: function() { - if (Date.now) { - return Date.now(); - } else { - return new Date().valueOf(); - } - }, - - defer: function(callback) { - return new Pusher.Timer(0, callback); - }, - - /** Merges multiple objects into the target argument. - * - * For properties that are plain Objects, performs a deep-merge. For the - * rest it just copies the value of the property. - * - * To extend prototypes use it as following: - * Pusher.Util.extend(Target.prototype, Base.prototype) - * - * You can also use it to merge objects without altering them: - * Pusher.Util.extend({}, object1, object2) - * - * @param {Object} target - * @return {Object} the target argument - */ - extend: function(target) { - for (var i = 1; i < arguments.length; i++) { - var extensions = arguments[i]; - for (var property in extensions) { - if (extensions[property] && extensions[property].constructor && - extensions[property].constructor === Object) { - target[property] = Pusher.Util.extend( - target[property] || {}, extensions[property] - ); - } else { - target[property] = extensions[property]; - } - } - } - return target; - }, - - stringify: function() { - var m = ["Pusher"]; - for (var i = 0; i < arguments.length; i++) { - if (typeof arguments[i] === "string") { - m.push(arguments[i]); - } else { - if (window.JSON === undefined) { - m.push(arguments[i].toString()); - } else { - m.push(JSON.stringify(arguments[i])); - } - } - } - return m.join(" : "); - }, - - arrayIndexOf: function(array, item) { // MSIE doesn't have array.indexOf - var nativeIndexOf = Array.prototype.indexOf; - if (array === null) { - return -1; - } - if (nativeIndexOf && array.indexOf === nativeIndexOf) { - return array.indexOf(item); - } - for (var i = 0, l = array.length; i < l; i++) { - if (array[i] === item) { - return i; - } - } - return -1; - }, - - /** Applies a function f to all properties of an object. - * - * Function f gets 3 arguments passed: - * - element from the object - * - key of the element - * - reference to the object - * - * @param {Object} object - * @param {Function} f - */ - objectApply: function(object, f) { - for (var key in object) { - if (Object.prototype.hasOwnProperty.call(object, key)) { - f(object[key], key, object); - } - } - }, - - /** Return a list of object's own property keys - * - * @param {Object} object - * @returns {Array} - */ - keys: function(object) { - var keys = []; - Pusher.Util.objectApply(object, function(_, key) { - keys.push(key); - }); - return keys; - }, - - /** Return a list of object's own property values - * - * @param {Object} object - * @returns {Array} - */ - values: function(object) { - var values = []; - Pusher.Util.objectApply(object, function(value) { - values.push(value); - }); - return values; - }, - - /** Applies a function f to all elements of an array. - * - * Function f gets 3 arguments passed: - * - element from the array - * - index of the element - * - reference to the array - * - * @param {Array} array - * @param {Function} f - */ - apply: function(array, f, context) { - for (var i = 0; i < array.length; i++) { - f.call(context || window, array[i], i, array); - } - }, - - /** Maps all elements of the array and returns the result. - * - * Function f gets 4 arguments passed: - * - element from the array - * - index of the element - * - reference to the source array - * - reference to the destination array - * - * @param {Array} array - * @param {Function} f - */ - map: function(array, f) { - var result = []; - for (var i = 0; i < array.length; i++) { - result.push(f(array[i], i, array, result)); - } - return result; - }, - - /** Maps all elements of the object and returns the result. - * - * Function f gets 4 arguments passed: - * - element from the object - * - key of the element - * - reference to the source object - * - reference to the destination object - * - * @param {Object} object - * @param {Function} f - */ - mapObject: function(object, f) { - var result = {}; - Pusher.Util.objectApply(object, function(value, key) { - result[key] = f(value); - }); - return result; - }, - - /** Filters elements of the array using a test function. - * - * Function test gets 4 arguments passed: - * - element from the array - * - index of the element - * - reference to the source array - * - reference to the destination array - * - * @param {Array} array - * @param {Function} f - */ - filter: function(array, test) { - test = test || function(value) { return !!value; }; - - var result = []; - for (var i = 0; i < array.length; i++) { - if (test(array[i], i, array, result)) { - result.push(array[i]); - } - } - return result; - }, - - /** Filters properties of the object using a test function. - * - * Function test gets 4 arguments passed: - * - element from the object - * - key of the element - * - reference to the source object - * - reference to the destination object - * - * @param {Object} object - * @param {Function} f - */ - filterObject: function(object, test) { - var result = {}; - Pusher.Util.objectApply(object, function(value, key) { - if ((test && test(value, key, object, result)) || Boolean(value)) { - result[key] = value; - } - }); - return result; - }, - - /** Flattens an object into a two-dimensional array. - * - * @param {Object} object - * @return {Array} resulting array of [key, value] pairs - */ - flatten: function(object) { - var result = []; - Pusher.Util.objectApply(object, function(value, key) { - result.push([key, value]); - }); - return result; - }, - - /** Checks whether any element of the array passes the test. - * - * Function test gets 3 arguments passed: - * - element from the array - * - index of the element - * - reference to the source array - * - * @param {Array} array - * @param {Function} f - */ - any: function(array, test) { - for (var i = 0; i < array.length; i++) { - if (test(array[i], i, array)) { - return true; - } - } - return false; - }, - - /** Checks whether all elements of the array pass the test. - * - * Function test gets 3 arguments passed: - * - element from the array - * - index of the element - * - reference to the source array - * - * @param {Array} array - * @param {Function} f - */ - all: function(array, test) { - for (var i = 0; i < array.length; i++) { - if (!test(array[i], i, array)) { - return false; - } - } - return true; - }, - - /** Builds a function that will proxy a method call to its first argument. - * - * Allows partial application of arguments, so additional arguments are - * prepended to the argument list. - * - * @param {String} name method name - * @return {Function} proxy function - */ - method: function(name) { - var boundArguments = Array.prototype.slice.call(arguments, 1); - return function(object) { - return object[name].apply(object, boundArguments.concat(arguments)); - }; - }, - - getWindow: function() { - return window; - }, - - getDocument: function() { - return document; - }, - - getNavigator: function() { - return navigator; - }, - - getLocalStorage: function() { - try { - return window.localStorage; - } catch (e) { - return undefined; - } - }, - - getClientFeatures: function() { - return Pusher.Util.keys( - Pusher.Util.filterObject( - { "ws": Pusher.WSTransport, "flash": Pusher.FlashTransport }, - function (t) { return t.isSupported({}); } - ) - ); - }, - - addWindowListener: function(event, listener) { - var _window = Pusher.Util.getWindow(); - if (_window.addEventListener !== undefined) { - _window.addEventListener(event, listener, false); - } else { - _window.attachEvent("on" + event, listener); - } - }, - - removeWindowListener: function(event, listener) { - var _window = Pusher.Util.getWindow(); - if (_window.addEventListener !== undefined) { - _window.removeEventListener(event, listener, false); - } else { - _window.detachEvent("on" + event, listener); - } - }, - - isXHRSupported: function() { - var XHR = window.XMLHttpRequest; - return Boolean(XHR) && (new XHR()).withCredentials !== undefined; - }, - - isXDRSupported: function(encrypted) { - var protocol = encrypted ? "https:" : "http:"; - var documentProtocol = Pusher.Util.getDocument().location.protocol; - return Boolean(window.XDomainRequest) && documentProtocol === protocol; - } - }; -}).call(this); - -;(function() { - Pusher.VERSION = '2.2.0'; - Pusher.PROTOCOL = 7; - - // DEPRECATED: WS connection parameters - Pusher.host = 'ws.pusherapp.com'; - Pusher.ws_port = 80; - Pusher.wss_port = 443; - // DEPRECATED: SockJS fallback parameters - Pusher.sockjs_host = 'sockjs.pusher.com'; - Pusher.sockjs_http_port = 80; - Pusher.sockjs_https_port = 443; - Pusher.sockjs_path = "/pusher"; - // DEPRECATED: Stats - Pusher.stats_host = 'stats.pusher.com'; - // DEPRECATED: Other settings - Pusher.channel_auth_endpoint = '/pusher/auth'; - Pusher.channel_auth_transport = 'ajax'; - Pusher.activity_timeout = 120000; - Pusher.pong_timeout = 30000; - Pusher.unavailable_timeout = 10000; - // CDN configuration - Pusher.cdn_http = 'http://js.pusher.com/'; - Pusher.cdn_https = 'https://d3dy5gmtp8yhk7.cloudfront.net/'; - Pusher.dependency_suffix = ''; - - Pusher.getDefaultStrategy = function(config) { - var wsStrategy; - if (config.encrypted) { - wsStrategy = [ - ":best_connected_ever", - ":ws_loop", - [":delayed", 2000, [":http_fallback_loop"]] - ]; - } else { - wsStrategy = [ - ":best_connected_ever", - ":ws_loop", - [":delayed", 2000, [":wss_loop"]], - [":delayed", 5000, [":http_fallback_loop"]] - ]; - } - - return [ - [":def", "ws_options", { - hostUnencrypted: config.wsHost + ":" + config.wsPort, - hostEncrypted: config.wsHost + ":" + config.wssPort - }], - [":def", "wss_options", [":extend", ":ws_options", { - encrypted: true - }]], - [":def", "sockjs_options", { - hostUnencrypted: config.httpHost + ":" + config.httpPort, - hostEncrypted: config.httpHost + ":" + config.httpsPort - }], - [":def", "timeouts", { - loop: true, - timeout: 15000, - timeoutLimit: 60000 - }], - - [":def", "ws_manager", [":transport_manager", { - lives: 2, - minPingDelay: 10000, - maxPingDelay: config.activity_timeout - }]], - [":def", "streaming_manager", [":transport_manager", { - lives: 2, - minPingDelay: 10000, - maxPingDelay: config.activity_timeout - }]], - - [":def_transport", "ws", "ws", 3, ":ws_options", ":ws_manager"], - [":def_transport", "wss", "ws", 3, ":wss_options", ":ws_manager"], - [":def_transport", "flash", "flash", 2, ":ws_options", ":ws_manager"], - [":def_transport", "sockjs", "sockjs", 1, ":sockjs_options"], - [":def_transport", "xhr_streaming", "xhr_streaming", 1, ":sockjs_options", ":streaming_manager"], - [":def_transport", "xdr_streaming", "xdr_streaming", 1, ":sockjs_options", ":streaming_manager"], - [":def_transport", "xhr_polling", "xhr_polling", 1, ":sockjs_options"], - [":def_transport", "xdr_polling", "xdr_polling", 1, ":sockjs_options"], - - [":def", "ws_loop", [":sequential", ":timeouts", ":ws"]], - [":def", "wss_loop", [":sequential", ":timeouts", ":wss"]], - [":def", "flash_loop", [":sequential", ":timeouts", ":flash"]], - [":def", "sockjs_loop", [":sequential", ":timeouts", ":sockjs"]], - - [":def", "streaming_loop", [":sequential", ":timeouts", - [":if", [":is_supported", ":xhr_streaming"], - ":xhr_streaming", - ":xdr_streaming" - ] - ]], - [":def", "polling_loop", [":sequential", ":timeouts", - [":if", [":is_supported", ":xhr_polling"], - ":xhr_polling", - ":xdr_polling" - ] - ]], - - [":def", "http_loop", [":if", [":is_supported", ":streaming_loop"], [ - ":best_connected_ever", - ":streaming_loop", - [":delayed", 4000, [":polling_loop"]] - ], [ - ":polling_loop" - ]]], - - [":def", "http_fallback_loop", - [":if", [":is_supported", ":http_loop"], [ - ":http_loop" - ], [ - ":sockjs_loop" - ]] - ], - - [":def", "strategy", - [":cached", 1800000, - [":first_connected", - [":if", [":is_supported", ":ws"], - wsStrategy, - [":if", [":is_supported", ":flash"], [ - ":best_connected_ever", - ":flash_loop", - [":delayed", 2000, [":http_fallback_loop"]] - ], [ - ":http_fallback_loop" - ]]] - ] - ] - ] - ]; - }; -}).call(this); - -;(function() { - Pusher.getGlobalConfig = function() { - return { - wsHost: Pusher.host, - wsPort: Pusher.ws_port, - wssPort: Pusher.wss_port, - httpHost: Pusher.sockjs_host, - httpPort: Pusher.sockjs_http_port, - httpsPort: Pusher.sockjs_https_port, - httpPath: Pusher.sockjs_path, - statsHost: Pusher.stats_host, - authEndpoint: Pusher.channel_auth_endpoint, - authTransport: Pusher.channel_auth_transport, - // TODO make this consistent with other options in next major version - activity_timeout: Pusher.activity_timeout, - pong_timeout: Pusher.pong_timeout, - unavailable_timeout: Pusher.unavailable_timeout - }; - }; - - Pusher.getClusterConfig = function(clusterName) { - return { - wsHost: "ws-" + clusterName + ".pusher.com", - httpHost: "sockjs-" + clusterName + ".pusher.com" - }; - }; -}).call(this); - -;(function() { - function buildExceptionClass(name) { - var constructor = function(message) { - Error.call(this, message); - this.name = name; - }; - Pusher.Util.extend(constructor.prototype, Error.prototype); - - return constructor; - } - - /** Error classes used throughout pusher-js library. */ - Pusher.Errors = { - BadEventName: buildExceptionClass("BadEventName"), - RequestTimedOut: buildExceptionClass("RequestTimedOut"), - TransportPriorityTooLow: buildExceptionClass("TransportPriorityTooLow"), - TransportClosed: buildExceptionClass("TransportClosed"), - UnsupportedTransport: buildExceptionClass("UnsupportedTransport"), - UnsupportedStrategy: buildExceptionClass("UnsupportedStrategy") - }; -}).call(this); - -;(function() { - /** Manages callback bindings and event emitting. - * - * @param Function failThrough called when no listeners are bound to an event - */ - function EventsDispatcher(failThrough) { - this.callbacks = new CallbackRegistry(); - this.global_callbacks = []; - this.failThrough = failThrough; - } - var prototype = EventsDispatcher.prototype; - - prototype.bind = function(eventName, callback, context) { - this.callbacks.add(eventName, callback, context); - return this; - }; - - prototype.bind_all = function(callback) { - this.global_callbacks.push(callback); - return this; - }; - - prototype.unbind = function(eventName, callback, context) { - this.callbacks.remove(eventName, callback, context); - return this; - }; - - prototype.unbind_all = function(eventName, callback) { - this.callbacks.remove(eventName, callback); - return this; - }; - - prototype.emit = function(eventName, data) { - var i; - - for (i = 0; i < this.global_callbacks.length; i++) { - this.global_callbacks[i](eventName, data); - } - - var callbacks = this.callbacks.get(eventName); - if (callbacks && callbacks.length > 0) { - for (i = 0; i < callbacks.length; i++) { - callbacks[i].fn.call(callbacks[i].context || window, data); - } - } else if (this.failThrough) { - this.failThrough(eventName, data); - } - - return this; - }; - - /** Callback registry helper. */ - - function CallbackRegistry() { - this._callbacks = {}; - } - - CallbackRegistry.prototype.get = function(name) { - return this._callbacks[prefix(name)]; - }; - - CallbackRegistry.prototype.add = function(name, callback, context) { - var prefixedEventName = prefix(name); - this._callbacks[prefixedEventName] = this._callbacks[prefixedEventName] || []; - this._callbacks[prefixedEventName].push({ - fn: callback, - context: context - }); - }; - - CallbackRegistry.prototype.remove = function(name, callback, context) { - if (!name && !callback && !context) { - this._callbacks = {}; - return; - } - - var names = name ? [prefix(name)] : Pusher.Util.keys(this._callbacks); - - if (callback || context) { - Pusher.Util.apply(names, function(name) { - this._callbacks[name] = Pusher.Util.filter( - this._callbacks[name] || [], - function(binding) { - return (callback && callback !== binding.fn) || - (context && context !== binding.context); - } - ); - if (this._callbacks[name].length === 0) { - delete this._callbacks[name]; - } - }, this); - } else { - Pusher.Util.apply(names, function(name) { - delete this._callbacks[name]; - }, this); - } - }; - - function prefix(name) { - return "_" + name; - } - - Pusher.EventsDispatcher = EventsDispatcher; -}).call(this); - -(function() { - /** Builds receivers for JSONP and Script requests. - * - * Each receiver is an object with following fields: - * - number - unique (for the factory instance), numerical id of the receiver - * - id - a string ID that can be used in DOM attributes - * - name - name of the function triggering the receiver - * - callback - callback function - * - * Receivers are triggered only once, on the first callback call. - * - * Receivers can be called by their name or by accessing factory object - * by the number key. - * - * @param {String} prefix the prefix used in ids - * @param {String} name the name of the object - */ - function ScriptReceiverFactory(prefix, name) { - this.lastId = 0; - this.prefix = prefix; - this.name = name; - } - var prototype = ScriptReceiverFactory.prototype; - - /** Creates a script receiver. - * - * @param {Function} callback - * @return {ScriptReceiver} - */ - prototype.create = function(callback) { - this.lastId++; - - var number = this.lastId; - var id = this.prefix + number; - var name = this.name + "[" + number + "]"; - - var called = false; - var callbackWrapper = function() { - if (!called) { - callback.apply(null, arguments); - called = true; - } - }; - - this[number] = callbackWrapper; - return { number: number, id: id, name: name, callback: callbackWrapper }; - }; - - /** Removes the script receiver from the list. - * - * @param {ScriptReceiver} receiver - */ - prototype.remove = function(receiver) { - delete this[receiver.number]; - }; - - Pusher.ScriptReceiverFactory = ScriptReceiverFactory; - Pusher.ScriptReceivers = new ScriptReceiverFactory( - "_pusher_script_", "Pusher.ScriptReceivers" - ); -}).call(this); - -(function() { - /** Sends a generic HTTP GET request using a script tag. - * - * By constructing URL in a specific way, it can be used for loading - * JavaScript resources or JSONP requests. It can notify about errors, but - * only in certain environments. Please take care of monitoring the state of - * the request yourself. - * - * @param {String} src - */ - function ScriptRequest(src) { - this.src = src; - } - var prototype = ScriptRequest.prototype; - - /** Sends the actual script request. - * - * @param {ScriptReceiver} receiver - */ - prototype.send = function(receiver) { - var self = this; - var errorString = "Error loading " + self.src; - - self.script = document.createElement("script"); - self.script.id = receiver.id; - self.script.src = self.src; - self.script.type = "text/javascript"; - self.script.charset = "UTF-8"; - - if (self.script.addEventListener) { - self.script.onerror = function() { - receiver.callback(errorString); - }; - self.script.onload = function() { - receiver.callback(null); - }; - } else { - self.script.onreadystatechange = function() { - if (self.script.readyState === 'loaded' || - self.script.readyState === 'complete') { - receiver.callback(null); - } - }; - } - - // Opera<11.6 hack for missing onerror callback - if (self.script.async === undefined && document.attachEvent && - /opera/i.test(navigator.userAgent)) { - self.errorScript = document.createElement("script"); - self.errorScript.id = receiver.id + "_error"; - self.errorScript.text = receiver.name + "('" + errorString + "');"; - self.script.async = self.errorScript.async = false; - } else { - self.script.async = true; - } - - var head = document.getElementsByTagName('head')[0]; - head.insertBefore(self.script, head.firstChild); - if (self.errorScript) { - head.insertBefore(self.errorScript, self.script.nextSibling); - } - }; - - /** Cleans up the DOM remains of the script request. */ - prototype.cleanup = function() { - if (this.script) { - this.script.onload = this.script.onerror = null; - this.script.onreadystatechange = null; - } - if (this.script && this.script.parentNode) { - this.script.parentNode.removeChild(this.script); - } - if (this.errorScript && this.errorScript.parentNode) { - this.errorScript.parentNode.removeChild(this.errorScript); - } - this.script = null; - this.errorScript = null; - }; - - Pusher.ScriptRequest = ScriptRequest; -}).call(this); - -;(function() { - /** Handles loading dependency files. - * - * Dependency loaders don't remember whether a resource has been loaded or - * not. It is caller's responsibility to make sure the resource is not loaded - * twice. This is because it's impossible to detect resource loading status - * without knowing its content. - * - * Options: - * - cdn_http - url to HTTP CND - * - cdn_https - url to HTTPS CDN - * - version - version of pusher-js - * - suffix - suffix appended to all names of dependency files - * - * @param {Object} options - */ - function DependencyLoader(options) { - this.options = options; - this.receivers = options.receivers || Pusher.ScriptReceivers; - this.loading = {}; - } - var prototype = DependencyLoader.prototype; - - /** Loads the dependency from CDN. - * - * @param {String} name - * @param {Function} callback - */ - prototype.load = function(name, callback) { - var self = this; - - if (self.loading[name] && self.loading[name].length > 0) { - self.loading[name].push(callback); - } else { - self.loading[name] = [callback]; - - var request = new Pusher.ScriptRequest(self.getPath(name)); - var receiver = self.receivers.create(function(error) { - self.receivers.remove(receiver); - - if (self.loading[name]) { - var callbacks = self.loading[name]; - delete self.loading[name]; - - var successCallback = function(wasSuccessful) { - if (!wasSuccessful) { - request.cleanup(); - } - }; - for (var i = 0; i < callbacks.length; i++) { - callbacks[i](error, successCallback); - } - } - }); - request.send(receiver); - } - }; - - /** Returns a root URL for pusher-js CDN. - * - * @returns {String} - */ - prototype.getRoot = function(options) { - var cdn; - var protocol = Pusher.Util.getDocument().location.protocol; - if ((options && options.encrypted) || protocol === "https:") { - cdn = this.options.cdn_https; - } else { - cdn = this.options.cdn_http; - } - // make sure there are no double slashes - return cdn.replace(/\/*$/, "") + "/" + this.options.version; - }; - - /** Returns a full path to a dependency file. - * - * @param {String} name - * @returns {String} - */ - prototype.getPath = function(name, options) { - return this.getRoot(options) + '/' + name + this.options.suffix + '.js'; - }; - - Pusher.DependencyLoader = DependencyLoader; -}).call(this); - -;(function() { - Pusher.DependenciesReceivers = new Pusher.ScriptReceiverFactory( - "_pusher_dependencies", "Pusher.DependenciesReceivers" - ); - Pusher.Dependencies = new Pusher.DependencyLoader({ - cdn_http: Pusher.cdn_http, - cdn_https: Pusher.cdn_https, - version: Pusher.VERSION, - suffix: Pusher.dependency_suffix, - receivers: Pusher.DependenciesReceivers - }); - - function initialize() { - Pusher.ready(); - } - - // Allows calling a function when the document body is available - function onDocumentBody(callback) { - if (document.body) { - callback(); - } else { - setTimeout(function() { - onDocumentBody(callback); - }, 0); - } - } - - function initializeOnDocumentBody() { - onDocumentBody(initialize); - } - - if (!window.JSON) { - Pusher.Dependencies.load("json2", initializeOnDocumentBody); - } else { - initializeOnDocumentBody(); - } -})(); - -(function() { - - var Base64 = { - encode: function (s) { - return btoa(utob(s)); - } - }; - - var fromCharCode = String.fromCharCode; - - var b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; - var b64tab = {}; - - for (var i = 0, l = b64chars.length; i < l; i++) { - b64tab[b64chars.charAt(i)] = i; - } - - var cb_utob = function(c) { - var cc = c.charCodeAt(0); - return cc < 0x80 ? c - : cc < 0x800 ? fromCharCode(0xc0 | (cc >>> 6)) + - fromCharCode(0x80 | (cc & 0x3f)) - : fromCharCode(0xe0 | ((cc >>> 12) & 0x0f)) + - fromCharCode(0x80 | ((cc >>> 6) & 0x3f)) + - fromCharCode(0x80 | ( cc & 0x3f)); - }; - - var utob = function(u) { - return u.replace(/[^\x00-\x7F]/g, cb_utob); - }; - - var cb_encode = function(ccc) { - var padlen = [0, 2, 1][ccc.length % 3]; - var ord = ccc.charCodeAt(0) << 16 - | ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8) - | ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)); - var chars = [ - b64chars.charAt( ord >>> 18), - b64chars.charAt((ord >>> 12) & 63), - padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63), - padlen >= 1 ? '=' : b64chars.charAt(ord & 63) - ]; - return chars.join(''); - }; - - var btoa = window.btoa || function(b) { - return b.replace(/[\s\S]{1,3}/g, cb_encode); - }; - - Pusher.Base64 = Base64; - -}).call(this); - -(function() { - /** Sends data via JSONP. - * - * Data is a key-value map. Its values are JSON-encoded and then passed - * through base64. Finally, keys and encoded values are appended to the query - * string. - * - * The class itself does not guarantee raising errors on failures, as it's not - * possible to support such feature on all browsers. Instead, JSONP endpoint - * should call back in a way that's easy to distinguish from browser calls, - * for example by passing a second argument to the receiver. - * - * @param {String} url - * @param {Object} data key-value map of data to be submitted - */ - function JSONPRequest(url, data) { - this.url = url; - this.data = data; - } - var prototype = JSONPRequest.prototype; - - /** Sends the actual JSONP request. - * - * @param {ScriptReceiver} receiver - */ - prototype.send = function(receiver) { - if (this.request) { - return; - } - - var params = Pusher.Util.filterObject(this.data, function(value) { - return value !== undefined; - }); - var query = Pusher.Util.map( - Pusher.Util.flatten(encodeParamsObject(params)), - Pusher.Util.method("join", "=") - ).join("&"); - var url = this.url + "/" + receiver.number + "?" + query; - - this.request = new Pusher.ScriptRequest(url); - this.request.send(receiver); - }; - - /** Cleans up the DOM remains of the JSONP request. */ - prototype.cleanup = function() { - if (this.request) { - this.request.cleanup(); - } - }; - - function encodeParamsObject(data) { - return Pusher.Util.mapObject(data, function(value) { - if (typeof value === "object") { - value = JSON.stringify(value); - } - return encodeURIComponent(Pusher.Base64.encode(value.toString())); - }); - } - - Pusher.JSONPRequest = JSONPRequest; -}).call(this); - -(function() { - function Timeline(key, session, options) { - this.key = key; - this.session = session; - this.events = []; - this.options = options || {}; - this.sent = 0; - this.uniqueID = 0; - } - var prototype = Timeline.prototype; - - // Log levels - Timeline.ERROR = 3; - Timeline.INFO = 6; - Timeline.DEBUG = 7; - - prototype.log = function(level, event) { - if (level <= this.options.level) { - this.events.push( - Pusher.Util.extend({}, event, { timestamp: Pusher.Util.now() }) - ); - if (this.options.limit && this.events.length > this.options.limit) { - this.events.shift(); - } - } - }; - - prototype.error = function(event) { - this.log(Timeline.ERROR, event); - }; - - prototype.info = function(event) { - this.log(Timeline.INFO, event); - }; - - prototype.debug = function(event) { - this.log(Timeline.DEBUG, event); - }; - - prototype.isEmpty = function() { - return this.events.length === 0; - }; - - prototype.send = function(sendJSONP, callback) { - var self = this; - - var data = Pusher.Util.extend({ - session: self.session, - bundle: self.sent + 1, - key: self.key, - lib: "js", - version: self.options.version, - cluster: self.options.cluster, - features: self.options.features, - timeline: self.events - }, self.options.params); - - self.events = []; - sendJSONP(data, function(error, result) { - if (!error) { - self.sent++; - } - if (callback) { - callback(error, result); - } - }); - - return true; - }; - - prototype.generateUniqueID = function() { - this.uniqueID++; - return this.uniqueID; - }; - - Pusher.Timeline = Timeline; -}).call(this); - -(function() { - function TimelineSender(timeline, options) { - this.timeline = timeline; - this.options = options || {}; - } - var prototype = TimelineSender.prototype; - - prototype.send = function(encrypted, callback) { - var self = this; - - if (self.timeline.isEmpty()) { - return; - } - - var sendJSONP = function(data, callback) { - var scheme = "http" + (encrypted ? "s" : "") + "://"; - var url = scheme + (self.host || self.options.host) + self.options.path; - var request = new Pusher.JSONPRequest(url, data); - - var receiver = Pusher.ScriptReceivers.create(function(error, result) { - Pusher.ScriptReceivers.remove(receiver); - request.cleanup(); - - if (result && result.host) { - self.host = result.host; - } - if (callback) { - callback(error, result); - } - }); - request.send(receiver); - }; - self.timeline.send(sendJSONP, callback); - }; - - Pusher.TimelineSender = TimelineSender; -}).call(this); - -;(function() { - /** Launches all substrategies and emits prioritized connected transports. - * - * @param {Array} strategies - */ - function BestConnectedEverStrategy(strategies) { - this.strategies = strategies; - } - var prototype = BestConnectedEverStrategy.prototype; - - prototype.isSupported = function() { - return Pusher.Util.any(this.strategies, Pusher.Util.method("isSupported")); - }; - - prototype.connect = function(minPriority, callback) { - return connect(this.strategies, minPriority, function(i, runners) { - return function(error, handshake) { - runners[i].error = error; - if (error) { - if (allRunnersFailed(runners)) { - callback(true); - } - return; - } - Pusher.Util.apply(runners, function(runner) { - runner.forceMinPriority(handshake.transport.priority); - }); - callback(null, handshake); - }; - }); - }; - - /** Connects to all strategies in parallel. - * - * Callback builder should be a function that takes two arguments: index - * and a list of runners. It should return another function that will be - * passed to the substrategy with given index. Runners can be aborted using - * abortRunner(s) functions from this class. - * - * @param {Array} strategies - * @param {Function} callbackBuilder - * @return {Object} strategy runner - */ - function connect(strategies, minPriority, callbackBuilder) { - var runners = Pusher.Util.map(strategies, function(strategy, i, _, rs) { - return strategy.connect(minPriority, callbackBuilder(i, rs)); - }); - return { - abort: function() { - Pusher.Util.apply(runners, abortRunner); - }, - forceMinPriority: function(p) { - Pusher.Util.apply(runners, function(runner) { - runner.forceMinPriority(p); - }); - } - }; - } - - function allRunnersFailed(runners) { - return Pusher.Util.all(runners, function(runner) { - return Boolean(runner.error); - }); - } - - function abortRunner(runner) { - if (!runner.error && !runner.aborted) { - runner.abort(); - runner.aborted = true; - } - } - - Pusher.BestConnectedEverStrategy = BestConnectedEverStrategy; -}).call(this); - -;(function() { - /** Caches last successful transport and uses it for following attempts. - * - * @param {Strategy} strategy - * @param {Object} transports - * @param {Object} options - */ - function CachedStrategy(strategy, transports, options) { - this.strategy = strategy; - this.transports = transports; - this.ttl = options.ttl || 1800*1000; - this.encrypted = options.encrypted; - this.timeline = options.timeline; - } - var prototype = CachedStrategy.prototype; - - prototype.isSupported = function() { - return this.strategy.isSupported(); - }; - - prototype.connect = function(minPriority, callback) { - var encrypted = this.encrypted; - var info = fetchTransportCache(encrypted); - - var strategies = [this.strategy]; - if (info && info.timestamp + this.ttl >= Pusher.Util.now()) { - var transport = this.transports[info.transport]; - if (transport) { - this.timeline.info({ - cached: true, - transport: info.transport, - latency: info.latency - }); - strategies.push(new Pusher.SequentialStrategy([transport], { - timeout: info.latency * 2 + 1000, - failFast: true - })); - } - } - - var startTimestamp = Pusher.Util.now(); - var runner = strategies.pop().connect( - minPriority, - function cb(error, handshake) { - if (error) { - flushTransportCache(encrypted); - if (strategies.length > 0) { - startTimestamp = Pusher.Util.now(); - runner = strategies.pop().connect(minPriority, cb); - } else { - callback(error); - } - } else { - storeTransportCache( - encrypted, - handshake.transport.name, - Pusher.Util.now() - startTimestamp - ); - callback(null, handshake); - } - } - ); - - return { - abort: function() { - runner.abort(); - }, - forceMinPriority: function(p) { - minPriority = p; - if (runner) { - runner.forceMinPriority(p); - } - } - }; - }; - - function getTransportCacheKey(encrypted) { - return "pusherTransport" + (encrypted ? "Encrypted" : "Unencrypted"); - } - - function fetchTransportCache(encrypted) { - var storage = Pusher.Util.getLocalStorage(); - if (storage) { - try { - var serializedCache = storage[getTransportCacheKey(encrypted)]; - if (serializedCache) { - return JSON.parse(serializedCache); - } - } catch (e) { - flushTransportCache(encrypted); - } - } - return null; - } - - function storeTransportCache(encrypted, transport, latency) { - var storage = Pusher.Util.getLocalStorage(); - if (storage) { - try { - storage[getTransportCacheKey(encrypted)] = JSON.stringify({ - timestamp: Pusher.Util.now(), - transport: transport, - latency: latency - }); - } catch (e) { - // catch over quota exceptions raised by localStorage - } - } - } - - function flushTransportCache(encrypted) { - var storage = Pusher.Util.getLocalStorage(); - if (storage) { - try { - delete storage[getTransportCacheKey(encrypted)]; - } catch (e) { - // catch exceptions raised by localStorage - } - } - } - - Pusher.CachedStrategy = CachedStrategy; -}).call(this); - -;(function() { - /** Runs substrategy after specified delay. - * - * Options: - * - delay - time in miliseconds to delay the substrategy attempt - * - * @param {Strategy} strategy - * @param {Object} options - */ - function DelayedStrategy(strategy, options) { - this.strategy = strategy; - this.options = { delay: options.delay }; - } - var prototype = DelayedStrategy.prototype; - - prototype.isSupported = function() { - return this.strategy.isSupported(); - }; - - prototype.connect = function(minPriority, callback) { - var strategy = this.strategy; - var runner; - var timer = new Pusher.Timer(this.options.delay, function() { - runner = strategy.connect(minPriority, callback); - }); - - return { - abort: function() { - timer.ensureAborted(); - if (runner) { - runner.abort(); - } - }, - forceMinPriority: function(p) { - minPriority = p; - if (runner) { - runner.forceMinPriority(p); - } - } - }; - }; - - Pusher.DelayedStrategy = DelayedStrategy; -}).call(this); - -;(function() { - /** Launches the substrategy and terminates on the first open connection. - * - * @param {Strategy} strategy - */ - function FirstConnectedStrategy(strategy) { - this.strategy = strategy; - } - var prototype = FirstConnectedStrategy.prototype; - - prototype.isSupported = function() { - return this.strategy.isSupported(); - }; - - prototype.connect = function(minPriority, callback) { - var runner = this.strategy.connect( - minPriority, - function(error, handshake) { - if (handshake) { - runner.abort(); - } - callback(error, handshake); - } - ); - return runner; - }; - - Pusher.FirstConnectedStrategy = FirstConnectedStrategy; -}).call(this); - -;(function() { - /** Proxies method calls to one of substrategies basing on the test function. - * - * @param {Function} test - * @param {Strategy} trueBranch strategy used when test returns true - * @param {Strategy} falseBranch strategy used when test returns false - */ - function IfStrategy(test, trueBranch, falseBranch) { - this.test = test; - this.trueBranch = trueBranch; - this.falseBranch = falseBranch; - } - var prototype = IfStrategy.prototype; - - prototype.isSupported = function() { - var branch = this.test() ? this.trueBranch : this.falseBranch; - return branch.isSupported(); - }; - - prototype.connect = function(minPriority, callback) { - var branch = this.test() ? this.trueBranch : this.falseBranch; - return branch.connect(minPriority, callback); - }; - - Pusher.IfStrategy = IfStrategy; -}).call(this); - -;(function() { - /** Loops through strategies with optional timeouts. - * - * Options: - * - loop - whether it should loop through the substrategy list - * - timeout - initial timeout for a single substrategy - * - timeoutLimit - maximum timeout - * - * @param {Strategy[]} strategies - * @param {Object} options - */ - function SequentialStrategy(strategies, options) { - this.strategies = strategies; - this.loop = Boolean(options.loop); - this.failFast = Boolean(options.failFast); - this.timeout = options.timeout; - this.timeoutLimit = options.timeoutLimit; - } - var prototype = SequentialStrategy.prototype; - - prototype.isSupported = function() { - return Pusher.Util.any(this.strategies, Pusher.Util.method("isSupported")); - }; - - prototype.connect = function(minPriority, callback) { - var self = this; - - var strategies = this.strategies; - var current = 0; - var timeout = this.timeout; - var runner = null; - - var tryNextStrategy = function(error, handshake) { - if (handshake) { - callback(null, handshake); - } else { - current = current + 1; - if (self.loop) { - current = current % strategies.length; - } - - if (current < strategies.length) { - if (timeout) { - timeout = timeout * 2; - if (self.timeoutLimit) { - timeout = Math.min(timeout, self.timeoutLimit); - } - } - runner = self.tryStrategy( - strategies[current], - minPriority, - { timeout: timeout, failFast: self.failFast }, - tryNextStrategy - ); - } else { - callback(true); - } - } - }; - - runner = this.tryStrategy( - strategies[current], - minPriority, - { timeout: timeout, failFast: this.failFast }, - tryNextStrategy - ); - - return { - abort: function() { - runner.abort(); - }, - forceMinPriority: function(p) { - minPriority = p; - if (runner) { - runner.forceMinPriority(p); - } - } - }; - }; - - /** @private */ - prototype.tryStrategy = function(strategy, minPriority, options, callback) { - var timer = null; - var runner = null; - - if (options.timeout > 0) { - timer = new Pusher.Timer(options.timeout, function() { - runner.abort(); - callback(true); - }); - } - - runner = strategy.connect(minPriority, function(error, handshake) { - if (error && timer && timer.isRunning() && !options.failFast) { - // advance to the next strategy after the timeout - return; - } - if (timer) { - timer.ensureAborted(); - } - callback(error, handshake); - }); - - return { - abort: function() { - if (timer) { - timer.ensureAborted(); - } - runner.abort(); - }, - forceMinPriority: function(p) { - runner.forceMinPriority(p); - } - }; - }; - - Pusher.SequentialStrategy = SequentialStrategy; -}).call(this); - -;(function() { - /** Provides a strategy interface for transports. - * - * @param {String} name - * @param {Number} priority - * @param {Class} transport - * @param {Object} options - */ - function TransportStrategy(name, priority, transport, options) { - this.name = name; - this.priority = priority; - this.transport = transport; - this.options = options || {}; - } - var prototype = TransportStrategy.prototype; - - /** Returns whether the transport is supported in the browser. - * - * @returns {Boolean} - */ - prototype.isSupported = function() { - return this.transport.isSupported({ - encrypted: this.options.encrypted - }); - }; - - /** Launches a connection attempt and returns a strategy runner. - * - * @param {Function} callback - * @return {Object} strategy runner - */ - prototype.connect = function(minPriority, callback) { - if (!this.isSupported()) { - return failAttempt(new Pusher.Errors.UnsupportedStrategy(), callback); - } else if (this.priority < minPriority) { - return failAttempt(new Pusher.Errors.TransportPriorityTooLow(), callback); - } - - var self = this; - var connected = false; - - var transport = this.transport.createConnection( - this.name, this.priority, this.options.key, this.options - ); - var handshake = null; - - var onInitialized = function() { - transport.unbind("initialized", onInitialized); - transport.connect(); - }; - var onOpen = function() { - handshake = new Pusher.Handshake(transport, function(result) { - connected = true; - unbindListeners(); - callback(null, result); - }); - }; - var onError = function(error) { - unbindListeners(); - callback(error); - }; - var onClosed = function() { - unbindListeners(); - callback(new Pusher.Errors.TransportClosed(transport)); - }; - - var unbindListeners = function() { - transport.unbind("initialized", onInitialized); - transport.unbind("open", onOpen); - transport.unbind("error", onError); - transport.unbind("closed", onClosed); - }; - - transport.bind("initialized", onInitialized); - transport.bind("open", onOpen); - transport.bind("error", onError); - transport.bind("closed", onClosed); - - // connect will be called automatically after initialization - transport.initialize(); - - return { - abort: function() { - if (connected) { - return; - } - unbindListeners(); - if (handshake) { - handshake.close(); - } else { - transport.close(); - } - }, - forceMinPriority: function(p) { - if (connected) { - return; - } - if (self.priority < p) { - if (handshake) { - handshake.close(); - } else { - transport.close(); - } - } - } - }; - }; - - function failAttempt(error, callback) { - Pusher.Util.defer(function() { - callback(error); - }); - return { - abort: function() {}, - forceMinPriority: function() {} - }; - } - - Pusher.TransportStrategy = TransportStrategy; -}).call(this); - -(function() { - function getGenericURL(baseScheme, params, path) { - var scheme = baseScheme + (params.encrypted ? "s" : ""); - var host = params.encrypted ? params.hostEncrypted : params.hostUnencrypted; - return scheme + "://" + host + path; - } - - function getGenericPath(key, queryString) { - var path = "/app/" + key; - var query = - "?protocol=" + Pusher.PROTOCOL + - "&client=js" + - "&version=" + Pusher.VERSION + - (queryString ? ("&" + queryString) : ""); - return path + query; - } - - /** URL schemes for different transport types. */ - Pusher.URLSchemes = { - /** Standard WebSocket URL scheme. */ - ws: { - getInitial: function(key, params) { - return getGenericURL("ws", params, getGenericPath(key, "flash=false")); - } - }, - /** URL scheme for Flash. Same as WebSocket, but with a flash parameter. */ - flash: { - getInitial: function(key, params) { - return getGenericURL("ws", params, getGenericPath(key, "flash=true")); - } - }, - /** SockJS URL scheme. Supplies the path separately from the initial URL. */ - sockjs: { - getInitial: function(key, params) { - return getGenericURL("http", params, params.httpPath || "/pusher", ""); - }, - getPath: function(key, params) { - return getGenericPath(key); - } - }, - /** URL scheme for HTTP transports. Basically, WS scheme with a prefix. */ - http: { - getInitial: function(key, params) { - var path = (params.httpPath || "/pusher") + getGenericPath(key); - return getGenericURL("http", params, path); - } - } - }; -}).call(this); - -(function() { - /** Provides universal API for transport connections. - * - * Transport connection is a low-level object that wraps a connection method - * and exposes a simple evented interface for the connection state and - * messaging. It does not implement Pusher-specific WebSocket protocol. - * - * Additionally, it fetches resources needed for transport to work and exposes - * an interface for querying transport features. - * - * States: - * - new - initial state after constructing the object - * - initializing - during initialization phase, usually fetching resources - * - intialized - ready to establish a connection - * - connection - when connection is being established - * - open - when connection ready to be used - * - closed - after connection was closed be either side - * - * Emits: - * - error - after the connection raised an error - * - * Options: - * - encrypted - whether connection should use ssl - * - hostEncrypted - host to connect to when connection is encrypted - * - hostUnencrypted - host to connect to when connection is not encrypted - * - * @param {String} key application key - * @param {Object} options - */ - function TransportConnection(hooks, name, priority, key, options) { - Pusher.EventsDispatcher.call(this); - - this.hooks = hooks; - this.name = name; - this.priority = priority; - this.key = key; - this.options = options; - - this.state = "new"; - this.timeline = options.timeline; - this.activityTimeout = options.activityTimeout; - this.id = this.timeline.generateUniqueID(); - } - var prototype = TransportConnection.prototype; - Pusher.Util.extend(prototype, Pusher.EventsDispatcher.prototype); - - /** Checks whether the transport handles activity checks by itself. - * - * @return {Boolean} - */ - prototype.handlesActivityChecks = function() { - return Boolean(this.hooks.handlesActivityChecks); - }; - - /** Checks whether the transport supports the ping/pong API. - * - * @return {Boolean} - */ - prototype.supportsPing = function() { - return Boolean(this.hooks.supportsPing); - }; - - /** Initializes the transport. - * - * Fetches resources if needed and then transitions to initialized. - */ - prototype.initialize = function() { - var self = this; - - self.timeline.info(self.buildTimelineMessage({ - transport: self.name + (self.options.encrypted ? "s" : "") - })); - - if (self.hooks.beforeInitialize) { - self.hooks.beforeInitialize(); - } - - if (self.hooks.isInitialized()) { - self.changeState("initialized"); - } else if (self.hooks.file) { - self.changeState("initializing"); - Pusher.Dependencies.load(self.hooks.file, function(error, callback) { - if (self.hooks.isInitialized()) { - self.changeState("initialized"); - callback(true); - } else { - if (error) { - self.onError(error); - } - self.onClose(); - callback(false); - } - }); - } else { - self.onClose(); - } - }; - - /** Tries to establish a connection. - * - * @returns {Boolean} false if transport is in invalid state - */ - prototype.connect = function() { - var self = this; - - if (self.socket || self.state !== "initialized") { - return false; - } - - var url = self.hooks.urls.getInitial(self.key, self.options); - try { - self.socket = self.hooks.getSocket(url, self.options); - } catch (e) { - Pusher.Util.defer(function() { - self.onError(e); - self.changeState("closed"); - }); - return false; - } - - self.bindListeners(); - - Pusher.debug("Connecting", { transport: self.name, url: url }); - self.changeState("connecting"); - return true; - }; - - /** Closes the connection. - * - * @return {Boolean} true if there was a connection to close - */ - prototype.close = function() { - if (this.socket) { - this.socket.close(); - return true; - } else { - return false; - } - }; - - /** Sends data over the open connection. - * - * @param {String} data - * @return {Boolean} true only when in the "open" state - */ - prototype.send = function(data) { - var self = this; - - if (self.state === "open") { - // Workaround for MobileSafari bug (see https://gist.github.com/2052006) - Pusher.Util.defer(function() { - if (self.socket) { - self.socket.send(data); - } - }); - return true; - } else { - return false; - } - }; - - /** Sends a ping if the connection is open and transport supports it. */ - prototype.ping = function() { - if (this.state === "open" && this.supportsPing()) { - this.socket.ping(); - } - }; - - /** @private */ - prototype.onOpen = function() { - if (this.hooks.beforeOpen) { - this.hooks.beforeOpen( - this.socket, this.hooks.urls.getPath(this.key, this.options) - ); - } - this.changeState("open"); - this.socket.onopen = undefined; - }; - - /** @private */ - prototype.onError = function(error) { - this.emit("error", { type: 'WebSocketError', error: error }); - this.timeline.error(this.buildTimelineMessage({ error: error.toString() })); - }; - - /** @private */ - prototype.onClose = function(closeEvent) { - if (closeEvent) { - this.changeState("closed", { - code: closeEvent.code, - reason: closeEvent.reason, - wasClean: closeEvent.wasClean - }); - } else { - this.changeState("closed"); - } - this.unbindListeners(); - this.socket = undefined; - }; - - /** @private */ - prototype.onMessage = function(message) { - this.emit("message", message); - }; - - /** @private */ - prototype.onActivity = function() { - this.emit("activity"); - }; - - /** @private */ - prototype.bindListeners = function() { - var self = this; - - self.socket.onopen = function() { - self.onOpen(); - }; - self.socket.onerror = function(error) { - self.onError(error); - }; - self.socket.onclose = function(closeEvent) { - self.onClose(closeEvent); - }; - self.socket.onmessage = function(message) { - self.onMessage(message); - }; - - if (self.supportsPing()) { - self.socket.onactivity = function() { self.onActivity(); }; - } - }; - - /** @private */ - prototype.unbindListeners = function() { - if (this.socket) { - this.socket.onopen = undefined; - this.socket.onerror = undefined; - this.socket.onclose = undefined; - this.socket.onmessage = undefined; - if (this.supportsPing()) { - this.socket.onactivity = undefined; - } - } - }; - - /** @private */ - prototype.changeState = function(state, params) { - this.state = state; - this.timeline.info(this.buildTimelineMessage({ - state: state, - params: params - })); - this.emit(state, params); - }; - - /** @private */ - prototype.buildTimelineMessage = function(message) { - return Pusher.Util.extend({ cid: this.id }, message); - }; - - Pusher.TransportConnection = TransportConnection; -}).call(this); - -(function() { - /** Provides interface for transport connection instantiation. - * - * Takes transport-specific hooks as the only argument, which allow checking - * for transport support and creating its connections. - * - * Supported hooks: - * - file - the name of the file to be fetched during initialization - * - urls - URL scheme to be used by transport - * - handlesActivityCheck - true when the transport handles activity checks - * - supportsPing - true when the transport has a ping/activity API - * - isSupported - tells whether the transport is supported in the environment - * - getSocket - creates a WebSocket-compatible transport socket - * - * See transports.js for specific implementations. - * - * @param {Object} hooks object containing all needed transport hooks - */ - function Transport(hooks) { - this.hooks = hooks; - } - var prototype = Transport.prototype; - - /** Returns whether the transport is supported in the environment. - * - * @param {Object} environment the environment details (encryption, settings) - * @returns {Boolean} true when the transport is supported - */ - prototype.isSupported = function(environment) { - return this.hooks.isSupported(environment); - }; - - /** Creates a transport connection. - * - * @param {String} name - * @param {Number} priority - * @param {String} key the application key - * @param {Object} options - * @returns {TransportConnection} - */ - prototype.createConnection = function(name, priority, key, options) { - return new Pusher.TransportConnection( - this.hooks, name, priority, key, options - ); - }; - - Pusher.Transport = Transport; -}).call(this); - -(function() { - /** WebSocket transport. - * - * Uses native WebSocket implementation, including MozWebSocket supported by - * earlier Firefox versions. - */ - Pusher.WSTransport = new Pusher.Transport({ - urls: Pusher.URLSchemes.ws, - handlesActivityChecks: false, - supportsPing: false, - - isInitialized: function() { - return Boolean(window.WebSocket || window.MozWebSocket); - }, - isSupported: function() { - return Boolean(window.WebSocket || window.MozWebSocket); - }, - getSocket: function(url) { - var Constructor = window.WebSocket || window.MozWebSocket; - return new Constructor(url); - } - }); - - /** Flash transport using the WebSocket protocol. */ - Pusher.FlashTransport = new Pusher.Transport({ - file: "flashfallback", - urls: Pusher.URLSchemes.flash, - handlesActivityChecks: false, - supportsPing: false, - - isSupported: function() { - try { - return Boolean(new ActiveXObject('ShockwaveFlash.ShockwaveFlash')); - } catch (e1) { - try { - var nav = Pusher.Util.getNavigator(); - return Boolean( - nav && - nav.mimeTypes && - nav.mimeTypes["application/x-shockwave-flash"] !== undefined - ); - } catch (e2) { - return false; - } - } - }, - beforeInitialize: function() { - if (window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR === undefined) { - window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true; - } - window.WEB_SOCKET_SWF_LOCATION = Pusher.Dependencies.getRoot() + - "/WebSocketMain.swf"; - }, - isInitialized: function() { - return window.FlashWebSocket !== undefined; - }, - getSocket: function(url) { - return new FlashWebSocket(url); - } - }); - - /** SockJS transport. */ - Pusher.SockJSTransport = new Pusher.Transport({ - file: "sockjs", - urls: Pusher.URLSchemes.sockjs, - handlesActivityChecks: true, - supportsPing: false, - - isSupported: function() { - return true; - }, - isInitialized: function() { - return window.SockJS !== undefined; - }, - getSocket: function(url, options) { - return new SockJS(url, null, { - js_path: Pusher.Dependencies.getPath("sockjs", { - encrypted: options.encrypted - }), - ignore_null_origin: options.ignoreNullOrigin - }); - }, - beforeOpen: function(socket, path) { - socket.send(JSON.stringify({ - path: path - })); - } - }); - - var httpConfiguration = { - urls: Pusher.URLSchemes.http, - handlesActivityChecks: false, - supportsPing: true, - isInitialized: function() { - return Boolean(Pusher.HTTP.Socket); - } - }; - - var streamingConfiguration = Pusher.Util.extend( - { getSocket: function(url) { - return Pusher.HTTP.getStreamingSocket(url); - } - }, - httpConfiguration - ); - var pollingConfiguration = Pusher.Util.extend( - { getSocket: function(url) { - return Pusher.HTTP.getPollingSocket(url); - } - }, - httpConfiguration - ); - - var xhrConfiguration = { - file: "xhr", - isSupported: Pusher.Util.isXHRSupported - }; - var xdrConfiguration = { - file: "xdr", - isSupported: function(environment) { - return Pusher.Util.isXDRSupported(environment.encrypted); - } - }; - - /** HTTP streaming transport using CORS-enabled XMLHttpRequest. */ - Pusher.XHRStreamingTransport = new Pusher.Transport( - Pusher.Util.extend({}, streamingConfiguration, xhrConfiguration) - ); - /** HTTP streaming transport using XDomainRequest (IE 8,9). */ - Pusher.XDRStreamingTransport = new Pusher.Transport( - Pusher.Util.extend({}, streamingConfiguration, xdrConfiguration) - ); - /** HTTP long-polling transport using CORS-enabled XMLHttpRequest. */ - Pusher.XHRPollingTransport = new Pusher.Transport( - Pusher.Util.extend({}, pollingConfiguration, xhrConfiguration) - ); - /** HTTP long-polling transport using XDomainRequest (IE 8,9). */ - Pusher.XDRPollingTransport = new Pusher.Transport( - Pusher.Util.extend({}, pollingConfiguration, xdrConfiguration) - ); -}).call(this); - -;(function() { - /** Creates transport connections monitored by a transport manager. - * - * When a transport is closed, it might mean the environment does not support - * it. It's possible that messages get stuck in an intermediate buffer or - * proxies terminate inactive connections. To combat these problems, - * assistants monitor the connection lifetime, report unclean exits and - * adjust ping timeouts to keep the connection active. The decision to disable - * a transport is the manager's responsibility. - * - * @param {TransportManager} manager - * @param {TransportConnection} transport - * @param {Object} options - */ - function AssistantToTheTransportManager(manager, transport, options) { - this.manager = manager; - this.transport = transport; - this.minPingDelay = options.minPingDelay; - this.maxPingDelay = options.maxPingDelay; - this.pingDelay = undefined; - } - var prototype = AssistantToTheTransportManager.prototype; - - /** Creates a transport connection. - * - * This function has the same API as Transport#createConnection. - * - * @param {String} name - * @param {Number} priority - * @param {String} key the application key - * @param {Object} options - * @returns {TransportConnection} - */ - prototype.createConnection = function(name, priority, key, options) { - var self = this; - - options = Pusher.Util.extend({}, options, { - activityTimeout: self.pingDelay - }); - var connection = self.transport.createConnection( - name, priority, key, options - ); - - var openTimestamp = null; - - var onOpen = function() { - connection.unbind("open", onOpen); - connection.bind("closed", onClosed); - openTimestamp = Pusher.Util.now(); - }; - var onClosed = function(closeEvent) { - connection.unbind("closed", onClosed); - - if (closeEvent.code === 1002 || closeEvent.code === 1003) { - // we don't want to use transports not obeying the protocol - self.manager.reportDeath(); - } else if (!closeEvent.wasClean && openTimestamp) { - // report deaths only for short-living transport - var lifespan = Pusher.Util.now() - openTimestamp; - if (lifespan < 2 * self.maxPingDelay) { - self.manager.reportDeath(); - self.pingDelay = Math.max(lifespan / 2, self.minPingDelay); - } - } - }; - - connection.bind("open", onOpen); - return connection; - }; - - /** Returns whether the transport is supported in the environment. - * - * This function has the same API as Transport#isSupported. Might return false - * when the manager decides to kill the transport. - * - * @param {Object} environment the environment details (encryption, settings) - * @returns {Boolean} true when the transport is supported - */ - prototype.isSupported = function(environment) { - return this.manager.isAlive() && this.transport.isSupported(environment); - }; - - Pusher.AssistantToTheTransportManager = AssistantToTheTransportManager; -}).call(this); - -;(function() { - /** Keeps track of the number of lives left for a transport. - * - * In the beginning of a session, transports may be assigned a number of - * lives. When an AssistantToTheTransportManager instance reports a transport - * connection closed uncleanly, the transport loses a life. When the number - * of lives drops to zero, the transport gets disabled by its manager. - * - * @param {Object} options - */ - function TransportManager(options) { - this.options = options || {}; - this.livesLeft = this.options.lives || Infinity; - } - var prototype = TransportManager.prototype; - - /** Creates a assistant for the transport. - * - * @param {Transport} transport - * @returns {AssistantToTheTransportManager} - */ - prototype.getAssistant = function(transport) { - return new Pusher.AssistantToTheTransportManager(this, transport, { - minPingDelay: this.options.minPingDelay, - maxPingDelay: this.options.maxPingDelay - }); - }; - - /** Returns whether the transport has any lives left. - * - * @returns {Boolean} - */ - prototype.isAlive = function() { - return this.livesLeft > 0; - }; - - /** Takes one life from the transport. */ - prototype.reportDeath = function() { - this.livesLeft -= 1; - }; - - Pusher.TransportManager = TransportManager; -}).call(this); - -;(function() { - var StrategyBuilder = { - /** Transforms a JSON scheme to a strategy tree. - * - * @param {Array} scheme JSON strategy scheme - * @param {Object} options a hash of symbols to be included in the scheme - * @returns {Strategy} strategy tree that's represented by the scheme - */ - build: function(scheme, options) { - var context = Pusher.Util.extend({}, globalContext, options); - return evaluate(scheme, context)[1].strategy; - } - }; - - var transports = { - ws: Pusher.WSTransport, - flash: Pusher.FlashTransport, - sockjs: Pusher.SockJSTransport, - xhr_streaming: Pusher.XHRStreamingTransport, - xdr_streaming: Pusher.XDRStreamingTransport, - xhr_polling: Pusher.XHRPollingTransport, - xdr_polling: Pusher.XDRPollingTransport - }; - - var UnsupportedStrategy = { - isSupported: function() { - return false; - }, - connect: function(_, callback) { - var deferred = Pusher.Util.defer(function() { - callback(new Pusher.Errors.UnsupportedStrategy()); - }); - return { - abort: function() { - deferred.ensureAborted(); - }, - forceMinPriority: function() {} - }; - } - }; - - // DSL bindings - - function returnWithOriginalContext(f) { - return function(context) { - return [f.apply(this, arguments), context]; - }; - } - - var globalContext = { - extend: function(context, first, second) { - return [Pusher.Util.extend({}, first, second), context]; - }, - - def: function(context, name, value) { - if (context[name] !== undefined) { - throw "Redefining symbol " + name; - } - context[name] = value; - return [undefined, context]; - }, - - def_transport: function(context, name, type, priority, options, manager) { - var transportClass = transports[type]; - if (!transportClass) { - throw new Pusher.Errors.UnsupportedTransport(type); - } - - var enabled = - (!context.enabledTransports || - Pusher.Util.arrayIndexOf(context.enabledTransports, name) !== -1) && - (!context.disabledTransports || - Pusher.Util.arrayIndexOf(context.disabledTransports, name) === -1) && - (name !== "flash" || context.disableFlash !== true); - - var transport; - if (enabled) { - transport = new Pusher.TransportStrategy( - name, - priority, - manager ? manager.getAssistant(transportClass) : transportClass, - Pusher.Util.extend({ - key: context.key, - encrypted: context.encrypted, - timeline: context.timeline, - ignoreNullOrigin: context.ignoreNullOrigin - }, options) - ); - } else { - transport = UnsupportedStrategy; - } - - var newContext = context.def(context, name, transport)[1]; - newContext.transports = context.transports || {}; - newContext.transports[name] = transport; - return [undefined, newContext]; - }, - - transport_manager: returnWithOriginalContext(function(_, options) { - return new Pusher.TransportManager(options); - }), - - sequential: returnWithOriginalContext(function(_, options) { - var strategies = Array.prototype.slice.call(arguments, 2); - return new Pusher.SequentialStrategy(strategies, options); - }), - - cached: returnWithOriginalContext(function(context, ttl, strategy){ - return new Pusher.CachedStrategy(strategy, context.transports, { - ttl: ttl, - timeline: context.timeline, - encrypted: context.encrypted - }); - }), - - first_connected: returnWithOriginalContext(function(_, strategy) { - return new Pusher.FirstConnectedStrategy(strategy); - }), - - best_connected_ever: returnWithOriginalContext(function() { - var strategies = Array.prototype.slice.call(arguments, 1); - return new Pusher.BestConnectedEverStrategy(strategies); - }), - - delayed: returnWithOriginalContext(function(_, delay, strategy) { - return new Pusher.DelayedStrategy(strategy, { delay: delay }); - }), - - "if": returnWithOriginalContext(function(_, test, trueBranch, falseBranch) { - return new Pusher.IfStrategy(test, trueBranch, falseBranch); - }), - - is_supported: returnWithOriginalContext(function(_, strategy) { - return function() { - return strategy.isSupported(); - }; - }) - }; - - // DSL interpreter - - function isSymbol(expression) { - return (typeof expression === "string") && expression.charAt(0) === ":"; - } - - function getSymbolValue(expression, context) { - return context[expression.slice(1)]; - } - - function evaluateListOfExpressions(expressions, context) { - if (expressions.length === 0) { - return [[], context]; - } - var head = evaluate(expressions[0], context); - var tail = evaluateListOfExpressions(expressions.slice(1), head[1]); - return [[head[0]].concat(tail[0]), tail[1]]; - } - - function evaluateString(expression, context) { - if (!isSymbol(expression)) { - return [expression, context]; - } - var value = getSymbolValue(expression, context); - if (value === undefined) { - throw "Undefined symbol " + expression; - } - return [value, context]; - } - - function evaluateArray(expression, context) { - if (isSymbol(expression[0])) { - var f = getSymbolValue(expression[0], context); - if (expression.length > 1) { - if (typeof f !== "function") { - throw "Calling non-function " + expression[0]; - } - var args = [Pusher.Util.extend({}, context)].concat( - Pusher.Util.map(expression.slice(1), function(arg) { - return evaluate(arg, Pusher.Util.extend({}, context))[0]; - }) - ); - return f.apply(this, args); - } else { - return [f, context]; - } - } else { - return evaluateListOfExpressions(expression, context); - } - } - - function evaluate(expression, context) { - var expressionType = typeof expression; - if (typeof expression === "string") { - return evaluateString(expression, context); - } else if (typeof expression === "object") { - if (expression instanceof Array && expression.length > 0) { - return evaluateArray(expression, context); - } - } - return [expression, context]; - } - - Pusher.StrategyBuilder = StrategyBuilder; -}).call(this); - -;(function() { - /** - * Provides functions for handling Pusher protocol-specific messages. - */ - var Protocol = {}; - - /** - * Decodes a message in a Pusher format. - * - * Throws errors when messages are not parse'able. - * - * @param {Object} message - * @return {Object} - */ - Protocol.decodeMessage = function(message) { - try { - var params = JSON.parse(message.data); - if (typeof params.data === 'string') { - try { - params.data = JSON.parse(params.data); - } catch (e) { - if (!(e instanceof SyntaxError)) { - // TODO looks like unreachable code - // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/JSON/parse - throw e; - } - } - } - return params; - } catch (e) { - throw { type: 'MessageParseError', error: e, data: message.data}; - } - }; - - /** - * Encodes a message to be sent. - * - * @param {Object} message - * @return {String} - */ - Protocol.encodeMessage = function(message) { - return JSON.stringify(message); - }; - - /** Processes a handshake message and returns appropriate actions. - * - * Returns an object with an 'action' and other action-specific properties. - * - * There are three outcomes when calling this function. First is a successful - * connection attempt, when pusher:connection_established is received, which - * results in a 'connected' action with an 'id' property. When passed a - * pusher:error event, it returns a result with action appropriate to the - * close code and an error. Otherwise, it raises an exception. - * - * @param {String} message - * @result Object - */ - Protocol.processHandshake = function(message) { - message = this.decodeMessage(message); - - if (message.event === "pusher:connection_established") { - if (!message.data.activity_timeout) { - throw "No activity timeout specified in handshake"; - } - return { - action: "connected", - id: message.data.socket_id, - activityTimeout: message.data.activity_timeout * 1000 - }; - } else if (message.event === "pusher:error") { - // From protocol 6 close codes are sent only once, so this only - // happens when connection does not support close codes - return { - action: this.getCloseAction(message.data), - error: this.getCloseError(message.data) - }; - } else { - throw "Invalid handshake"; - } - }; - - /** - * Dispatches the close event and returns an appropriate action name. - * - * See: - * 1. https://developer.mozilla.org/en-US/docs/WebSockets/WebSockets_reference/CloseEvent - * 2. http://pusher.com/docs/pusher_protocol - * - * @param {CloseEvent} closeEvent - * @return {String} close action name - */ - Protocol.getCloseAction = function(closeEvent) { - if (closeEvent.code < 4000) { - // ignore 1000 CLOSE_NORMAL, 1001 CLOSE_GOING_AWAY, - // 1005 CLOSE_NO_STATUS, 1006 CLOSE_ABNORMAL - // ignore 1007...3999 - // handle 1002 CLOSE_PROTOCOL_ERROR, 1003 CLOSE_UNSUPPORTED, - // 1004 CLOSE_TOO_LARGE - if (closeEvent.code >= 1002 && closeEvent.code <= 1004) { - return "backoff"; - } else { - return null; - } - } else if (closeEvent.code === 4000) { - return "ssl_only"; - } else if (closeEvent.code < 4100) { - return "refused"; - } else if (closeEvent.code < 4200) { - return "backoff"; - } else if (closeEvent.code < 4300) { - return "retry"; - } else { - // unknown error - return "refused"; - } - }; - - /** - * Returns an error or null basing on the close event. - * - * Null is returned when connection was closed cleanly. Otherwise, an object - * with error details is returned. - * - * @param {CloseEvent} closeEvent - * @return {Object} error object - */ - Protocol.getCloseError = function(closeEvent) { - if (closeEvent.code !== 1000 && closeEvent.code !== 1001) { - return { - type: 'PusherError', - data: { - code: closeEvent.code, - message: closeEvent.reason || closeEvent.message - } - }; - } else { - return null; - } - }; - - Pusher.Protocol = Protocol; -}).call(this); - -;(function() { - /** - * Provides Pusher protocol interface for transports. - * - * Emits following events: - * - message - on received messages - * - ping - on ping requests - * - pong - on pong responses - * - error - when the transport emits an error - * - closed - after closing the transport - * - * It also emits more events when connection closes with a code. - * See Protocol.getCloseAction to get more details. - * - * @param {Number} id - * @param {AbstractTransport} transport - */ - function Connection(id, transport) { - Pusher.EventsDispatcher.call(this); - - this.id = id; - this.transport = transport; - this.activityTimeout = transport.activityTimeout; - this.bindListeners(); - } - var prototype = Connection.prototype; - Pusher.Util.extend(prototype, Pusher.EventsDispatcher.prototype); - - /** Returns whether used transport handles activity checks by itself - * - * @returns {Boolean} true if activity checks are handled by the transport - */ - prototype.handlesActivityChecks = function() { - return this.transport.handlesActivityChecks(); - }; - - /** Sends raw data. - * - * @param {String} data - */ - prototype.send = function(data) { - return this.transport.send(data); - }; - - /** Sends an event. - * - * @param {String} name - * @param {String} data - * @param {String} [channel] - * @returns {Boolean} whether message was sent or not - */ - prototype.send_event = function(name, data, channel) { - var message = { event: name, data: data }; - if (channel) { - message.channel = channel; - } - Pusher.debug('Event sent', message); - return this.send(Pusher.Protocol.encodeMessage(message)); - }; - - /** Sends a ping message to the server. - * - * Basing on the underlying transport, it might send either transport's - * protocol-specific ping or pusher:ping event. - */ - prototype.ping = function() { - if (this.transport.supportsPing()) { - this.transport.ping(); - } else { - this.send_event('pusher:ping', {}); - } - }; - - /** Closes the connection. */ - prototype.close = function() { - this.transport.close(); - }; - - /** @private */ - prototype.bindListeners = function() { - var self = this; - - var listeners = { - message: function(m) { - var message; - try { - message = Pusher.Protocol.decodeMessage(m); - } catch(e) { - self.emit('error', { - type: 'MessageParseError', - error: e, - data: m.data - }); - } - - if (message !== undefined) { - Pusher.debug('Event recd', message); - - switch (message.event) { - case 'pusher:error': - self.emit('error', { type: 'PusherError', data: message.data }); - break; - case 'pusher:ping': - self.emit("ping"); - break; - case 'pusher:pong': - self.emit("pong"); - break; - } - self.emit('message', message); - } - }, - activity: function() { - self.emit("activity"); - }, - error: function(error) { - self.emit("error", { type: "WebSocketError", error: error }); - }, - closed: function(closeEvent) { - unbindListeners(); - - if (closeEvent && closeEvent.code) { - self.handleCloseEvent(closeEvent); - } - - self.transport = null; - self.emit("closed"); - } - }; - - var unbindListeners = function() { - Pusher.Util.objectApply(listeners, function(listener, event) { - self.transport.unbind(event, listener); - }); - }; - - Pusher.Util.objectApply(listeners, function(listener, event) { - self.transport.bind(event, listener); - }); - }; - - /** @private */ - prototype.handleCloseEvent = function(closeEvent) { - var action = Pusher.Protocol.getCloseAction(closeEvent); - var error = Pusher.Protocol.getCloseError(closeEvent); - if (error) { - this.emit('error', error); - } - if (action) { - this.emit(action); - } - }; - - Pusher.Connection = Connection; -}).call(this); - -;(function() { - /** - * Handles Pusher protocol handshakes for transports. - * - * Calls back with a result object after handshake is completed. Results - * always have two fields: - * - action - string describing action to be taken after the handshake - * - transport - the transport object passed to the constructor - * - * Different actions can set different additional properties on the result. - * In the case of 'connected' action, there will be a 'connection' property - * containing a Connection object for the transport. Other actions should - * carry an 'error' property. - * - * @param {AbstractTransport} transport - * @param {Function} callback - */ - function Handshake(transport, callback) { - this.transport = transport; - this.callback = callback; - this.bindListeners(); - } - var prototype = Handshake.prototype; - - prototype.close = function() { - this.unbindListeners(); - this.transport.close(); - }; - - /** @private */ - prototype.bindListeners = function() { - var self = this; - - self.onMessage = function(m) { - self.unbindListeners(); - - try { - var result = Pusher.Protocol.processHandshake(m); - if (result.action === "connected") { - self.finish("connected", { - connection: new Pusher.Connection(result.id, self.transport), - activityTimeout: result.activityTimeout - }); - } else { - self.finish(result.action, { error: result.error }); - self.transport.close(); - } - } catch (e) { - self.finish("error", { error: e }); - self.transport.close(); - } - }; - - self.onClosed = function(closeEvent) { - self.unbindListeners(); - - var action = Pusher.Protocol.getCloseAction(closeEvent) || "backoff"; - var error = Pusher.Protocol.getCloseError(closeEvent); - self.finish(action, { error: error }); - }; - - self.transport.bind("message", self.onMessage); - self.transport.bind("closed", self.onClosed); - }; - - /** @private */ - prototype.unbindListeners = function() { - this.transport.unbind("message", this.onMessage); - this.transport.unbind("closed", this.onClosed); - }; - - /** @private */ - prototype.finish = function(action, params) { - this.callback( - Pusher.Util.extend({ transport: this.transport, action: action }, params) - ); - }; - - Pusher.Handshake = Handshake; -}).call(this); - -;(function() { - /** Manages connection to Pusher. - * - * Uses a strategy (currently only default), timers and network availability - * info to establish a connection and export its state. In case of failures, - * manages reconnection attempts. - * - * Exports state changes as following events: - * - "state_change", { previous: p, current: state } - * - state - * - * States: - * - initialized - initial state, never transitioned to - * - connecting - connection is being established - * - connected - connection has been fully established - * - disconnected - on requested disconnection - * - unavailable - after connection timeout or when there's no network - * - failed - when the connection strategy is not supported - * - * Options: - * - unavailableTimeout - time to transition to unavailable state - * - activityTimeout - time after which ping message should be sent - * - pongTimeout - time for Pusher to respond with pong before reconnecting - * - * @param {String} key application key - * @param {Object} options - */ - function ConnectionManager(key, options) { - Pusher.EventsDispatcher.call(this); - - this.key = key; - this.options = options || {}; - this.state = "initialized"; - this.connection = null; - this.encrypted = !!options.encrypted; - this.timeline = this.options.timeline; - - this.connectionCallbacks = this.buildConnectionCallbacks(); - this.errorCallbacks = this.buildErrorCallbacks(); - this.handshakeCallbacks = this.buildHandshakeCallbacks(this.errorCallbacks); - - var self = this; - - Pusher.Network.bind("online", function() { - self.timeline.info({ netinfo: "online" }); - if (self.state === "connecting" || self.state === "unavailable") { - self.retryIn(0); - } - }); - Pusher.Network.bind("offline", function() { - self.timeline.info({ netinfo: "offline" }); - if (self.state === "connected") { - self.sendActivityCheck(); - } - }); - - this.updateStrategy(); - } - var prototype = ConnectionManager.prototype; - - Pusher.Util.extend(prototype, Pusher.EventsDispatcher.prototype); - - /** Establishes a connection to Pusher. - * - * Does nothing when connection is already established. See top-level doc - * to find events emitted on connection attempts. - */ - prototype.connect = function() { - if (this.connection || this.runner) { - return; - } - if (!this.strategy.isSupported()) { - this.updateState("failed"); - return; - } - this.updateState("connecting"); - this.startConnecting(); - this.setUnavailableTimer(); - }; - - /** Sends raw data. - * - * @param {String} data - */ - prototype.send = function(data) { - if (this.connection) { - return this.connection.send(data); - } else { - return false; - } - }; - - /** Sends an event. - * - * @param {String} name - * @param {String} data - * @param {String} [channel] - * @returns {Boolean} whether message was sent or not - */ - prototype.send_event = function(name, data, channel) { - if (this.connection) { - return this.connection.send_event(name, data, channel); - } else { - return false; - } - }; - - /** Closes the connection. */ - prototype.disconnect = function() { - this.disconnectInternally(); - this.updateState("disconnected"); - }; - - prototype.isEncrypted = function() { - return this.encrypted; - }; - - /** @private */ - prototype.startConnecting = function() { - var self = this; - var callback = function(error, handshake) { - if (error) { - self.runner = self.strategy.connect(0, callback); - } else { - if (handshake.action === "error") { - self.emit("error", { type: "HandshakeError", error: handshake.error }); - self.timeline.error({ handshakeError: handshake.error }); - } else { - self.abortConnecting(); // we don't support switching connections yet - self.handshakeCallbacks[handshake.action](handshake); - } - } - }; - self.runner = self.strategy.connect(0, callback); - }; - - /** @private */ - prototype.abortConnecting = function() { - if (this.runner) { - this.runner.abort(); - this.runner = null; - } - }; - - /** @private */ - prototype.disconnectInternally = function() { - this.abortConnecting(); - this.clearRetryTimer(); - this.clearUnavailableTimer(); - this.stopActivityCheck(); - if (this.connection) { - var connection = this.abandonConnection(); - connection.close(); - } - }; - - /** @private */ - prototype.updateStrategy = function() { - this.strategy = this.options.getStrategy({ - key: this.key, - timeline: this.timeline, - encrypted: this.encrypted - }); - }; - - /** @private */ - prototype.retryIn = function(delay) { - var self = this; - self.timeline.info({ action: "retry", delay: delay }); - if (delay > 0) { - self.emit("connecting_in", Math.round(delay / 1000)); - } - self.retryTimer = new Pusher.Timer(delay || 0, function() { - self.disconnectInternally(); - self.connect(); - }); - }; - - /** @private */ - prototype.clearRetryTimer = function() { - if (this.retryTimer) { - this.retryTimer.ensureAborted(); - this.retryTimer = null; - } - }; - - /** @private */ - prototype.setUnavailableTimer = function() { - var self = this; - self.unavailableTimer = new Pusher.Timer( - self.options.unavailableTimeout, - function() { - self.updateState("unavailable"); - } - ); - }; - - /** @private */ - prototype.clearUnavailableTimer = function() { - if (this.unavailableTimer) { - this.unavailableTimer.ensureAborted(); - } - }; - - /** @private */ - prototype.sendActivityCheck = function() { - var self = this; - self.stopActivityCheck(); - self.connection.ping(); - // wait for pong response - self.activityTimer = new Pusher.Timer( - self.options.pongTimeout, - function() { - self.timeline.error({ pong_timed_out: self.options.pongTimeout }); - self.retryIn(0); - } - ); - }; - - /** @private */ - prototype.resetActivityCheck = function() { - var self = this; - self.stopActivityCheck(); - // send ping after inactivity - if (!self.connection.handlesActivityChecks()) { - self.activityTimer = new Pusher.Timer(self.activityTimeout, function() { - self.sendActivityCheck(); - }); - } - }; - - /** @private */ - prototype.stopActivityCheck = function() { - if (this.activityTimer) { - this.activityTimer.ensureAborted(); - } - }; - - /** @private */ - prototype.buildConnectionCallbacks = function() { - var self = this; - return { - message: function(message) { - // includes pong messages from server - self.resetActivityCheck(); - self.emit('message', message); - }, - ping: function() { - self.send_event('pusher:pong', {}); - }, - activity: function() { - self.resetActivityCheck(); - }, - error: function(error) { - // just emit error to user - socket will already be closed by browser - self.emit("error", { type: "WebSocketError", error: error }); - }, - closed: function() { - self.abandonConnection(); - if (self.shouldRetry()) { - self.retryIn(1000); - } - } - }; - }; - - /** @private */ - prototype.buildHandshakeCallbacks = function(errorCallbacks) { - var self = this; - return Pusher.Util.extend({}, errorCallbacks, { - connected: function(handshake) { - self.activityTimeout = Math.min( - self.options.activityTimeout, - handshake.activityTimeout, - handshake.connection.activityTimeout || Infinity - ); - self.clearUnavailableTimer(); - self.setConnection(handshake.connection); - self.socket_id = self.connection.id; - self.updateState("connected", { socket_id: self.socket_id }); - } - }); - }; - - /** @private */ - prototype.buildErrorCallbacks = function() { - var self = this; - - function withErrorEmitted(callback) { - return function(result) { - if (result.error) { - self.emit("error", { type: "WebSocketError", error: result.error }); - } - callback(result); - }; - } - - return { - ssl_only: withErrorEmitted(function() { - self.encrypted = true; - self.updateStrategy(); - self.retryIn(0); - }), - refused: withErrorEmitted(function() { - self.disconnect(); - }), - backoff: withErrorEmitted(function() { - self.retryIn(1000); - }), - retry: withErrorEmitted(function() { - self.retryIn(0); - }) - }; - }; - - /** @private */ - prototype.setConnection = function(connection) { - this.connection = connection; - for (var event in this.connectionCallbacks) { - this.connection.bind(event, this.connectionCallbacks[event]); - } - this.resetActivityCheck(); - }; - - /** @private */ - prototype.abandonConnection = function() { - if (!this.connection) { - return; - } - for (var event in this.connectionCallbacks) { - this.connection.unbind(event, this.connectionCallbacks[event]); - } - var connection = this.connection; - this.connection = null; - return connection; - }; - - /** @private */ - prototype.updateState = function(newState, data) { - var previousState = this.state; - this.state = newState; - if (previousState !== newState) { - Pusher.debug('State changed', previousState + ' -> ' + newState); - this.timeline.info({ state: newState, params: data }); - this.emit('state_change', { previous: previousState, current: newState }); - this.emit(newState, data); - } - }; - - /** @private */ - prototype.shouldRetry = function() { - return this.state === "connecting" || this.state === "connected"; - }; - - Pusher.ConnectionManager = ConnectionManager; -}).call(this); - -;(function() { - /** Really basic interface providing network availability info. - * - * Emits: - * - online - when browser goes online - * - offline - when browser goes offline - */ - function NetInfo() { - Pusher.EventsDispatcher.call(this); - - var self = this; - // This is okay, as IE doesn't support this stuff anyway. - if (window.addEventListener !== undefined) { - window.addEventListener("online", function() { - self.emit('online'); - }, false); - window.addEventListener("offline", function() { - self.emit('offline'); - }, false); - } - } - Pusher.Util.extend(NetInfo.prototype, Pusher.EventsDispatcher.prototype); - - var prototype = NetInfo.prototype; - - /** Returns whether browser is online or not - * - * Offline means definitely offline (no connection to router). - * Inverse does NOT mean definitely online (only currently supported in Safari - * and even there only means the device has a connection to the router). - * - * @return {Boolean} - */ - prototype.isOnline = function() { - if (window.navigator.onLine === undefined) { - return true; - } else { - return window.navigator.onLine; - } - }; - - Pusher.NetInfo = NetInfo; - Pusher.Network = new NetInfo(); -}).call(this); - -;(function() { - /** Represents a collection of members of a presence channel. */ - function Members() { - this.reset(); - } - var prototype = Members.prototype; - - /** Returns member's info for given id. - * - * Resulting object containts two fields - id and info. - * - * @param {Number} id - * @return {Object} member's info or null - */ - prototype.get = function(id) { - if (Object.prototype.hasOwnProperty.call(this.members, id)) { - return { - id: id, - info: this.members[id] - }; - } else { - return null; - } - }; - - /** Calls back for each member in unspecified order. - * - * @param {Function} callback - */ - prototype.each = function(callback) { - var self = this; - Pusher.Util.objectApply(self.members, function(member, id) { - callback(self.get(id)); - }); - }; - - /** Updates the id for connected member. For internal use only. */ - prototype.setMyID = function(id) { - this.myID = id; - }; - - /** Handles subscription data. For internal use only. */ - prototype.onSubscription = function(subscriptionData) { - this.members = subscriptionData.presence.hash; - this.count = subscriptionData.presence.count; - this.me = this.get(this.myID); - }; - - /** Adds a new member to the collection. For internal use only. */ - prototype.addMember = function(memberData) { - if (this.get(memberData.user_id) === null) { - this.count++; - } - this.members[memberData.user_id] = memberData.user_info; - return this.get(memberData.user_id); - }; - - /** Adds a member from the collection. For internal use only. */ - prototype.removeMember = function(memberData) { - var member = this.get(memberData.user_id); - if (member) { - delete this.members[memberData.user_id]; - this.count--; - } - return member; - }; - - /** Resets the collection to the initial state. For internal use only. */ - prototype.reset = function() { - this.members = {}; - this.count = 0; - this.myID = null; - this.me = null; - }; - - Pusher.Members = Members; -}).call(this); - -;(function() { - /** Provides base public channel interface with an event emitter. - * - * Emits: - * - pusher:subscription_succeeded - after subscribing successfully - * - other non-internal events - * - * @param {String} name - * @param {Pusher} pusher - */ - function Channel(name, pusher) { - Pusher.EventsDispatcher.call(this, function(event, data) { - Pusher.debug('No callbacks on ' + name + ' for ' + event); - }); - - this.name = name; - this.pusher = pusher; - this.subscribed = false; - } - var prototype = Channel.prototype; - Pusher.Util.extend(prototype, Pusher.EventsDispatcher.prototype); - - /** Skips authorization, since public channels don't require it. - * - * @param {Function} callback - */ - prototype.authorize = function(socketId, callback) { - return callback(false, {}); - }; - - /** Triggers an event */ - prototype.trigger = function(event, data) { - if (event.indexOf("client-") !== 0) { - throw new Pusher.Errors.BadEventName( - "Event '" + event + "' does not start with 'client-'" - ); - } - return this.pusher.send_event(event, data, this.name); - }; - - /** Signals disconnection to the channel. For internal use only. */ - prototype.disconnect = function() { - this.subscribed = false; - }; - - /** Handles an event. For internal use only. - * - * @param {String} event - * @param {*} data - */ - prototype.handleEvent = function(event, data) { - if (event.indexOf("pusher_internal:") === 0) { - if (event === "pusher_internal:subscription_succeeded") { - this.subscribed = true; - this.emit("pusher:subscription_succeeded", data); - } - } else { - this.emit(event, data); - } - }; - - /** Sends a subscription request. For internal use only. */ - prototype.subscribe = function() { - var self = this; - - self.authorize(self.pusher.connection.socket_id, function(error, data) { - if (error) { - self.handleEvent('pusher:subscription_error', data); - } else { - self.pusher.send_event('pusher:subscribe', { - auth: data.auth, - channel_data: data.channel_data, - channel: self.name - }); - } - }); - }; - - /** Sends an unsubscription request. For internal use only. */ - prototype.unsubscribe = function() { - this.pusher.send_event('pusher:unsubscribe', { - channel: this.name - }); - }; - - Pusher.Channel = Channel; -}).call(this); - -;(function() { - /** Extends public channels to provide private channel interface. - * - * @param {String} name - * @param {Pusher} pusher - */ - function PrivateChannel(name, pusher) { - Pusher.Channel.call(this, name, pusher); - } - var prototype = PrivateChannel.prototype; - Pusher.Util.extend(prototype, Pusher.Channel.prototype); - - /** Authorizes the connection to use the channel. - * - * @param {String} socketId - * @param {Function} callback - */ - prototype.authorize = function(socketId, callback) { - var authorizer = new Pusher.Channel.Authorizer(this, this.pusher.config); - return authorizer.authorize(socketId, callback); - }; - - Pusher.PrivateChannel = PrivateChannel; -}).call(this); - -;(function() { - /** Adds presence channel functionality to private channels. - * - * @param {String} name - * @param {Pusher} pusher - */ - function PresenceChannel(name, pusher) { - Pusher.PrivateChannel.call(this, name, pusher); - this.members = new Pusher.Members(); - } - var prototype = PresenceChannel.prototype; - Pusher.Util.extend(prototype, Pusher.PrivateChannel.prototype); - - /** Authenticates the connection as a member of the channel. - * - * @param {String} socketId - * @param {Function} callback - */ - prototype.authorize = function(socketId, callback) { - var _super = Pusher.PrivateChannel.prototype.authorize; - var self = this; - _super.call(self, socketId, function(error, authData) { - if (!error) { - if (authData.channel_data === undefined) { - Pusher.warn( - "Invalid auth response for channel '" + - self.name + - "', expected 'channel_data' field" - ); - callback("Invalid auth response"); - return; - } - var channelData = JSON.parse(authData.channel_data); - self.members.setMyID(channelData.user_id); - } - callback(error, authData); - }); - }; - - /** Handles presence and subscription events. For internal use only. - * - * @param {String} event - * @param {*} data - */ - prototype.handleEvent = function(event, data) { - switch (event) { - case "pusher_internal:subscription_succeeded": - this.members.onSubscription(data); - this.subscribed = true; - this.emit("pusher:subscription_succeeded", this.members); - break; - case "pusher_internal:member_added": - var addedMember = this.members.addMember(data); - this.emit('pusher:member_added', addedMember); - break; - case "pusher_internal:member_removed": - var removedMember = this.members.removeMember(data); - if (removedMember) { - this.emit('pusher:member_removed', removedMember); - } - break; - default: - Pusher.PrivateChannel.prototype.handleEvent.call(this, event, data); - } - }; - - /** Resets the channel state, including members map. For internal use only. */ - prototype.disconnect = function() { - this.members.reset(); - Pusher.PrivateChannel.prototype.disconnect.call(this); - }; - - Pusher.PresenceChannel = PresenceChannel; -}).call(this); - -;(function() { - /** Handles a channel map. */ - function Channels() { - this.channels = {}; - } - var prototype = Channels.prototype; - - /** Creates or retrieves an existing channel by its name. - * - * @param {String} name - * @param {Pusher} pusher - * @return {Channel} - */ - prototype.add = function(name, pusher) { - if (!this.channels[name]) { - this.channels[name] = createChannel(name, pusher); - } - return this.channels[name]; - }; - - /** Returns a list of all channels - * - * @return {Array} - */ - prototype.all = function(name) { - return Pusher.Util.values(this.channels); - }; - - /** Finds a channel by its name. - * - * @param {String} name - * @return {Channel} channel or null if it doesn't exist - */ - prototype.find = function(name) { - return this.channels[name]; - }; - - /** Removes a channel from the map. - * - * @param {String} name - */ - prototype.remove = function(name) { - var channel = this.channels[name]; - delete this.channels[name]; - return channel; - }; - - /** Proxies disconnection signal to all channels. */ - prototype.disconnect = function() { - Pusher.Util.objectApply(this.channels, function(channel) { - channel.disconnect(); - }); - }; - - function createChannel(name, pusher) { - if (name.indexOf('private-') === 0) { - return new Pusher.PrivateChannel(name, pusher); - } else if (name.indexOf('presence-') === 0) { - return new Pusher.PresenceChannel(name, pusher); - } else { - return new Pusher.Channel(name, pusher); - } - } - - Pusher.Channels = Channels; -}).call(this); - -;(function() { - Pusher.Channel.Authorizer = function(channel, options) { - this.channel = channel; - this.type = options.authTransport; - - this.options = options; - this.authOptions = (options || {}).auth || {}; - }; - - Pusher.Channel.Authorizer.prototype = { - composeQuery: function(socketId) { - var query = '&socket_id=' + encodeURIComponent(socketId) + - '&channel_name=' + encodeURIComponent(this.channel.name); - - for(var i in this.authOptions.params) { - query += "&" + encodeURIComponent(i) + "=" + encodeURIComponent(this.authOptions.params[i]); - } - - return query; - }, - - authorize: function(socketId, callback) { - return Pusher.authorizers[this.type].call(this, socketId, callback); - } - }; - - var nextAuthCallbackID = 1; - - Pusher.auth_callbacks = {}; - Pusher.authorizers = { - ajax: function(socketId, callback){ - var self = this, xhr; - - if (Pusher.XHR) { - xhr = new Pusher.XHR(); - } else { - xhr = (window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP")); - } - - xhr.open("POST", self.options.authEndpoint, true); - - // add request headers - xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); - for(var headerName in this.authOptions.headers) { - xhr.setRequestHeader(headerName, this.authOptions.headers[headerName]); - } - - xhr.onreadystatechange = function() { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - var data, parsed = false; - - try { - data = JSON.parse(xhr.responseText); - parsed = true; - } catch (e) { - callback(true, 'JSON returned from webapp was invalid, yet status code was 200. Data was: ' + xhr.responseText); - } - - if (parsed) { // prevents double execution. - callback(false, data); - } - } else { - Pusher.warn("Couldn't get auth info from your webapp", xhr.status); - callback(true, xhr.status); - } - } - }; - - xhr.send(this.composeQuery(socketId)); - return xhr; - }, - - jsonp: function(socketId, callback){ - if(this.authOptions.headers !== undefined) { - Pusher.warn("Warn", "To send headers with the auth request, you must use AJAX, rather than JSONP."); - } - - var callbackName = nextAuthCallbackID.toString(); - nextAuthCallbackID++; - - var document = Pusher.Util.getDocument(); - var script = document.createElement("script"); - // Hacked wrapper. - Pusher.auth_callbacks[callbackName] = function(data) { - callback(false, data); - }; - - var callback_name = "Pusher.auth_callbacks['" + callbackName + "']"; - script.src = this.options.authEndpoint + - '?callback=' + - encodeURIComponent(callback_name) + - this.composeQuery(socketId); - - var head = document.getElementsByTagName("head")[0] || document.documentElement; - head.insertBefore( script, head.firstChild ); - } - }; -}).call(this); - +(function(){function b(a,d){(null===a||void 0===a)&&b.warn("Warning","You must pass your app key when you instantiate Pusher.");d=d||{};var c=this;this.key=a;this.config=b.Util.extend(b.getGlobalConfig(),d.cluster?b.getClusterConfig(d.cluster):{},d);this.channels=new b.Channels;this.global_emitter=new b.EventsDispatcher;this.sessionID=Math.floor(1E9*Math.random());this.timeline=new b.Timeline(this.key,this.sessionID,{cluster:this.config.cluster,features:b.Util.getClientFeatures(),params:this.config.timelineParams|| +{},limit:50,level:b.Timeline.INFO,version:b.VERSION});this.config.disableStats||(this.timelineSender=new b.TimelineSender(this.timeline,{host:this.config.statsHost,path:"/timeline/v2/jsonp"}));this.connection=new b.ConnectionManager(this.key,b.Util.extend({getStrategy:function(a){a=b.Util.extend({},c.config,a);return b.StrategyBuilder.build(b.getDefaultStrategy(a),a)},timeline:this.timeline,activityTimeout:this.config.activity_timeout,pongTimeout:this.config.pong_timeout,unavailableTimeout:this.config.unavailable_timeout}, +this.config,{encrypted:this.isEncrypted()}));this.connection.bind("connected",function(){c.subscribeAll();c.timelineSender&&c.timelineSender.send(c.connection.isEncrypted())});this.connection.bind("message",function(a){var d=0===a.event.indexOf("pusher_internal:");if(a.channel){var b=c.channel(a.channel);b&&b.handleEvent(a.event,a.data)}d||c.global_emitter.emit(a.event,a.data)});this.connection.bind("disconnected",function(){c.channels.disconnect()});this.connection.bind("error",function(a){b.warn("Error", +a)});b.instances.push(this);this.timeline.info({instances:b.instances.length});b.isReady&&c.connect()}var c=b.prototype;b.instances=[];b.isReady=!1;b.debug=function(){b.log&&b.log(b.Util.stringify.apply(this,arguments))};b.warn=function(){var a=b.Util.stringify.apply(this,arguments);window.console&&(window.console.warn?window.console.warn(a):window.console.log&&window.console.log(a));b.log&&b.log(a)};b.ready=function(){b.isReady=!0;for(var a=0,d=b.instances.length;ac;c++)"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(c);var a=function(a){var d=a.charCodeAt(0);return 128>d?a:2048>d?b(192|d>>>6)+b(128|d&63):b(224|d>>>12&15)+b(128|d>>>6&63)+b(128|d&63)},d=function(a){var d=[0,2,1][a.length%3];a=a.charCodeAt(0)<<16|(1>>18),"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(a>>> +12&63),2<=d?"=":"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(a>>>6&63),1<=d?"=":"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(a&63)].join("")},h=window.btoa||function(a){return a.replace(/[\s\S]{1,3}/g,d)};Pusher.Base64={encode:function(d){return h(d.replace(/[^\x00-\x7F]/g,a))}}}).call(this); +(function(){function b(a,b){this.url=a;this.data=b}function c(a){return Pusher.Util.mapObject(a,function(a){"object"===typeof a&&(a=JSON.stringify(a));return encodeURIComponent(Pusher.Base64.encode(a.toString()))})}var a=b.prototype;a.send=function(a){if(!this.request){var b=Pusher.Util.filterObject(this.data,function(a){return void 0!==a}),b=Pusher.Util.map(Pusher.Util.flatten(c(b)),Pusher.Util.method("join","=")).join("&");this.request=new Pusher.ScriptRequest(this.url+"/"+a.number+"?"+b);this.request.send(a)}}; +a.cleanup=function(){this.request&&this.request.cleanup()};Pusher.JSONPRequest=b}).call(this); +(function(){function b(a,b,c){this.key=a;this.session=b;this.events=[];this.options=c||{};this.uniqueID=this.sent=0}var c=b.prototype;b.ERROR=3;b.INFO=6;b.DEBUG=7;c.log=function(a,b){a<=this.options.level&&(this.events.push(Pusher.Util.extend({},b,{timestamp:Pusher.Util.now()})),this.options.limit&&this.events.length>this.options.limit&&this.events.shift())};c.error=function(a){this.log(b.ERROR,a)};c.info=function(a){this.log(b.INFO,a)};c.debug=function(a){this.log(b.DEBUG,a)};c.isEmpty=function(){return 0=== +this.events.length};c.send=function(a,b){var c=this,f=Pusher.Util.extend({session:c.session,bundle:c.sent+1,key:c.key,lib:"js",version:c.options.version,cluster:c.options.cluster,features:c.options.features,timeline:c.events},c.options.params);c.events=[];a(f,function(a,g){a||c.sent++;b&&b(a,g)});return!0};c.generateUniqueID=function(){this.uniqueID++;return this.uniqueID};Pusher.Timeline=b}).call(this); +(function(){function b(b,a){this.timeline=b;this.options=a||{}}b.prototype.send=function(b,a){var d=this;d.timeline.isEmpty()||d.timeline.send(function(a,f){var e=new Pusher.JSONPRequest("http"+(b?"s":"")+"://"+(d.host||d.options.host)+d.options.path,a),g=Pusher.ScriptReceivers.create(function(a,b){Pusher.ScriptReceivers.remove(g);e.cleanup();b&&b.host&&(d.host=b.host);f&&f(a,b)});e.send(g)},a)};Pusher.TimelineSender=b}).call(this); +(function(){function b(a){this.strategies=a}function c(a,b,c){var h=Pusher.Util.map(a,function(a,d,h,f){return a.connect(b,c(d,f))});return{abort:function(){Pusher.Util.apply(h,d)},forceMinPriority:function(a){Pusher.Util.apply(h,function(b){b.forceMinPriority(a)})}}}function a(a){return Pusher.Util.all(a,function(a){return Boolean(a.error)})}function d(a){!a.error&&!a.aborted&&(a.abort(),a.aborted=!0)}var h=b.prototype;h.isSupported=function(){return Pusher.Util.any(this.strategies,Pusher.Util.method("isSupported"))}; +h.connect=function(b,d){return c(this.strategies,b,function(b,c){return function(h,f){(c[b].error=h)?a(c)&&d(!0):(Pusher.Util.apply(c,function(a){a.forceMinPriority(f.transport.priority)}),d(null,f))}})};Pusher.BestConnectedEverStrategy=b}).call(this); +(function(){function b(a,b,d){this.strategy=a;this.transports=b;this.ttl=d.ttl||18E5;this.encrypted=d.encrypted;this.timeline=d.timeline}function c(a){return"pusherTransport"+(a?"Encrypted":"Unencrypted")}function a(a){var b=Pusher.Util.getLocalStorage();if(b)try{var h=b[c(a)];if(h)return JSON.parse(h)}catch(k){d(a)}return null}function d(a){var b=Pusher.Util.getLocalStorage();if(b)try{delete b[c(a)]}catch(d){}}var h=b.prototype;h.isSupported=function(){return this.strategy.isSupported()};h.connect= +function(b,h){var g=this.encrypted,k=a(g),l=[this.strategy];if(k&&k.timestamp+this.ttl>=Pusher.Util.now()){var m=this.transports[k.transport];m&&(this.timeline.info({cached:!0,transport:k.transport,latency:k.latency}),l.push(new Pusher.SequentialStrategy([m],{timeout:2*k.latency+1E3,failFast:!0})))}var p=Pusher.Util.now(),n=l.pop().connect(b,function s(a,k){if(a)d(g),0b.code?1002<=b.code&&1004>=b.code?"backoff":null:4E3===b.code?"ssl_only":4100>b.code?"refused":4200>b.code?"backoff":4300>b.code?"retry":"refused"},getCloseError:function(b){return 1E3!==b.code&&1001!==b.code?{type:"PusherError",data:{code:b.code,message:b.reason||b.message}}: +null}}}).call(this); +(function(){function b(a,b){Pusher.EventsDispatcher.call(this);this.id=a;this.transport=b;this.activityTimeout=b.activityTimeout;this.bindListeners()}var c=b.prototype;Pusher.Util.extend(c,Pusher.EventsDispatcher.prototype);c.handlesActivityChecks=function(){return this.transport.handlesActivityChecks()};c.send=function(a){return this.transport.send(a)};c.send_event=function(a,b,c){a={event:a,data:b};c&&(a.channel=c);Pusher.debug("Event sent",a);return this.send(Pusher.Protocol.encodeMessage(a))};c.ping= +function(){this.transport.supportsPing()?this.transport.ping():this.send_event("pusher:ping",{})};c.close=function(){this.transport.close()};c.bindListeners=function(){var a=this,b={message:function(b){var c;try{c=Pusher.Protocol.decodeMessage(b)}catch(d){a.emit("error",{type:"MessageParseError",error:d,data:b.data})}if(void 0!==c){Pusher.debug("Event recd",c);switch(c.event){case "pusher:error":a.emit("error",{type:"PusherError",data:c.data});break;case "pusher:ping":a.emit("ping");break;case "pusher:pong":a.emit("pong")}a.emit("message", +c)}},activity:function(){a.emit("activity")},error:function(b){a.emit("error",{type:"WebSocketError",error:b})},closed:function(b){c();b&&b.code&&a.handleCloseEvent(b);a.transport=null;a.emit("closed")}},c=function(){Pusher.Util.objectApply(b,function(b,c){a.transport.unbind(c,b)})};Pusher.Util.objectApply(b,function(b,c){a.transport.bind(c,b)})};c.handleCloseEvent=function(a){var b=Pusher.Protocol.getCloseAction(a);(a=Pusher.Protocol.getCloseError(a))&&this.emit("error",a);b&&this.emit(b)};Pusher.Connection= +b}).call(this); +(function(){function b(a,b){this.transport=a;this.callback=b;this.bindListeners()}var c=b.prototype;c.close=function(){this.unbindListeners();this.transport.close()};c.bindListeners=function(){var a=this;a.onMessage=function(b){a.unbindListeners();try{var c=Pusher.Protocol.processHandshake(b);"connected"===c.action?a.finish("connected",{connection:new Pusher.Connection(c.id,a.transport),activityTimeout:c.activityTimeout}):(a.finish(c.action,{error:c.error}),a.transport.close())}catch(f){a.finish("error",{error:f}), +a.transport.close()}};a.onClosed=function(b){a.unbindListeners();var c=Pusher.Protocol.getCloseAction(b)||"backoff";b=Pusher.Protocol.getCloseError(b);a.finish(c,{error:b})};a.transport.bind("message",a.onMessage);a.transport.bind("closed",a.onClosed)};c.unbindListeners=function(){this.transport.unbind("message",this.onMessage);this.transport.unbind("closed",this.onClosed)};c.finish=function(a,b){this.callback(Pusher.Util.extend({transport:this.transport,action:a},b))};Pusher.Handshake=b}).call(this); +(function(){function b(a,b){Pusher.EventsDispatcher.call(this);this.key=a;this.options=b||{};this.state="initialized";this.connection=null;this.encrypted=!!b.encrypted;this.timeline=this.options.timeline;this.connectionCallbacks=this.buildConnectionCallbacks();this.errorCallbacks=this.buildErrorCallbacks();this.handshakeCallbacks=this.buildHandshakeCallbacks(this.errorCallbacks);var c=this;Pusher.Network.bind("online",function(){c.timeline.info({netinfo:"online"});("connecting"===c.state||"unavailable"=== +c.state)&&c.retryIn(0)});Pusher.Network.bind("offline",function(){c.timeline.info({netinfo:"offline"});c.connection&&c.sendActivityCheck()});this.updateStrategy()}var c=b.prototype;Pusher.Util.extend(c,Pusher.EventsDispatcher.prototype);c.connect=function(){!this.connection&&!this.runner&&(this.strategy.isSupported()?(this.updateState("connecting"),this.startConnecting(),this.setUnavailableTimer()):this.updateState("failed"))};c.send=function(a){return this.connection?this.connection.send(a):!1}; +c.send_event=function(a,b,c){return this.connection?this.connection.send_event(a,b,c):!1};c.disconnect=function(){this.disconnectInternally();this.updateState("disconnected")};c.isEncrypted=function(){return this.encrypted};c.startConnecting=function(){var a=this,b=function(c,f){c?a.runner=a.strategy.connect(0,b):"error"===f.action?(a.emit("error",{type:"HandshakeError",error:f.error}),a.timeline.error({handshakeError:f.error})):(a.abortConnecting(),a.handshakeCallbacks[f.action](f))};a.runner=a.strategy.connect(0, +b)};c.abortConnecting=function(){this.runner&&(this.runner.abort(),this.runner=null)};c.disconnectInternally=function(){this.abortConnecting();this.clearRetryTimer();this.clearUnavailableTimer();this.connection&&this.abandonConnection().close()};c.updateStrategy=function(){this.strategy=this.options.getStrategy({key:this.key,timeline:this.timeline,encrypted:this.encrypted})};c.retryIn=function(a){var b=this;b.timeline.info({action:"retry",delay:a});0 "+a),this.timeline.info({state:a,params:b}),this.emit("state_change",{previous:c,current:a}),this.emit(a,b))};c.shouldRetry=function(){return"connecting"=== +this.state||"connected"===this.state};Pusher.ConnectionManager=b}).call(this); +(function(){function b(){Pusher.EventsDispatcher.call(this);var b=this;void 0!==window.addEventListener&&(window.addEventListener("online",function(){b.emit("online")},!1),window.addEventListener("offline",function(){b.emit("offline")},!1))}Pusher.Util.extend(b.prototype,Pusher.EventsDispatcher.prototype);b.prototype.isOnline=function(){return void 0===window.navigator.onLine?!0:window.navigator.onLine};Pusher.NetInfo=b;Pusher.Network=new b}).call(this); +(function(){function b(){this.reset()}var c=b.prototype;c.get=function(a){return Object.prototype.hasOwnProperty.call(this.members,a)?{id:a,info:this.members[a]}:null};c.each=function(a){var b=this;Pusher.Util.objectApply(b.members,function(c,f){a(b.get(f))})};c.setMyID=function(a){this.myID=a};c.onSubscription=function(a){this.members=a.presence.hash;this.count=a.presence.count;this.me=this.get(this.myID)};c.addMember=function(a){null===this.get(a.user_id)&&this.count++;this.members[a.user_id]=a.user_info; +return this.get(a.user_id)};c.removeMember=function(a){var b=this.get(a.user_id);b&&(delete this.members[a.user_id],this.count--);return b};c.reset=function(){this.members={};this.count=0;this.me=this.myID=null};Pusher.Members=b}).call(this); +(function(){function b(a,b){Pusher.EventsDispatcher.call(this,function(b,c){Pusher.debug("No callbacks on "+a+" for "+b)});this.name=a;this.pusher=b;this.subscribed=!1}var c=b.prototype;Pusher.Util.extend(c,Pusher.EventsDispatcher.prototype);c.authorize=function(a,b){return b(!1,{})};c.trigger=function(a,b){if(0!==a.indexOf("client-"))throw new Pusher.Errors.BadEventName("Event '"+a+"' does not start with 'client-'");return this.pusher.send_event(a,b,this.name)};c.disconnect=function(){this.subscribed= +!1};c.handleEvent=function(a,b){0===a.indexOf("pusher_internal:")?"pusher_internal:subscription_succeeded"===a&&(this.subscribed=!0,this.emit("pusher:subscription_succeeded",b)):this.emit(a,b)};c.subscribe=function(){var a=this;a.authorize(a.pusher.connection.socket_id,function(b,c){b?a.handleEvent("pusher:subscription_error",c):a.pusher.send_event("pusher:subscribe",{auth:c.auth,channel_data:c.channel_data,channel:a.name})})};c.unsubscribe=function(){this.pusher.send_event("pusher:unsubscribe",{channel:this.name})}; +Pusher.Channel=b}).call(this);(function(){function b(a,b){Pusher.Channel.call(this,a,b)}var c=b.prototype;Pusher.Util.extend(c,Pusher.Channel.prototype);c.authorize=function(a,b){return(new Pusher.Channel.Authorizer(this,this.pusher.config)).authorize(a,b)};Pusher.PrivateChannel=b}).call(this); +(function(){function b(a,b){Pusher.PrivateChannel.call(this,a,b);this.members=new Pusher.Members}var c=b.prototype;Pusher.Util.extend(c,Pusher.PrivateChannel.prototype);c.authorize=function(a,b){var c=this;Pusher.PrivateChannel.prototype.authorize.call(c,a,function(a,e){if(!a){if(void 0===e.channel_data){Pusher.warn("Invalid auth response for channel '"+c.name+"', expected 'channel_data' field");b("Invalid auth response");return}var g=JSON.parse(e.channel_data);c.members.setMyID(g.user_id)}b(a,e)})}; +c.handleEvent=function(a,b){switch(a){case "pusher_internal:subscription_succeeded":this.members.onSubscription(b);this.subscribed=!0;this.emit("pusher:subscription_succeeded",this.members);break;case "pusher_internal:member_added":var c=this.members.addMember(b);this.emit("pusher:member_added",c);break;case "pusher_internal:member_removed":(c=this.members.removeMember(b))&&this.emit("pusher:member_removed",c);break;default:Pusher.PrivateChannel.prototype.handleEvent.call(this,a,b)}};c.disconnect= +function(){this.members.reset();Pusher.PrivateChannel.prototype.disconnect.call(this)};Pusher.PresenceChannel=b}).call(this); +(function(){function b(){this.channels={}}var c=b.prototype;c.add=function(a,b){if(!this.channels[a]){var c=this.channels,f;f=0===a.indexOf("private-")?new Pusher.PrivateChannel(a,b):0===a.indexOf("presence-")?new Pusher.PresenceChannel(a,b):new Pusher.Channel(a,b);c[a]=f}return this.channels[a]};c.all=function(a){return Pusher.Util.values(this.channels)};c.find=function(a){return this.channels[a]};c.remove=function(a){var b=this.channels[a];delete this.channels[a];return b};c.disconnect=function(){Pusher.Util.objectApply(this.channels, +function(a){a.disconnect()})};Pusher.Channels=b}).call(this); +(function(){Pusher.Channel.Authorizer=function(b,a){this.channel=b;this.type=a.authTransport;this.options=a;this.authOptions=(a||{}).auth||{}};Pusher.Channel.Authorizer.prototype={composeQuery:function(b){b="&socket_id="+encodeURIComponent(b)+"&channel_name="+encodeURIComponent(this.channel.name);for(var a in this.authOptions.params)b+="&"+encodeURIComponent(a)+"="+encodeURIComponent(this.authOptions.params[a]);return b},authorize:function(b,a){return Pusher.authorizers[this.type].call(this,b,a)}}; +var b=1;Pusher.auth_callbacks={};Pusher.authorizers={ajax:function(b,a){var d;d=Pusher.XHR?new Pusher.XHR:window.XMLHttpRequest?new window.XMLHttpRequest:new ActiveXObject("Microsoft.XMLHTTP");d.open("POST",this.options.authEndpoint,!0);d.setRequestHeader("Content-Type","application/x-www-form-urlencoded");for(var h in this.authOptions.headers)d.setRequestHeader(h,this.authOptions.headers[h]);d.onreadystatechange=function(){if(4===d.readyState)if(200===d.status){var b,c=!1;try{b=JSON.parse(d.responseText), +c=!0}catch(g){a(!0,"JSON returned from webapp was invalid, yet status code was 200. Data was: "+d.responseText)}c&&a(!1,b)}else Pusher.warn("Couldn't get auth info from your webapp",d.status),a(!0,d.status)};d.send(this.composeQuery(b));return d},jsonp:function(c,a){void 0!==this.authOptions.headers&&Pusher.warn("Warn","To send headers with the auth request, you must use AJAX, rather than JSONP.");var d=b.toString();b++;var h=Pusher.Util.getDocument(),f=h.createElement("script");Pusher.auth_callbacks[d]= +function(b){a(!1,b)};f.src=this.options.authEndpoint+"?callback="+encodeURIComponent("Pusher.auth_callbacks['"+d+"']")+this.composeQuery(c);d=h.getElementsByTagName("head")[0]||h.documentElement;d.insertBefore(f,d.firstChild)}}}).call(this);