pusher-fake 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3413 @@
1
+ /*!
2
+ * Pusher JavaScript Library v2.0.4
3
+ * http://pusherapp.com/
4
+ *
5
+ * Copyright 2013, Pusher
6
+ * Released under the MIT licence.
7
+ */
8
+
9
+ ;(function() {
10
+ function Pusher(app_key, options) {
11
+ var self = this;
12
+
13
+ this.options = options || {};
14
+ this.key = app_key;
15
+ this.channels = new Pusher.Channels();
16
+ this.global_emitter = new Pusher.EventsDispatcher();
17
+ this.sessionID = Math.floor(Math.random() * 1000000000);
18
+
19
+ checkAppKey(this.key);
20
+
21
+ var getStrategy = function(options) {
22
+ return Pusher.StrategyBuilder.build(
23
+ Pusher.getDefaultStrategy(),
24
+ Pusher.Util.extend({}, self.options, options)
25
+ );
26
+ };
27
+ var getTimeline = function() {
28
+ return new Pusher.Timeline(self.key, self.sessionID, {
29
+ features: Pusher.Util.getClientFeatures(),
30
+ params: self.options.timelineParams || {},
31
+ limit: 25,
32
+ level: Pusher.Timeline.INFO,
33
+ version: Pusher.VERSION
34
+ });
35
+ };
36
+ var getTimelineSender = function(timeline, options) {
37
+ if (self.options.disableStats) {
38
+ return null;
39
+ }
40
+ return new Pusher.TimelineSender(timeline, {
41
+ encrypted: self.isEncrypted() || !!options.encrypted,
42
+ host: Pusher.stats_host,
43
+ path: "/timeline"
44
+ });
45
+ };
46
+
47
+ this.connection = new Pusher.ConnectionManager(
48
+ this.key,
49
+ Pusher.Util.extend(
50
+ { getStrategy: getStrategy,
51
+ getTimeline: getTimeline,
52
+ getTimelineSender: getTimelineSender,
53
+ activityTimeout: Pusher.activity_timeout,
54
+ pongTimeout: Pusher.pong_timeout,
55
+ unavailableTimeout: Pusher.unavailable_timeout
56
+ },
57
+ this.options,
58
+ { encrypted: this.isEncrypted() }
59
+ )
60
+ );
61
+
62
+ this.connection.bind('connected', function() {
63
+ self.subscribeAll();
64
+ });
65
+ this.connection.bind('message', function(params) {
66
+ var internal = (params.event.indexOf('pusher_internal:') === 0);
67
+ if (params.channel) {
68
+ var channel = self.channel(params.channel);
69
+ if (channel) {
70
+ channel.emit(params.event, params.data);
71
+ }
72
+ }
73
+ // Emit globaly [deprecated]
74
+ if (!internal) self.global_emitter.emit(params.event, params.data);
75
+ });
76
+ this.connection.bind('disconnected', function() {
77
+ self.channels.disconnect();
78
+ });
79
+ this.connection.bind('error', function(err) {
80
+ Pusher.warn('Error', err);
81
+ });
82
+
83
+ Pusher.instances.push(this);
84
+
85
+ if (Pusher.isReady) self.connect();
86
+ }
87
+ var prototype = Pusher.prototype;
88
+
89
+ Pusher.instances = [];
90
+ Pusher.isReady = false;
91
+
92
+ // To receive log output provide a Pusher.log function, for example
93
+ // Pusher.log = function(m){console.log(m)}
94
+ Pusher.debug = function() {
95
+ if (!Pusher.log) {
96
+ return;
97
+ }
98
+ Pusher.log(Pusher.Util.stringify.apply(this, arguments));
99
+ };
100
+
101
+ Pusher.warn = function() {
102
+ if (window.console && window.console.warn) {
103
+ window.console.warn(Pusher.Util.stringify.apply(this, arguments));
104
+ } else {
105
+ if (!Pusher.log) {
106
+ return;
107
+ }
108
+ Pusher.log(Pusher.Util.stringify.apply(this, arguments));
109
+ }
110
+ };
111
+
112
+ Pusher.ready = function() {
113
+ Pusher.isReady = true;
114
+ for (var i = 0, l = Pusher.instances.length; i < l; i++) {
115
+ Pusher.instances[i].connect();
116
+ }
117
+ };
118
+
119
+ prototype.channel = function(name) {
120
+ return this.channels.find(name);
121
+ };
122
+
123
+ prototype.connect = function() {
124
+ this.connection.connect();
125
+ };
126
+
127
+ prototype.disconnect = function() {
128
+ this.connection.disconnect();
129
+ };
130
+
131
+ prototype.bind = function(event_name, callback) {
132
+ this.global_emitter.bind(event_name, callback);
133
+ return this;
134
+ };
135
+
136
+ prototype.bind_all = function(callback) {
137
+ this.global_emitter.bind_all(callback);
138
+ return this;
139
+ };
140
+
141
+ prototype.subscribeAll = function() {
142
+ var channelName;
143
+ for (channelName in this.channels.channels) {
144
+ if (this.channels.channels.hasOwnProperty(channelName)) {
145
+ this.subscribe(channelName);
146
+ }
147
+ }
148
+ };
149
+
150
+ prototype.subscribe = function(channel_name) {
151
+ var self = this;
152
+ var channel = this.channels.add(channel_name, this);
153
+
154
+ if (this.connection.state === 'connected') {
155
+ channel.authorize(
156
+ this.connection.socket_id,
157
+ this.options,
158
+ function(err, data) {
159
+ if (err) {
160
+ channel.emit('pusher:subscription_error', data);
161
+ } else {
162
+ self.send_event('pusher:subscribe', {
163
+ channel: channel_name,
164
+ auth: data.auth,
165
+ channel_data: data.channel_data
166
+ });
167
+ }
168
+ }
169
+ );
170
+ }
171
+ return channel;
172
+ };
173
+
174
+ prototype.unsubscribe = function(channel_name) {
175
+ this.channels.remove(channel_name);
176
+ if (this.connection.state === 'connected') {
177
+ this.send_event('pusher:unsubscribe', {
178
+ channel: channel_name
179
+ });
180
+ }
181
+ };
182
+
183
+ prototype.send_event = function(event_name, data, channel) {
184
+ return this.connection.send_event(event_name, data, channel);
185
+ };
186
+
187
+ prototype.isEncrypted = function() {
188
+ if (Pusher.Util.getDocumentLocation().protocol === "https:") {
189
+ return true;
190
+ } else {
191
+ return !!this.options.encrypted;
192
+ }
193
+ };
194
+
195
+ function checkAppKey(key) {
196
+ if (key === null || key === undefined) {
197
+ Pusher.warn(
198
+ 'Warning', 'You must pass your app key when you instantiate Pusher.'
199
+ );
200
+ }
201
+ }
202
+
203
+ this.Pusher = Pusher;
204
+ }).call(this);
205
+
206
+ ;(function() {
207
+ Pusher.Util = {
208
+ now: function() {
209
+ if (Date.now) {
210
+ return Date.now();
211
+ } else {
212
+ return new Date().valueOf();
213
+ }
214
+ },
215
+
216
+ /** Merges multiple objects into the target argument.
217
+ *
218
+ * For properties that are plain Objects, performs a deep-merge. For the
219
+ * rest it just copies the value of the property.
220
+ *
221
+ * To extend prototypes use it as following:
222
+ * Pusher.Util.extend(Target.prototype, Base.prototype)
223
+ *
224
+ * You can also use it to merge objects without altering them:
225
+ * Pusher.Util.extend({}, object1, object2)
226
+ *
227
+ * @param {Object} target
228
+ * @return {Object} the target argument
229
+ */
230
+ extend: function(target) {
231
+ for (var i = 1; i < arguments.length; i++) {
232
+ var extensions = arguments[i];
233
+ for (var property in extensions) {
234
+ if (extensions[property] && extensions[property].constructor &&
235
+ extensions[property].constructor === Object) {
236
+ target[property] = Pusher.Util.extend(
237
+ target[property] || {}, extensions[property]
238
+ );
239
+ } else {
240
+ target[property] = extensions[property];
241
+ }
242
+ }
243
+ }
244
+ return target;
245
+ },
246
+
247
+ stringify: function() {
248
+ var m = ["Pusher"];
249
+ for (var i = 0; i < arguments.length; i++) {
250
+ if (typeof arguments[i] === "string") {
251
+ m.push(arguments[i]);
252
+ } else {
253
+ if (window.JSON === undefined) {
254
+ m.push(arguments[i].toString());
255
+ } else {
256
+ m.push(JSON.stringify(arguments[i]));
257
+ }
258
+ }
259
+ }
260
+ return m.join(" : ");
261
+ },
262
+
263
+ arrayIndexOf: function(array, item) { // MSIE doesn't have array.indexOf
264
+ var nativeIndexOf = Array.prototype.indexOf;
265
+ if (array === null) {
266
+ return -1;
267
+ }
268
+ if (nativeIndexOf && array.indexOf === nativeIndexOf) {
269
+ return array.indexOf(item);
270
+ }
271
+ for (var i = 0, l = array.length; i < l; i++) {
272
+ if (array[i] === item) {
273
+ return i;
274
+ }
275
+ }
276
+ return -1;
277
+ },
278
+
279
+ keys: function(object) {
280
+ var result = [];
281
+ for (var key in object) {
282
+ if (Object.prototype.hasOwnProperty.call(object, key)) {
283
+ result.push(key);
284
+ }
285
+ }
286
+ return result;
287
+ },
288
+
289
+ /** Applies a function f to all elements of an array.
290
+ *
291
+ * Function f gets 3 arguments passed:
292
+ * - element from the array
293
+ * - index of the element
294
+ * - reference to the array
295
+ *
296
+ * @param {Array} array
297
+ * @param {Function} f
298
+ */
299
+ apply: function(array, f) {
300
+ for (var i = 0; i < array.length; i++) {
301
+ f(array[i], i, array);
302
+ }
303
+ },
304
+
305
+ /** Applies a function f to all properties of an object.
306
+ *
307
+ * Function f gets 3 arguments passed:
308
+ * - element from the object
309
+ * - key of the element
310
+ * - reference to the object
311
+ *
312
+ * @param {Object} object
313
+ * @param {Function} f
314
+ */
315
+ objectApply: function(object, f) {
316
+ for (var key in object) {
317
+ if (Object.prototype.hasOwnProperty.call(object, key)) {
318
+ f(object[key], key, object);
319
+ }
320
+ }
321
+ },
322
+
323
+ /** Maps all elements of the array and returns the result.
324
+ *
325
+ * Function f gets 4 arguments passed:
326
+ * - element from the array
327
+ * - index of the element
328
+ * - reference to the source array
329
+ * - reference to the destination array
330
+ *
331
+ * @param {Array} array
332
+ * @param {Function} f
333
+ */
334
+ map: function(array, f) {
335
+ var result = [];
336
+ for (var i = 0; i < array.length; i++) {
337
+ result.push(f(array[i], i, array, result));
338
+ }
339
+ return result;
340
+ },
341
+
342
+ /** Maps all elements of the object and returns the result.
343
+ *
344
+ * Function f gets 4 arguments passed:
345
+ * - element from the object
346
+ * - key of the element
347
+ * - reference to the source object
348
+ * - reference to the destination object
349
+ *
350
+ * @param {Object} object
351
+ * @param {Function} f
352
+ */
353
+ mapObject: function(object, f) {
354
+ var result = {};
355
+ for (var key in object) {
356
+ if (Object.prototype.hasOwnProperty.call(object, key)) {
357
+ result[key] = f(object[key]);
358
+ }
359
+ }
360
+ return result;
361
+ },
362
+
363
+ /** Filters elements of the array using a test function.
364
+ *
365
+ * Function test gets 4 arguments passed:
366
+ * - element from the array
367
+ * - index of the element
368
+ * - reference to the source array
369
+ * - reference to the destination array
370
+ *
371
+ * @param {Array} array
372
+ * @param {Function} f
373
+ */
374
+ filter: function(array, test) {
375
+ test = test || function(value) { return !!value; };
376
+
377
+ var result = [];
378
+ for (var i = 0; i < array.length; i++) {
379
+ if (test(array[i], i, array, result)) {
380
+ result.push(array[i]);
381
+ }
382
+ }
383
+ return result;
384
+ },
385
+
386
+ /** Filters properties of the object using a test function.
387
+ *
388
+ * Function test gets 4 arguments passed:
389
+ * - element from the object
390
+ * - key of the element
391
+ * - reference to the source object
392
+ * - reference to the destination object
393
+ *
394
+ * @param {Object} object
395
+ * @param {Function} f
396
+ */
397
+ filterObject: function(object, test) {
398
+ test = test || function(value) { return !!value; };
399
+
400
+ var result = {};
401
+ for (var key in object) {
402
+ if (Object.prototype.hasOwnProperty.call(object, key)) {
403
+ if (test(object[key], key, object, result)) {
404
+ result[key] = object[key];
405
+ }
406
+ }
407
+ }
408
+ return result;
409
+ },
410
+
411
+ /** Flattens an object into a two-dimensional array.
412
+ *
413
+ * @param {Object} object
414
+ * @return {Array} resulting array of [key, value] pairs
415
+ */
416
+ flatten: function(object) {
417
+ var result = [];
418
+ for (var key in object) {
419
+ if (Object.prototype.hasOwnProperty.call(object, key)) {
420
+ result.push([key, object[key]]);
421
+ }
422
+ }
423
+ return result;
424
+ },
425
+
426
+ /** Checks whether any element of the array passes the test.
427
+ *
428
+ * Function test gets 3 arguments passed:
429
+ * - element from the array
430
+ * - index of the element
431
+ * - reference to the source array
432
+ *
433
+ * @param {Array} array
434
+ * @param {Function} f
435
+ */
436
+ any: function(array, test) {
437
+ for (var i = 0; i < array.length; i++) {
438
+ if (test(array[i], i, array)) {
439
+ return true;
440
+ }
441
+ }
442
+ return false;
443
+ },
444
+
445
+ /** Checks whether all elements of the array pass the test.
446
+ *
447
+ * Function test gets 3 arguments passed:
448
+ * - element from the array
449
+ * - index of the element
450
+ * - reference to the source array
451
+ *
452
+ * @param {Array} array
453
+ * @param {Function} f
454
+ */
455
+ all: function(array, test) {
456
+ for (var i = 0; i < array.length; i++) {
457
+ if (!test(array[i], i, array)) {
458
+ return false;
459
+ }
460
+ }
461
+ return true;
462
+ },
463
+
464
+ /** Builds a function that will proxy a method call to its first argument.
465
+ *
466
+ * Allows partial application of arguments, so additional arguments are
467
+ * prepended to the argument list.
468
+ *
469
+ * @param {String} name method name
470
+ * @return {Function} proxy function
471
+ */
472
+ method: function(name) {
473
+ var boundArguments = Array.prototype.slice.call(arguments, 1);
474
+ return function(object) {
475
+ return object[name].apply(object, boundArguments.concat(arguments));
476
+ };
477
+ },
478
+
479
+ getDocument: function() {
480
+ return document;
481
+ },
482
+
483
+ getDocumentLocation: function() {
484
+ return Pusher.Util.getDocument().location;
485
+ },
486
+
487
+ getLocalStorage: function() {
488
+ return window.localStorage;
489
+ },
490
+
491
+ getClientFeatures: function() {
492
+ return Pusher.Util.keys(
493
+ Pusher.Util.filterObject(
494
+ { "ws": Pusher.WSTransport, "flash": Pusher.FlashTransport },
495
+ function (t) { return t.isSupported(); }
496
+ )
497
+ );
498
+ }
499
+ };
500
+ }).call(this);
501
+
502
+ ;(function() {
503
+ Pusher.VERSION = '2.0.4';
504
+ Pusher.PROTOCOL = 6;
505
+
506
+ // WS connection parameters
507
+ Pusher.host = 'ws.pusherapp.com';
508
+ Pusher.ws_port = 80;
509
+ Pusher.wss_port = 443;
510
+ // SockJS fallback parameters
511
+ Pusher.sockjs_host = 'sockjs.pusher.com';
512
+ Pusher.sockjs_http_port = 80;
513
+ Pusher.sockjs_https_port = 443;
514
+ Pusher.sockjs_path = "/pusher";
515
+ // Stats
516
+ Pusher.stats_host = 'stats.pusher.com';
517
+ // Other settings
518
+ Pusher.channel_auth_endpoint = '/pusher/auth';
519
+ Pusher.cdn_http = 'http://js.pusher.com/';
520
+ Pusher.cdn_https = 'https://d3dy5gmtp8yhk7.cloudfront.net/';
521
+ Pusher.dependency_suffix = '';
522
+ Pusher.channel_auth_transport = 'ajax';
523
+ Pusher.activity_timeout = 120000;
524
+ Pusher.pong_timeout = 30000;
525
+ Pusher.unavailable_timeout = 10000;
526
+
527
+ Pusher.getDefaultStrategy = function() {
528
+ return [
529
+ [":def", "ws_options", {
530
+ hostUnencrypted: Pusher.host + ":" + Pusher.ws_port,
531
+ hostEncrypted: Pusher.host + ":" + Pusher.wss_port
532
+ }],
533
+ [":def", "sockjs_options", {
534
+ hostUnencrypted: Pusher.sockjs_host + ":" + Pusher.sockjs_http_port,
535
+ hostEncrypted: Pusher.sockjs_host + ":" + Pusher.sockjs_https_port
536
+ }],
537
+ [":def", "timeouts", {
538
+ loop: true,
539
+ timeout: 15000,
540
+ timeoutLimit: 60000
541
+ }],
542
+
543
+ [":def", "ws_manager", [":transport_manager", { lives: 2 }]],
544
+ [":def_transport", "ws", "ws", 3, ":ws_options", ":ws_manager"],
545
+ [":def_transport", "flash", "flash", 2, ":ws_options", ":ws_manager"],
546
+ [":def_transport", "sockjs", "sockjs", 1, ":sockjs_options"],
547
+ [":def", "ws_loop", [":sequential", ":timeouts", ":ws"]],
548
+ [":def", "flash_loop", [":sequential", ":timeouts", ":flash"]],
549
+ [":def", "sockjs_loop", [":sequential", ":timeouts", ":sockjs"]],
550
+
551
+ [":def", "strategy",
552
+ [":cached", 1800000,
553
+ [":first_connected",
554
+ [":if", [":is_supported", ":ws"], [
555
+ ":best_connected_ever", ":ws_loop", [":delayed", 2000, [":sockjs_loop"]]
556
+ ], [":if", [":is_supported", ":flash"], [
557
+ ":best_connected_ever", ":flash_loop", [":delayed", 2000, [":sockjs_loop"]]
558
+ ], [
559
+ ":sockjs_loop"
560
+ ]
561
+ ]]
562
+ ]
563
+ ]
564
+ ]
565
+ ];
566
+ };
567
+ }).call(this);
568
+
569
+ ;(function() {
570
+ function buildExceptionClass(name) {
571
+ var klass = function(message) {
572
+ Error.call(this, message);
573
+ this.name = name;
574
+ };
575
+ Pusher.Util.extend(klass.prototype, Error.prototype);
576
+
577
+ return klass;
578
+ }
579
+
580
+ /** Error classes used throughout pusher-js library. */
581
+ Pusher.Errors = {
582
+ UnsupportedTransport: buildExceptionClass("UnsupportedTransport"),
583
+ UnsupportedStrategy: buildExceptionClass("UnsupportedStrategy"),
584
+ TransportPriorityTooLow: buildExceptionClass("TransportPriorityTooLow"),
585
+ TransportClosed: buildExceptionClass("TransportClosed")
586
+ };
587
+ }).call(this);
588
+
589
+ ;(function() {
590
+ /** Manages callback bindings and event emitting.
591
+ *
592
+ * @param Function failThrough called when no listeners are bound to an event
593
+ */
594
+ function EventsDispatcher(failThrough) {
595
+ this.callbacks = new CallbackRegistry();
596
+ this.global_callbacks = [];
597
+ this.failThrough = failThrough;
598
+ }
599
+ var prototype = EventsDispatcher.prototype;
600
+
601
+ prototype.bind = function(eventName, callback) {
602
+ this.callbacks.add(eventName, callback);
603
+ return this;
604
+ };
605
+
606
+ prototype.bind_all = function(callback) {
607
+ this.global_callbacks.push(callback);
608
+ return this;
609
+ };
610
+
611
+ prototype.unbind = function(eventName, callback) {
612
+ this.callbacks.remove(eventName, callback);
613
+ return this;
614
+ };
615
+
616
+ prototype.emit = function(eventName, data) {
617
+ var i;
618
+
619
+ for (i = 0; i < this.global_callbacks.length; i++) {
620
+ this.global_callbacks[i](eventName, data);
621
+ }
622
+
623
+ var callbacks = this.callbacks.get(eventName);
624
+ if (callbacks && callbacks.length > 0) {
625
+ for (i = 0; i < callbacks.length; i++) {
626
+ callbacks[i](data);
627
+ }
628
+ } else if (this.failThrough) {
629
+ this.failThrough(eventName, data);
630
+ }
631
+
632
+ return this;
633
+ };
634
+
635
+ /** Callback registry helper. */
636
+
637
+ function CallbackRegistry() {
638
+ this._callbacks = {};
639
+ }
640
+
641
+ CallbackRegistry.prototype.get = function(eventName) {
642
+ return this._callbacks[this._prefix(eventName)];
643
+ };
644
+
645
+ CallbackRegistry.prototype.add = function(eventName, callback) {
646
+ var prefixedEventName = this._prefix(eventName);
647
+ this._callbacks[prefixedEventName] = this._callbacks[prefixedEventName] || [];
648
+ this._callbacks[prefixedEventName].push(callback);
649
+ };
650
+
651
+ CallbackRegistry.prototype.remove = function(eventName, callback) {
652
+ if(this.get(eventName)) {
653
+ var index = Pusher.Util.arrayIndexOf(this.get(eventName), callback);
654
+ if (index !== -1){
655
+ var callbacksCopy = this._callbacks[this._prefix(eventName)].slice(0);
656
+ callbacksCopy.splice(index, 1);
657
+ this._callbacks[this._prefix(eventName)] = callbacksCopy;
658
+ }
659
+ }
660
+ };
661
+
662
+ CallbackRegistry.prototype._prefix = function(eventName) {
663
+ return "_" + eventName;
664
+ };
665
+
666
+ Pusher.EventsDispatcher = EventsDispatcher;
667
+ }).call(this);
668
+
669
+ ;(function() {
670
+ /** Handles loading dependency files.
671
+ *
672
+ * Options:
673
+ * - cdn_http - url to HTTP CND
674
+ * - cdn_https - url to HTTPS CDN
675
+ * - version - version of pusher-js
676
+ * - suffix - suffix appended to all names of dependency files
677
+ *
678
+ * @param {Object} options
679
+ */
680
+ function DependencyLoader(options) {
681
+ this.options = options;
682
+ this.loading = {};
683
+ this.loaded = {};
684
+ }
685
+ var prototype = DependencyLoader.prototype;
686
+
687
+ /** Loads the dependency from CDN.
688
+ *
689
+ * @param {String} name
690
+ * @param {Function} callback
691
+ */
692
+ prototype.load = function(name, callback) {
693
+ var self = this;
694
+
695
+ if (this.loaded[name]) {
696
+ callback();
697
+ return;
698
+ }
699
+
700
+ if (!this.loading[name]) {
701
+ this.loading[name] = [];
702
+ }
703
+ this.loading[name].push(callback);
704
+ if (this.loading[name].length > 1) {
705
+ return;
706
+ }
707
+
708
+ require(this.getPath(name), function() {
709
+ for (var i = 0; i < self.loading[name].length; i++) {
710
+ self.loading[name][i]();
711
+ }
712
+ delete self.loading[name];
713
+ self.loaded[name] = true;
714
+ });
715
+ };
716
+
717
+ /** Returns a root URL for pusher-js CDN.
718
+ *
719
+ * @returns {String}
720
+ */
721
+ prototype.getRoot = function(options) {
722
+ var cdn;
723
+ var protocol = Pusher.Util.getDocumentLocation().protocol;
724
+ if ((options && options.encrypted) || protocol === "https:") {
725
+ cdn = this.options.cdn_https;
726
+ } else {
727
+ cdn = this.options.cdn_http;
728
+ }
729
+ // make sure there are no double slashes
730
+ return cdn.replace(/\/*$/, "") + "/" + this.options.version;
731
+ };
732
+
733
+ /** Returns a full path to a dependency file.
734
+ *
735
+ * @param {String} name
736
+ * @returns {String}
737
+ */
738
+ prototype.getPath = function(name, options) {
739
+ return this.getRoot(options) + '/' + name + this.options.suffix + '.js';
740
+ };
741
+
742
+ function handleScriptLoaded(elem, callback) {
743
+ if (Pusher.Util.getDocument().addEventListener) {
744
+ elem.addEventListener('load', callback, false);
745
+ } else {
746
+ elem.attachEvent('onreadystatechange', function () {
747
+ if (elem.readyState === 'loaded' || elem.readyState === 'complete') {
748
+ callback();
749
+ }
750
+ });
751
+ }
752
+ }
753
+
754
+ function require(src, callback) {
755
+ var document = Pusher.Util.getDocument();
756
+ var head = document.getElementsByTagName('head')[0];
757
+ var script = document.createElement('script');
758
+
759
+ script.setAttribute('src', src);
760
+ script.setAttribute("type","text/javascript");
761
+ script.setAttribute('async', true);
762
+
763
+ handleScriptLoaded(script, function() {
764
+ // workaround for an Opera issue
765
+ setTimeout(callback, 0);
766
+ });
767
+
768
+ head.appendChild(script);
769
+ }
770
+
771
+ Pusher.DependencyLoader = DependencyLoader;
772
+ }).call(this);
773
+
774
+ ;(function() {
775
+ Pusher.Dependencies = new Pusher.DependencyLoader({
776
+ cdn_http: Pusher.cdn_http,
777
+ cdn_https: Pusher.cdn_https,
778
+ version: Pusher.VERSION,
779
+ suffix: Pusher.dependency_suffix
780
+ });
781
+
782
+ // Support Firefox versions which prefix WebSocket
783
+ if (!window.WebSocket && window.MozWebSocket) {
784
+ window.WebSocket = window.MozWebSocket;
785
+ }
786
+
787
+ function initialize() {
788
+ Pusher.ready();
789
+ }
790
+
791
+ // Allows calling a function when the document body is available
792
+ function onDocumentBody(callback) {
793
+ if (document.body) {
794
+ callback();
795
+ } else {
796
+ setTimeout(function() {
797
+ onDocumentBody(callback);
798
+ }, 0);
799
+ }
800
+ }
801
+
802
+ function initializeOnDocumentBody() {
803
+ onDocumentBody(initialize);
804
+ }
805
+
806
+ if (!window.JSON) {
807
+ Pusher.Dependencies.load("json2", initializeOnDocumentBody);
808
+ } else {
809
+ initializeOnDocumentBody();
810
+ }
811
+ })();
812
+
813
+ ;(function() {
814
+ /** Cross-browser compatible timer abstraction.
815
+ *
816
+ * @param {Number} delay
817
+ * @param {Function} callback
818
+ */
819
+ function Timer(delay, callback) {
820
+ var self = this;
821
+
822
+ this.timeout = setTimeout(function() {
823
+ if (self.timeout !== null) {
824
+ callback();
825
+ self.timeout = null;
826
+ }
827
+ }, delay);
828
+ }
829
+ var prototype = Timer.prototype;
830
+
831
+ /** Returns whether the timer is still running.
832
+ *
833
+ * @return {Boolean}
834
+ */
835
+ prototype.isRunning = function() {
836
+ return this.timeout !== null;
837
+ };
838
+
839
+ /** Aborts a timer when it's running. */
840
+ prototype.ensureAborted = function() {
841
+ if (this.timeout) {
842
+ clearTimeout(this.timeout);
843
+ this.timeout = null;
844
+ }
845
+ };
846
+
847
+ Pusher.Timer = Timer;
848
+ }).call(this);
849
+
850
+ (function() {
851
+
852
+ var Base64 = {
853
+ encode: function (s) {
854
+ return btoa(utob(s));
855
+ }
856
+ };
857
+
858
+ var fromCharCode = String.fromCharCode;
859
+
860
+ var b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
861
+ var b64tab = {};
862
+
863
+ for (var i = 0, l = b64chars.length; i < l; i++) {
864
+ b64tab[b64chars.charAt(i)] = i;
865
+ }
866
+
867
+ var cb_utob = function(c) {
868
+ var cc = c.charCodeAt(0);
869
+ return cc < 0x80 ? c
870
+ : cc < 0x800 ? fromCharCode(0xc0 | (cc >>> 6)) +
871
+ fromCharCode(0x80 | (cc & 0x3f))
872
+ : fromCharCode(0xe0 | ((cc >>> 12) & 0x0f)) +
873
+ fromCharCode(0x80 | ((cc >>> 6) & 0x3f)) +
874
+ fromCharCode(0x80 | ( cc & 0x3f));
875
+ };
876
+
877
+ var utob = function(u) {
878
+ return u.replace(/[^\x00-\x7F]/g, cb_utob);
879
+ };
880
+
881
+ var cb_encode = function(ccc) {
882
+ var padlen = [0, 2, 1][ccc.length % 3];
883
+ var ord = ccc.charCodeAt(0) << 16
884
+ | ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8)
885
+ | ((ccc.length > 2 ? ccc.charCodeAt(2) : 0));
886
+ var chars = [
887
+ b64chars.charAt( ord >>> 18),
888
+ b64chars.charAt((ord >>> 12) & 63),
889
+ padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63),
890
+ padlen >= 1 ? '=' : b64chars.charAt(ord & 63)
891
+ ];
892
+ return chars.join('');
893
+ };
894
+
895
+ var btoa = window.btoa || function(b) {
896
+ return b.replace(/[\s\S]{1,3}/g, cb_encode);
897
+ };
898
+
899
+ Pusher.Base64 = Base64;
900
+
901
+ }).call(this);
902
+
903
+ (function() {
904
+
905
+ function JSONPRequest(options) {
906
+ this.options = options;
907
+ }
908
+
909
+ JSONPRequest.send = function(options, callback) {
910
+ var request = new Pusher.JSONPRequest({
911
+ url: options.url,
912
+ receiver: options.receiverName,
913
+ tagPrefix: options.tagPrefix
914
+ });
915
+ var id = options.receiver.register(function(error, result) {
916
+ request.cleanup();
917
+ callback(error, result);
918
+ });
919
+
920
+ return request.send(id, options.data, function(error) {
921
+ var callback = options.receiver.unregister(id);
922
+ if (callback) {
923
+ callback(error);
924
+ }
925
+ });
926
+ };
927
+
928
+ var prototype = JSONPRequest.prototype;
929
+
930
+ prototype.send = function(id, data, callback) {
931
+ if (this.script) {
932
+ return false;
933
+ }
934
+
935
+ var tagPrefix = this.options.tagPrefix || "_pusher_jsonp_";
936
+
937
+ var params = Pusher.Util.extend(
938
+ {}, data, { receiver: this.options.receiver }
939
+ );
940
+ var query = Pusher.Util.map(
941
+ Pusher.Util.flatten(
942
+ encodeData(
943
+ Pusher.Util.filterObject(params, function(value) {
944
+ return value !== undefined;
945
+ })
946
+ )
947
+ ),
948
+ Pusher.Util.method("join", "=")
949
+ ).join("&");
950
+
951
+ this.script = document.createElement("script");
952
+ this.script.id = tagPrefix + id;
953
+ this.script.src = this.options.url + "/" + id + "?" + query;
954
+ this.script.type = "text/javascript";
955
+ this.script.charset = "UTF-8";
956
+ this.script.onerror = this.script.onload = callback;
957
+
958
+ // Opera<11.6 hack for missing onerror callback
959
+ if (this.script.async === undefined && document.attachEvent) {
960
+ if (/opera/i.test(navigator.userAgent)) {
961
+ var receiverName = this.options.receiver || "Pusher.JSONP.receive";
962
+ this.errorScript = document.createElement("script");
963
+ this.errorScript.text = receiverName + "(" + id + ", true);";
964
+ this.script.async = this.errorScript.async = false;
965
+ }
966
+ }
967
+
968
+ var self = this;
969
+ this.script.onreadystatechange = function() {
970
+ if (self.script && /loaded|complete/.test(self.script.readyState)) {
971
+ callback(true);
972
+ }
973
+ };
974
+
975
+ var head = document.getElementsByTagName('head')[0];
976
+ head.insertBefore(this.script, head.firstChild);
977
+ if (this.errorScript) {
978
+ head.insertBefore(this.errorScript, this.script.nextSibling);
979
+ }
980
+
981
+ return true;
982
+ };
983
+
984
+ prototype.cleanup = function() {
985
+ if (this.script && this.script.parentNode) {
986
+ this.script.parentNode.removeChild(this.script);
987
+ this.script = null;
988
+ }
989
+ if (this.errorScript && this.errorScript.parentNode) {
990
+ this.errorScript.parentNode.removeChild(this.errorScript);
991
+ this.errorScript = null;
992
+ }
993
+ };
994
+
995
+ function encodeData(data) {
996
+ return Pusher.Util.mapObject(data, function(value) {
997
+ if (typeof value === "object") {
998
+ value = JSON.stringify(value);
999
+ }
1000
+ return encodeURIComponent(Pusher.Base64.encode(value.toString()));
1001
+ });
1002
+ }
1003
+
1004
+ Pusher.JSONPRequest = JSONPRequest;
1005
+
1006
+ }).call(this);
1007
+
1008
+ (function() {
1009
+
1010
+ function JSONPReceiver() {
1011
+ this.lastId = 0;
1012
+ this.callbacks = {};
1013
+ }
1014
+
1015
+ var prototype = JSONPReceiver.prototype;
1016
+
1017
+ prototype.register = function(callback) {
1018
+ this.lastId++;
1019
+ var id = this.lastId;
1020
+ this.callbacks[id] = callback;
1021
+ return id;
1022
+ };
1023
+
1024
+ prototype.unregister = function(id) {
1025
+ if (this.callbacks[id]) {
1026
+ var callback = this.callbacks[id];
1027
+ delete this.callbacks[id];
1028
+ return callback;
1029
+ } else {
1030
+ return null;
1031
+ }
1032
+ };
1033
+
1034
+ prototype.receive = function(id, error, data) {
1035
+ var callback = this.unregister(id);
1036
+ if (callback) {
1037
+ callback(error, data);
1038
+ }
1039
+ };
1040
+
1041
+ Pusher.JSONPReceiver = JSONPReceiver;
1042
+ Pusher.JSONP = new JSONPReceiver();
1043
+
1044
+ }).call(this);
1045
+
1046
+ (function() {
1047
+ function Timeline(key, session, options) {
1048
+ this.key = key;
1049
+ this.session = session;
1050
+ this.events = [];
1051
+ this.options = options || {};
1052
+ this.sent = 0;
1053
+ this.uniqueID = 0;
1054
+ }
1055
+ var prototype = Timeline.prototype;
1056
+
1057
+ // Log levels
1058
+ Timeline.ERROR = 3;
1059
+ Timeline.INFO = 6;
1060
+ Timeline.DEBUG = 7;
1061
+
1062
+ prototype.log = function(level, event) {
1063
+ if (this.options.level === undefined || level <= this.options.level) {
1064
+ this.events.push(
1065
+ Pusher.Util.extend({}, event, {
1066
+ timestamp: Pusher.Util.now(),
1067
+ level: level
1068
+ })
1069
+ );
1070
+ if (this.options.limit && this.events.length > this.options.limit) {
1071
+ this.events.shift();
1072
+ }
1073
+ }
1074
+ };
1075
+
1076
+ prototype.error = function(event) {
1077
+ this.log(Timeline.ERROR, event);
1078
+ };
1079
+
1080
+ prototype.info = function(event) {
1081
+ this.log(Timeline.INFO, event);
1082
+ };
1083
+
1084
+ prototype.debug = function(event) {
1085
+ this.log(Timeline.DEBUG, event);
1086
+ };
1087
+
1088
+ prototype.isEmpty = function() {
1089
+ return this.events.length === 0;
1090
+ };
1091
+
1092
+ prototype.send = function(sendJSONP, callback) {
1093
+ var self = this;
1094
+
1095
+ var data = {};
1096
+ if (this.sent === 0) {
1097
+ data = Pusher.Util.extend({
1098
+ key: this.key,
1099
+ features: this.options.features,
1100
+ version: this.options.version
1101
+ }, this.options.params || {});
1102
+ }
1103
+ data.session = this.session;
1104
+ data.timeline = this.events;
1105
+ data = Pusher.Util.filterObject(data, function(v) {
1106
+ return v !== undefined;
1107
+ });
1108
+
1109
+ this.events = [];
1110
+ sendJSONP(data, function(error, result) {
1111
+ if (!error) {
1112
+ self.sent++;
1113
+ }
1114
+ callback(error, result);
1115
+ });
1116
+
1117
+ return true;
1118
+ };
1119
+
1120
+ prototype.generateUniqueID = function() {
1121
+ this.uniqueID++;
1122
+ return this.uniqueID;
1123
+ };
1124
+
1125
+ Pusher.Timeline = Timeline;
1126
+ }).call(this);
1127
+
1128
+ (function() {
1129
+ function TimelineSender(timeline, options) {
1130
+ this.timeline = timeline;
1131
+ this.options = options || {};
1132
+ }
1133
+ var prototype = TimelineSender.prototype;
1134
+
1135
+ prototype.send = function(callback) {
1136
+ if (this.timeline.isEmpty()) {
1137
+ return;
1138
+ }
1139
+
1140
+ var options = this.options;
1141
+ var scheme = "http" + (this.isEncrypted() ? "s" : "") + "://";
1142
+
1143
+ var sendJSONP = function(data, callback) {
1144
+ return Pusher.JSONPRequest.send({
1145
+ data: data,
1146
+ url: scheme + options.host + options.path,
1147
+ receiver: Pusher.JSONP
1148
+ }, callback);
1149
+ };
1150
+ this.timeline.send(sendJSONP, callback);
1151
+ };
1152
+
1153
+ prototype.isEncrypted = function() {
1154
+ return !!this.options.encrypted;
1155
+ };
1156
+
1157
+ Pusher.TimelineSender = TimelineSender;
1158
+ }).call(this);
1159
+
1160
+ ;(function() {
1161
+ /** Launches all substrategies and emits prioritized connected transports.
1162
+ *
1163
+ * @param {Array} strategies
1164
+ */
1165
+ function BestConnectedEverStrategy(strategies) {
1166
+ this.strategies = strategies;
1167
+ }
1168
+ var prototype = BestConnectedEverStrategy.prototype;
1169
+
1170
+ prototype.isSupported = function() {
1171
+ return Pusher.Util.any(this.strategies, Pusher.Util.method("isSupported"));
1172
+ };
1173
+
1174
+ prototype.connect = function(minPriority, callback) {
1175
+ return connect(this.strategies, minPriority, function(i, runners) {
1176
+ return function(error, handshake) {
1177
+ runners[i].error = error;
1178
+ if (error) {
1179
+ if (allRunnersFailed(runners)) {
1180
+ callback(true);
1181
+ }
1182
+ return;
1183
+ }
1184
+ Pusher.Util.apply(runners, function(runner) {
1185
+ runner.forceMinPriority(handshake.transport.priority);
1186
+ });
1187
+ callback(null, handshake);
1188
+ };
1189
+ });
1190
+ };
1191
+
1192
+ /** Connects to all strategies in parallel.
1193
+ *
1194
+ * Callback builder should be a function that takes two arguments: index
1195
+ * and a list of runners. It should return another function that will be
1196
+ * passed to the substrategy with given index. Runners can be aborted using
1197
+ * abortRunner(s) functions from this class.
1198
+ *
1199
+ * @param {Array} strategies
1200
+ * @param {Function} callbackBuilder
1201
+ * @return {Object} strategy runner
1202
+ */
1203
+ function connect(strategies, minPriority, callbackBuilder) {
1204
+ var runners = Pusher.Util.map(strategies, function(strategy, i, _, rs) {
1205
+ return strategy.connect(minPriority, callbackBuilder(i, rs));
1206
+ });
1207
+ return {
1208
+ abort: function() {
1209
+ Pusher.Util.apply(runners, abortRunner);
1210
+ },
1211
+ forceMinPriority: function(p) {
1212
+ Pusher.Util.apply(runners, function(runner) {
1213
+ runner.forceMinPriority(p);
1214
+ });
1215
+ }
1216
+ };
1217
+ }
1218
+
1219
+ function allRunnersFailed(runners) {
1220
+ return Pusher.Util.all(runners, function(runner) {
1221
+ return Boolean(runner.error);
1222
+ });
1223
+ }
1224
+
1225
+ function abortRunner(runner) {
1226
+ if (!runner.error && !runner.aborted) {
1227
+ runner.abort();
1228
+ runner.aborted = true;
1229
+ }
1230
+ }
1231
+
1232
+ Pusher.BestConnectedEverStrategy = BestConnectedEverStrategy;
1233
+ }).call(this);
1234
+
1235
+ ;(function() {
1236
+ /** Caches last successful transport and uses it for following attempts.
1237
+ *
1238
+ * @param {Strategy} strategy
1239
+ * @param {Object} transports
1240
+ * @param {Object} options
1241
+ */
1242
+ function CachedStrategy(strategy, transports, options) {
1243
+ this.strategy = strategy;
1244
+ this.transports = transports;
1245
+ this.ttl = options.ttl || 1800*1000;
1246
+ this.timeline = options.timeline;
1247
+ }
1248
+ var prototype = CachedStrategy.prototype;
1249
+
1250
+ prototype.isSupported = function() {
1251
+ return this.strategy.isSupported();
1252
+ };
1253
+
1254
+ prototype.connect = function(minPriority, callback) {
1255
+ var info = fetchTransportInfo();
1256
+
1257
+ var strategies = [this.strategy];
1258
+ if (info && info.timestamp + this.ttl >= Pusher.Util.now()) {
1259
+ var transport = this.transports[info.transport];
1260
+ if (transport) {
1261
+ this.timeline.info({ cached: true, transport: info.transport });
1262
+ strategies.push(new Pusher.SequentialStrategy([transport], {
1263
+ timeout: info.latency * 2,
1264
+ failFast: true
1265
+ }));
1266
+ }
1267
+ }
1268
+
1269
+ var startTimestamp = Pusher.Util.now();
1270
+ var runner = strategies.pop().connect(
1271
+ minPriority,
1272
+ function cb(error, handshake) {
1273
+ if (error) {
1274
+ flushTransportInfo();
1275
+ if (strategies.length > 0) {
1276
+ startTimestamp = Pusher.Util.now();
1277
+ runner = strategies.pop().connect(minPriority, cb);
1278
+ } else {
1279
+ callback(error);
1280
+ }
1281
+ } else {
1282
+ var latency = Pusher.Util.now() - startTimestamp;
1283
+ storeTransportInfo(handshake.transport.name, latency);
1284
+ callback(null, handshake);
1285
+ }
1286
+ }
1287
+ );
1288
+
1289
+ return {
1290
+ abort: function() {
1291
+ runner.abort();
1292
+ },
1293
+ forceMinPriority: function(p) {
1294
+ minPriority = p;
1295
+ if (runner) {
1296
+ runner.forceMinPriority(p);
1297
+ }
1298
+ }
1299
+ };
1300
+ };
1301
+
1302
+ function fetchTransportInfo() {
1303
+ var storage = Pusher.Util.getLocalStorage();
1304
+ if (storage) {
1305
+ var info = storage.pusherTransport;
1306
+ if (info) {
1307
+ return JSON.parse(storage.pusherTransport);
1308
+ }
1309
+ }
1310
+ return null;
1311
+ }
1312
+
1313
+ function storeTransportInfo(transport, latency) {
1314
+ var storage = Pusher.Util.getLocalStorage();
1315
+ if (storage) {
1316
+ try {
1317
+ storage.pusherTransport = JSON.stringify({
1318
+ timestamp: Pusher.Util.now(),
1319
+ transport: transport,
1320
+ latency: latency
1321
+ });
1322
+ } catch(e) {
1323
+ // catch over quota exceptions raised by localStorage
1324
+ }
1325
+ }
1326
+ }
1327
+
1328
+ function flushTransportInfo() {
1329
+ var storage = Pusher.Util.getLocalStorage();
1330
+ if (storage && storage.pusherTransport) {
1331
+ delete storage.pusherTransport;
1332
+ }
1333
+ }
1334
+
1335
+ Pusher.CachedStrategy = CachedStrategy;
1336
+ }).call(this);
1337
+
1338
+ ;(function() {
1339
+ /** Runs substrategy after specified delay.
1340
+ *
1341
+ * Options:
1342
+ * - delay - time in miliseconds to delay the substrategy attempt
1343
+ *
1344
+ * @param {Strategy} strategy
1345
+ * @param {Object} options
1346
+ */
1347
+ function DelayedStrategy(strategy, options) {
1348
+ this.strategy = strategy;
1349
+ this.options = { delay: options.delay };
1350
+ }
1351
+ var prototype = DelayedStrategy.prototype;
1352
+
1353
+ prototype.isSupported = function() {
1354
+ return this.strategy.isSupported();
1355
+ };
1356
+
1357
+ prototype.connect = function(minPriority, callback) {
1358
+ var strategy = this.strategy;
1359
+ var runner;
1360
+ var timer = new Pusher.Timer(this.options.delay, function() {
1361
+ runner = strategy.connect(minPriority, callback);
1362
+ });
1363
+
1364
+ return {
1365
+ abort: function() {
1366
+ timer.ensureAborted();
1367
+ if (runner) {
1368
+ runner.abort();
1369
+ }
1370
+ },
1371
+ forceMinPriority: function(p) {
1372
+ minPriority = p;
1373
+ if (runner) {
1374
+ runner.forceMinPriority(p);
1375
+ }
1376
+ }
1377
+ };
1378
+ };
1379
+
1380
+ Pusher.DelayedStrategy = DelayedStrategy;
1381
+ }).call(this);
1382
+
1383
+ ;(function() {
1384
+ /** Launches the substrategy and terminates on the first open connection.
1385
+ *
1386
+ * @param {Strategy} strategy
1387
+ */
1388
+ function FirstConnectedStrategy(strategy) {
1389
+ this.strategy = strategy;
1390
+ }
1391
+ var prototype = FirstConnectedStrategy.prototype;
1392
+
1393
+ prototype.isSupported = function() {
1394
+ return this.strategy.isSupported();
1395
+ };
1396
+
1397
+ prototype.connect = function(minPriority, callback) {
1398
+ var runner = this.strategy.connect(
1399
+ minPriority,
1400
+ function(error, handshake) {
1401
+ if (handshake) {
1402
+ runner.abort();
1403
+ }
1404
+ callback(error, handshake);
1405
+ }
1406
+ );
1407
+ return runner;
1408
+ };
1409
+
1410
+ Pusher.FirstConnectedStrategy = FirstConnectedStrategy;
1411
+ }).call(this);
1412
+
1413
+ ;(function() {
1414
+ /** Proxies method calls to one of substrategies basing on the test function.
1415
+ *
1416
+ * @param {Function} test
1417
+ * @param {Strategy} trueBranch strategy used when test returns true
1418
+ * @param {Strategy} falseBranch strategy used when test returns false
1419
+ */
1420
+ function IfStrategy(test, trueBranch, falseBranch) {
1421
+ this.test = test;
1422
+ this.trueBranch = trueBranch;
1423
+ this.falseBranch = falseBranch;
1424
+ }
1425
+ var prototype = IfStrategy.prototype;
1426
+
1427
+ prototype.isSupported = function() {
1428
+ var branch = this.test() ? this.trueBranch : this.falseBranch;
1429
+ return branch.isSupported();
1430
+ };
1431
+
1432
+ prototype.connect = function(minPriority, callback) {
1433
+ var branch = this.test() ? this.trueBranch : this.falseBranch;
1434
+ return branch.connect(minPriority, callback);
1435
+ };
1436
+
1437
+ Pusher.IfStrategy = IfStrategy;
1438
+ }).call(this);
1439
+
1440
+ ;(function() {
1441
+ /** Loops through strategies with optional timeouts.
1442
+ *
1443
+ * Options:
1444
+ * - loop - whether it should loop through the substrategy list
1445
+ * - timeout - initial timeout for a single substrategy
1446
+ * - timeoutLimit - maximum timeout
1447
+ *
1448
+ * @param {Strategy[]} strategies
1449
+ * @param {Object} options
1450
+ */
1451
+ function SequentialStrategy(strategies, options) {
1452
+ this.strategies = strategies;
1453
+ this.loop = Boolean(options.loop);
1454
+ this.failFast = Boolean(options.failFast);
1455
+ this.timeout = options.timeout;
1456
+ this.timeoutLimit = options.timeoutLimit;
1457
+ }
1458
+ var prototype = SequentialStrategy.prototype;
1459
+
1460
+ prototype.isSupported = function() {
1461
+ return Pusher.Util.any(this.strategies, Pusher.Util.method("isSupported"));
1462
+ };
1463
+
1464
+ prototype.connect = function(minPriority, callback) {
1465
+ var self = this;
1466
+
1467
+ var strategies = this.strategies;
1468
+ var current = 0;
1469
+ var timeout = this.timeout;
1470
+ var runner = null;
1471
+
1472
+ var tryNextStrategy = function(error, handshake) {
1473
+ if (handshake) {
1474
+ callback(null, handshake);
1475
+ } else {
1476
+ current = current + 1;
1477
+ if (self.loop) {
1478
+ current = current % strategies.length;
1479
+ }
1480
+
1481
+ if (current < strategies.length) {
1482
+ if (timeout) {
1483
+ timeout = timeout * 2;
1484
+ if (self.timeoutLimit) {
1485
+ timeout = Math.min(timeout, self.timeoutLimit);
1486
+ }
1487
+ }
1488
+ runner = self.tryStrategy(
1489
+ strategies[current],
1490
+ minPriority,
1491
+ { timeout: timeout, failFast: self.failFast },
1492
+ tryNextStrategy
1493
+ );
1494
+ } else {
1495
+ callback(true);
1496
+ }
1497
+ }
1498
+ };
1499
+
1500
+ runner = this.tryStrategy(
1501
+ strategies[current],
1502
+ minPriority,
1503
+ { timeout: timeout, failFast: this.failFast },
1504
+ tryNextStrategy
1505
+ );
1506
+
1507
+ return {
1508
+ abort: function() {
1509
+ runner.abort();
1510
+ },
1511
+ forceMinPriority: function(p) {
1512
+ minPriority = p;
1513
+ if (runner) {
1514
+ runner.forceMinPriority(p);
1515
+ }
1516
+ }
1517
+ };
1518
+ };
1519
+
1520
+ /** @private */
1521
+ prototype.tryStrategy = function(strategy, minPriority, options, callback) {
1522
+ var timer = null;
1523
+ var runner = null;
1524
+
1525
+ runner = strategy.connect(minPriority, function(error, handshake) {
1526
+ if (error && timer && timer.isRunning() && !options.failFast) {
1527
+ // advance to the next strategy after the timeout
1528
+ return;
1529
+ }
1530
+ if (timer) {
1531
+ timer.ensureAborted();
1532
+ }
1533
+ callback(error, handshake);
1534
+ });
1535
+
1536
+ if (options.timeout > 0) {
1537
+ timer = new Pusher.Timer(options.timeout, function() {
1538
+ runner.abort();
1539
+ callback(true);
1540
+ });
1541
+ }
1542
+
1543
+ return {
1544
+ abort: function() {
1545
+ if (timer) {
1546
+ timer.ensureAborted();
1547
+ }
1548
+ runner.abort();
1549
+ },
1550
+ forceMinPriority: function(p) {
1551
+ runner.forceMinPriority(p);
1552
+ }
1553
+ };
1554
+ };
1555
+
1556
+ Pusher.SequentialStrategy = SequentialStrategy;
1557
+ }).call(this);
1558
+
1559
+ ;(function() {
1560
+ /** Provides a strategy interface for transports.
1561
+ *
1562
+ * @param {String} name
1563
+ * @param {Number} priority
1564
+ * @param {Class} transport
1565
+ * @param {Object} options
1566
+ */
1567
+ function TransportStrategy(name, priority, transport, options) {
1568
+ this.name = name;
1569
+ this.priority = priority;
1570
+ this.transport = transport;
1571
+ this.options = options || {};
1572
+ }
1573
+ var prototype = TransportStrategy.prototype;
1574
+
1575
+ /** Returns whether the transport is supported in the browser.
1576
+ *
1577
+ * @returns {Boolean}
1578
+ */
1579
+ prototype.isSupported = function() {
1580
+ return this.transport.isSupported({
1581
+ disableFlash: !!this.options.disableFlash
1582
+ });
1583
+ };
1584
+
1585
+ /** Launches a connection attempt and returns a strategy runner.
1586
+ *
1587
+ * @param {Function} callback
1588
+ * @return {Object} strategy runner
1589
+ */
1590
+ prototype.connect = function(minPriority, callback) {
1591
+ if (!this.transport.isSupported()) {
1592
+ return failAttempt(new Pusher.Errors.UnsupportedStrategy(), callback);
1593
+ } else if (this.priority < minPriority) {
1594
+ return failAttempt(new Pusher.Errors.TransportPriorityTooLow(), callback);
1595
+ }
1596
+
1597
+ var self = this;
1598
+ var connected = false;
1599
+
1600
+ var transport = this.transport.createConnection(
1601
+ this.name, this.priority, this.options.key, this.options
1602
+ );
1603
+
1604
+ var onInitialized = function() {
1605
+ transport.unbind("initialized", onInitialized);
1606
+ transport.connect();
1607
+ };
1608
+ var onOpen = function() {
1609
+ var handshake = new Pusher.Handshake(transport, function(result) {
1610
+ connected = true;
1611
+ unbindListeners();
1612
+ callback(null, result);
1613
+ });
1614
+ };
1615
+ var onError = function(error) {
1616
+ unbindListeners();
1617
+ callback(error);
1618
+ };
1619
+ var onClosed = function() {
1620
+ unbindListeners();
1621
+ callback(new Pusher.Errors.TransportClosed(transport));
1622
+ };
1623
+
1624
+ var unbindListeners = function() {
1625
+ transport.unbind("initialized", onInitialized);
1626
+ transport.unbind("open", onOpen);
1627
+ transport.unbind("error", onError);
1628
+ transport.unbind("closed", onClosed);
1629
+ };
1630
+
1631
+ transport.bind("initialized", onInitialized);
1632
+ transport.bind("open", onOpen);
1633
+ transport.bind("error", onError);
1634
+ transport.bind("closed", onClosed);
1635
+
1636
+ // connect will be called automatically after initialization
1637
+ transport.initialize();
1638
+
1639
+ return {
1640
+ abort: function() {
1641
+ if (connected) {
1642
+ return;
1643
+ }
1644
+ unbindListeners();
1645
+ transport.close();
1646
+ },
1647
+ forceMinPriority: function(p) {
1648
+ if (connected) {
1649
+ return;
1650
+ }
1651
+ if (self.priority < p) {
1652
+ // TODO close transport in a nicer way
1653
+ transport.close();
1654
+ }
1655
+ }
1656
+ };
1657
+ };
1658
+
1659
+ function failAttempt(error, callback) {
1660
+ new Pusher.Timer(0, function() {
1661
+ callback(error);
1662
+ });
1663
+ return {
1664
+ abort: function() {},
1665
+ forceMinPriority: function() {}
1666
+ };
1667
+ }
1668
+
1669
+ Pusher.TransportStrategy = TransportStrategy;
1670
+ }).call(this);
1671
+
1672
+ ;(function() {
1673
+ /** Handles common logic for all transports.
1674
+ *
1675
+ * Transport is a low-level connection object that wraps a connection method
1676
+ * and exposes a simple evented interface for the connection state and
1677
+ * messaging. It does not implement Pusher-specific WebSocket protocol.
1678
+ *
1679
+ * Additionally, it fetches resources needed for transport to work and exposes
1680
+ * an interface for querying transport support and its features.
1681
+ *
1682
+ * This is an abstract class, please do not instantiate it.
1683
+ *
1684
+ * States:
1685
+ * - new - initial state after constructing the object
1686
+ * - initializing - during initialization phase, usually fetching resources
1687
+ * - intialized - ready to establish a connection
1688
+ * - connection - when connection is being established
1689
+ * - open - when connection ready to be used
1690
+ * - closed - after connection was closed be either side
1691
+ *
1692
+ * Emits:
1693
+ * - error - after the connection raised an error
1694
+ *
1695
+ * Options:
1696
+ * - encrypted - whether connection should use ssl
1697
+ * - hostEncrypted - host to connect to when connection is encrypted
1698
+ * - hostUnencrypted - host to connect to when connection is not encrypted
1699
+ *
1700
+ * @param {String} key application key
1701
+ * @param {Object} options
1702
+ */
1703
+ function AbstractTransport(name, priority, key, options) {
1704
+ Pusher.EventsDispatcher.call(this);
1705
+
1706
+ this.name = name;
1707
+ this.priority = priority;
1708
+ this.key = key;
1709
+ this.state = "new";
1710
+ this.timeline = options.timeline;
1711
+ this.id = this.timeline.generateUniqueID();
1712
+
1713
+ this.options = {
1714
+ encrypted: Boolean(options.encrypted),
1715
+ hostUnencrypted: options.hostUnencrypted,
1716
+ hostEncrypted: options.hostEncrypted
1717
+ };
1718
+ }
1719
+ var prototype = AbstractTransport.prototype;
1720
+ Pusher.Util.extend(prototype, Pusher.EventsDispatcher.prototype);
1721
+
1722
+ /** Checks whether the transport is supported in the browser.
1723
+ *
1724
+ * @returns {Boolean}
1725
+ */
1726
+ AbstractTransport.isSupported = function() {
1727
+ return false;
1728
+ };
1729
+
1730
+ /** Checks whether the transport handles ping/pong on itself.
1731
+ *
1732
+ * @return {Boolean}
1733
+ */
1734
+ prototype.supportsPing = function() {
1735
+ return false;
1736
+ };
1737
+
1738
+ /** Initializes the transport.
1739
+ *
1740
+ * Fetches resources if needed and then transitions to initialized.
1741
+ */
1742
+ prototype.initialize = function() {
1743
+ this.timeline.info(this.buildTimelineMessage({
1744
+ transport: this.name + (this.options.encrypted ? "s" : "")
1745
+ }));
1746
+ this.timeline.debug(this.buildTimelineMessage({ method: "initialize" }));
1747
+
1748
+ this.changeState("initialized");
1749
+ };
1750
+
1751
+ /** Tries to establish a connection.
1752
+ *
1753
+ * @returns {Boolean} false if transport is in invalid state
1754
+ */
1755
+ prototype.connect = function() {
1756
+ var url = this.getURL(this.key, this.options);
1757
+ this.timeline.debug(this.buildTimelineMessage({
1758
+ method: "connect",
1759
+ url: url
1760
+ }));
1761
+
1762
+ if (this.socket || this.state !== "initialized") {
1763
+ return false;
1764
+ }
1765
+
1766
+ try {
1767
+ this.socket = this.createSocket(url);
1768
+ } catch (e) {
1769
+ var self = this;
1770
+ new Pusher.Timer(0, function() {
1771
+ self.onError(e);
1772
+ self.changeState("closed");
1773
+ });
1774
+ return false;
1775
+ }
1776
+
1777
+ this.bindListeners();
1778
+
1779
+ Pusher.debug("Connecting", { transport: this.name, url: url });
1780
+ this.changeState("connecting");
1781
+ return true;
1782
+ };
1783
+
1784
+ /** Closes the connection.
1785
+ *
1786
+ * @return {Boolean} true if there was a connection to close
1787
+ */
1788
+ prototype.close = function() {
1789
+ this.timeline.debug(this.buildTimelineMessage({ method: "close" }));
1790
+
1791
+ if (this.socket) {
1792
+ this.socket.close();
1793
+ return true;
1794
+ } else {
1795
+ return false;
1796
+ }
1797
+ };
1798
+
1799
+ /** Sends data over the open connection.
1800
+ *
1801
+ * @param {String} data
1802
+ * @return {Boolean} true only when in the "open" state
1803
+ */
1804
+ prototype.send = function(data) {
1805
+ this.timeline.debug(this.buildTimelineMessage({
1806
+ method: "send",
1807
+ data: data
1808
+ }));
1809
+
1810
+ if (this.state === "open") {
1811
+ // Workaround for MobileSafari bug (see https://gist.github.com/2052006)
1812
+ var self = this;
1813
+ setTimeout(function() {
1814
+ self.socket.send(data);
1815
+ }, 0);
1816
+ return true;
1817
+ } else {
1818
+ return false;
1819
+ }
1820
+ };
1821
+
1822
+ prototype.requestPing = function() {
1823
+ this.emit("ping_request");
1824
+ };
1825
+
1826
+ /** @protected */
1827
+ prototype.onOpen = function() {
1828
+ this.changeState("open");
1829
+ this.socket.onopen = undefined;
1830
+ };
1831
+
1832
+ /** @protected */
1833
+ prototype.onError = function(error) {
1834
+ this.emit("error", { type: 'WebSocketError', error: error });
1835
+ this.timeline.error(this.buildTimelineMessage({
1836
+ error: getErrorDetails(error)
1837
+ }));
1838
+ };
1839
+
1840
+ /** @protected */
1841
+ prototype.onClose = function(closeEvent) {
1842
+ this.changeState("closed", closeEvent);
1843
+ this.socket = undefined;
1844
+ };
1845
+
1846
+ /** @protected */
1847
+ prototype.onMessage = function(message) {
1848
+ this.timeline.debug(this.buildTimelineMessage({ message: message.data }));
1849
+ this.emit("message", message);
1850
+ };
1851
+
1852
+ /** @protected */
1853
+ prototype.bindListeners = function() {
1854
+ var self = this;
1855
+
1856
+ this.socket.onopen = function() { self.onOpen(); };
1857
+ this.socket.onerror = function(error) { self.onError(error); };
1858
+ this.socket.onclose = function(closeEvent) { self.onClose(closeEvent); };
1859
+ this.socket.onmessage = function(message) { self.onMessage(message); };
1860
+ };
1861
+
1862
+ /** @protected */
1863
+ prototype.createSocket = function(url) {
1864
+ return null;
1865
+ };
1866
+
1867
+ /** @protected */
1868
+ prototype.getScheme = function() {
1869
+ return this.options.encrypted ? "wss" : "ws";
1870
+ };
1871
+
1872
+ /** @protected */
1873
+ prototype.getBaseURL = function() {
1874
+ var host;
1875
+ if (this.options.encrypted) {
1876
+ host = this.options.hostEncrypted;
1877
+ } else {
1878
+ host = this.options.hostUnencrypted;
1879
+ }
1880
+ return this.getScheme() + "://" + host;
1881
+ };
1882
+
1883
+ /** @protected */
1884
+ prototype.getPath = function() {
1885
+ return "/app/" + this.key;
1886
+ };
1887
+
1888
+ /** @protected */
1889
+ prototype.getQueryString = function() {
1890
+ return "?protocol=" + Pusher.PROTOCOL +
1891
+ "&client=js&version=" + Pusher.VERSION;
1892
+ };
1893
+
1894
+ /** @protected */
1895
+ prototype.getURL = function() {
1896
+ return this.getBaseURL() + this.getPath() + this.getQueryString();
1897
+ };
1898
+
1899
+ /** @protected */
1900
+ prototype.changeState = function(state, params) {
1901
+ this.state = state;
1902
+ this.timeline.info(this.buildTimelineMessage({
1903
+ state: state,
1904
+ params: params
1905
+ }));
1906
+ this.emit(state, params);
1907
+ };
1908
+
1909
+ /** @protected */
1910
+ prototype.buildTimelineMessage = function(message) {
1911
+ return Pusher.Util.extend({ cid: this.id }, message);
1912
+ };
1913
+
1914
+ function getErrorDetails(error) {
1915
+ if (typeof error === "string") {
1916
+ return error;
1917
+ }
1918
+ if (typeof error === "object") {
1919
+ return Pusher.Util.mapObject(error, function(value) {
1920
+ var valueType = typeof value;
1921
+ if (valueType === "object" || valueType == "function") {
1922
+ return valueType;
1923
+ }
1924
+ return value;
1925
+ });
1926
+ }
1927
+ return typeof error;
1928
+ }
1929
+
1930
+ Pusher.AbstractTransport = AbstractTransport;
1931
+ }).call(this);
1932
+
1933
+ ;(function() {
1934
+ /** Transport using Flash to emulate WebSockets.
1935
+ *
1936
+ * @see AbstractTransport
1937
+ */
1938
+ function FlashTransport(name, priority, key, options) {
1939
+ Pusher.AbstractTransport.call(this, name, priority, key, options);
1940
+ }
1941
+ var prototype = FlashTransport.prototype;
1942
+ Pusher.Util.extend(prototype, Pusher.AbstractTransport.prototype);
1943
+
1944
+ /** Creates a new instance of FlashTransport.
1945
+ *
1946
+ * @param {String} key
1947
+ * @param {Object} options
1948
+ * @return {FlashTransport}
1949
+ */
1950
+ FlashTransport.createConnection = function(name, priority, key, options) {
1951
+ return new FlashTransport(name, priority, key, options);
1952
+ };
1953
+
1954
+ /** Checks whether Flash is supported in the browser.
1955
+ *
1956
+ * It is possible to disable flash by passing an envrionment object with the
1957
+ * disableFlash property set to true.
1958
+ *
1959
+ * @see AbstractTransport.isSupported
1960
+ * @param {Object} environment
1961
+ * @returns {Boolean}
1962
+ */
1963
+ FlashTransport.isSupported = function(environment) {
1964
+ if (environment && environment.disableFlash) {
1965
+ return false;
1966
+ }
1967
+ try {
1968
+ return Boolean(new ActiveXObject('ShockwaveFlash.ShockwaveFlash'));
1969
+ } catch (e) {
1970
+ return Boolean(
1971
+ navigator &&
1972
+ navigator.mimeTypes &&
1973
+ navigator.mimeTypes["application/x-shockwave-flash"] !== undefined
1974
+ );
1975
+ }
1976
+ };
1977
+
1978
+ /** Fetches flashfallback dependency if needed.
1979
+ *
1980
+ * Sets WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR to true (if not set before)
1981
+ * and WEB_SOCKET_SWF_LOCATION to Pusher's cdn before loading Flash resources.
1982
+ *
1983
+ * @see AbstractTransport.prototype.initialize
1984
+ */
1985
+ prototype.initialize = function() {
1986
+ var self = this;
1987
+
1988
+ this.timeline.info(this.buildTimelineMessage({
1989
+ transport: this.name + (this.options.encrypted ? "s" : "")
1990
+ }));
1991
+ this.timeline.debug(this.buildTimelineMessage({ method: "initialize" }));
1992
+ this.changeState("initializing");
1993
+
1994
+ if (window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR === undefined) {
1995
+ window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;
1996
+ }
1997
+ window.WEB_SOCKET_SWF_LOCATION = Pusher.Dependencies.getRoot() +
1998
+ "/WebSocketMain.swf";
1999
+ Pusher.Dependencies.load("flashfallback", function() {
2000
+ self.changeState("initialized");
2001
+ });
2002
+ };
2003
+
2004
+ /** @protected */
2005
+ prototype.createSocket = function(url) {
2006
+ return new FlashWebSocket(url);
2007
+ };
2008
+
2009
+ /** @protected */
2010
+ prototype.getQueryString = function() {
2011
+ return Pusher.AbstractTransport.prototype.getQueryString.call(this) +
2012
+ "&flash=true";
2013
+ };
2014
+
2015
+ Pusher.FlashTransport = FlashTransport;
2016
+ }).call(this);
2017
+
2018
+ ;(function() {
2019
+ /** Fallback transport using SockJS.
2020
+ *
2021
+ * @see AbstractTransport
2022
+ */
2023
+ function SockJSTransport(name, priority, key, options) {
2024
+ Pusher.AbstractTransport.call(this, name, priority, key, options);
2025
+ this.options.ignoreNullOrigin = options.ignoreNullOrigin;
2026
+ }
2027
+ var prototype = SockJSTransport.prototype;
2028
+ Pusher.Util.extend(prototype, Pusher.AbstractTransport.prototype);
2029
+
2030
+ /** Creates a new instance of SockJSTransport.
2031
+ *
2032
+ * @param {String} key
2033
+ * @param {Object} options
2034
+ * @return {SockJSTransport}
2035
+ */
2036
+ SockJSTransport.createConnection = function(name, priority, key, options) {
2037
+ return new SockJSTransport(name, priority, key, options);
2038
+ };
2039
+
2040
+ /** Assumes that SockJS is always supported.
2041
+ *
2042
+ * @returns {Boolean} always true
2043
+ */
2044
+ SockJSTransport.isSupported = function() {
2045
+ return true;
2046
+ };
2047
+
2048
+ /** Fetches sockjs dependency if needed.
2049
+ *
2050
+ * @see AbstractTransport.prototype.initialize
2051
+ */
2052
+ prototype.initialize = function() {
2053
+ var self = this;
2054
+
2055
+ this.timeline.info(this.buildTimelineMessage({
2056
+ transport: this.name + (this.options.encrypted ? "s" : "")
2057
+ }));
2058
+ this.timeline.debug(this.buildTimelineMessage({ method: "initialize" }));
2059
+
2060
+ this.changeState("initializing");
2061
+ Pusher.Dependencies.load("sockjs", function() {
2062
+ self.changeState("initialized");
2063
+ });
2064
+ };
2065
+
2066
+ /** Always returns true, since SockJS handles ping on its own.
2067
+ *
2068
+ * @returns {Boolean} always true
2069
+ */
2070
+ prototype.supportsPing = function() {
2071
+ return true;
2072
+ };
2073
+
2074
+ /** @protected */
2075
+ prototype.createSocket = function(url) {
2076
+ return new SockJS(url, null, {
2077
+ js_path: Pusher.Dependencies.getPath("sockjs", {
2078
+ encrypted: this.options.encrypted
2079
+ }),
2080
+ ignore_null_origin: this.options.ignoreNullOrigin
2081
+ });
2082
+ };
2083
+
2084
+ /** @protected */
2085
+ prototype.getScheme = function() {
2086
+ return this.options.encrypted ? "https" : "http";
2087
+ };
2088
+
2089
+ /** @protected */
2090
+ prototype.getPath = function() {
2091
+ return "/pusher";
2092
+ };
2093
+
2094
+ /** @protected */
2095
+ prototype.getQueryString = function() {
2096
+ return "";
2097
+ };
2098
+
2099
+ /** Handles opening a SockJS connection to Pusher.
2100
+ *
2101
+ * Since SockJS does not handle custom paths, we send it immediately after
2102
+ * establishing the connection.
2103
+ *
2104
+ * @protected
2105
+ */
2106
+ prototype.onOpen = function() {
2107
+ this.socket.send(JSON.stringify({
2108
+ path: Pusher.AbstractTransport.prototype.getPath.call(this) +
2109
+ Pusher.AbstractTransport.prototype.getQueryString.call(this)
2110
+ }));
2111
+ this.changeState("open");
2112
+ this.socket.onopen = undefined;
2113
+ };
2114
+
2115
+ Pusher.SockJSTransport = SockJSTransport;
2116
+ }).call(this);
2117
+
2118
+ ;(function() {
2119
+ /** WebSocket transport.
2120
+ *
2121
+ * @see AbstractTransport
2122
+ */
2123
+ function WSTransport(name, priority, key, options) {
2124
+ Pusher.AbstractTransport.call(this, name, priority, key, options);
2125
+ }
2126
+ var prototype = WSTransport.prototype;
2127
+ Pusher.Util.extend(prototype, Pusher.AbstractTransport.prototype);
2128
+
2129
+ /** Creates a new instance of WSTransport.
2130
+ *
2131
+ * @param {String} key
2132
+ * @param {Object} options
2133
+ * @return {WSTransport}
2134
+ */
2135
+ WSTransport.createConnection = function(name, priority, key, options) {
2136
+ return new WSTransport(name, priority, key, options);
2137
+ };
2138
+
2139
+ /** Checks whether the browser supports WebSockets in any form.
2140
+ *
2141
+ * @returns {Boolean} true if browser supports WebSockets
2142
+ */
2143
+ WSTransport.isSupported = function() {
2144
+ return window.WebSocket !== undefined || window.MozWebSocket !== undefined;
2145
+ };
2146
+
2147
+ /** @protected */
2148
+ prototype.createSocket = function(url) {
2149
+ var constructor = window.WebSocket || window.MozWebSocket;
2150
+ return new constructor(url);
2151
+ };
2152
+
2153
+ /** @protected */
2154
+ prototype.getQueryString = function() {
2155
+ return Pusher.AbstractTransport.prototype.getQueryString.call(this) +
2156
+ "&flash=false";
2157
+ };
2158
+
2159
+ Pusher.WSTransport = WSTransport;
2160
+ }).call(this);
2161
+
2162
+ ;(function() {
2163
+ function AssistantToTheTransportManager(manager, transport, options) {
2164
+ this.manager = manager;
2165
+ this.transport = transport;
2166
+ this.minPingDelay = options.minPingDelay || 10000;
2167
+ this.maxPingDelay = options.maxPingDelay || Pusher.activity_timeout;
2168
+ this.pingDelay = null;
2169
+ }
2170
+ var prototype = AssistantToTheTransportManager.prototype;
2171
+
2172
+ prototype.createConnection = function(name, priority, key, options) {
2173
+ var connection = this.transport.createConnection(
2174
+ name, priority, key, options
2175
+ );
2176
+
2177
+ var self = this;
2178
+ var openTimestamp = null;
2179
+ var pingTimer = null;
2180
+
2181
+ var onOpen = function() {
2182
+ connection.unbind("open", onOpen);
2183
+
2184
+ openTimestamp = Pusher.Util.now();
2185
+ if (self.pingDelay) {
2186
+ pingTimer = setInterval(function() {
2187
+ if (pingTimer) {
2188
+ connection.requestPing();
2189
+ }
2190
+ }, self.pingDelay);
2191
+ }
2192
+
2193
+ connection.bind("closed", onClosed);
2194
+ };
2195
+ var onClosed = function(closeEvent) {
2196
+ connection.unbind("closed", onClosed);
2197
+ if (pingTimer) {
2198
+ clearInterval(pingTimer);
2199
+ pingTimer = null;
2200
+ }
2201
+
2202
+ if (closeEvent.wasClean) {
2203
+ return;
2204
+ }
2205
+
2206
+ if (openTimestamp) {
2207
+ var lifespan = Pusher.Util.now() - openTimestamp;
2208
+ if (lifespan < 2 * self.maxPingDelay) {
2209
+ self.manager.reportDeath();
2210
+ self.pingDelay = Math.max(lifespan / 2, self.minPingDelay);
2211
+ }
2212
+ }
2213
+ };
2214
+
2215
+ connection.bind("open", onOpen);
2216
+ return connection;
2217
+ };
2218
+
2219
+ prototype.isSupported = function(environment) {
2220
+ return this.manager.isAlive() && this.transport.isSupported(environment);
2221
+ };
2222
+
2223
+ Pusher.AssistantToTheTransportManager = AssistantToTheTransportManager;
2224
+ }).call(this);
2225
+
2226
+ ;(function() {
2227
+ function TransportManager(options) {
2228
+ this.options = options || {};
2229
+ this.livesLeft = this.options.lives || Infinity;
2230
+ }
2231
+ var prototype = TransportManager.prototype;
2232
+
2233
+ prototype.getAssistant = function(transport) {
2234
+ return new Pusher.AssistantToTheTransportManager(this, transport, {
2235
+ minPingDelay: this.options.minPingDelay,
2236
+ maxPingDelay: this.options.maxPingDelay
2237
+ });
2238
+ };
2239
+
2240
+ prototype.isAlive = function() {
2241
+ return this.livesLeft > 0;
2242
+ };
2243
+
2244
+ prototype.reportDeath = function() {
2245
+ this.livesLeft -= 1;
2246
+ };
2247
+
2248
+ Pusher.TransportManager = TransportManager;
2249
+ }).call(this);
2250
+
2251
+ ;(function() {
2252
+ var StrategyBuilder = {
2253
+ /** Transforms a JSON scheme to a strategy tree.
2254
+ *
2255
+ * @param {Array} scheme JSON strategy scheme
2256
+ * @param {Object} options a hash of symbols to be included in the scheme
2257
+ * @returns {Strategy} strategy tree that's represented by the scheme
2258
+ */
2259
+ build: function(scheme, options) {
2260
+ var context = Pusher.Util.extend({}, globalContext, options);
2261
+ return evaluate(scheme, context)[1].strategy;
2262
+ }
2263
+ };
2264
+
2265
+ var transports = {
2266
+ ws: Pusher.WSTransport,
2267
+ flash: Pusher.FlashTransport,
2268
+ sockjs: Pusher.SockJSTransport
2269
+ };
2270
+
2271
+ // DSL bindings
2272
+
2273
+ function returnWithOriginalContext(f) {
2274
+ return function(context) {
2275
+ return [f.apply(this, arguments), context];
2276
+ };
2277
+ }
2278
+
2279
+ var globalContext = {
2280
+ def: function(context, name, value) {
2281
+ if (context[name] !== undefined) {
2282
+ throw "Redefining symbol " + name;
2283
+ }
2284
+ context[name] = value;
2285
+ return [undefined, context];
2286
+ },
2287
+
2288
+ def_transport: function(context, name, type, priority, options, manager) {
2289
+ var transportClass = transports[type];
2290
+ if (!transportClass) {
2291
+ throw new Pusher.Errors.UnsupportedTransport(type);
2292
+ }
2293
+ var transportOptions = Pusher.Util.extend({}, {
2294
+ key: context.key,
2295
+ encrypted: context.encrypted,
2296
+ timeline: context.timeline,
2297
+ disableFlash: context.disableFlash,
2298
+ ignoreNullOrigin: context.ignoreNullOrigin
2299
+ }, options);
2300
+ if (manager) {
2301
+ transportClass = manager.getAssistant(transportClass);
2302
+ }
2303
+ var transport = new Pusher.TransportStrategy(
2304
+ name, priority, transportClass, transportOptions
2305
+ );
2306
+ var newContext = context.def(context, name, transport)[1];
2307
+ newContext.transports = context.transports || {};
2308
+ newContext.transports[name] = transport;
2309
+ return [undefined, newContext];
2310
+ },
2311
+
2312
+ transport_manager: returnWithOriginalContext(function(_, options) {
2313
+ return new Pusher.TransportManager(options);
2314
+ }),
2315
+
2316
+ sequential: returnWithOriginalContext(function(_, options) {
2317
+ var strategies = Array.prototype.slice.call(arguments, 2);
2318
+ return new Pusher.SequentialStrategy(strategies, options);
2319
+ }),
2320
+
2321
+ cached: returnWithOriginalContext(function(context, ttl, strategy){
2322
+ return new Pusher.CachedStrategy(strategy, context.transports, {
2323
+ ttl: ttl,
2324
+ timeline: context.timeline
2325
+ });
2326
+ }),
2327
+
2328
+ first_connected: returnWithOriginalContext(function(_, strategy) {
2329
+ return new Pusher.FirstConnectedStrategy(strategy);
2330
+ }),
2331
+
2332
+ best_connected_ever: returnWithOriginalContext(function() {
2333
+ var strategies = Array.prototype.slice.call(arguments, 1);
2334
+ return new Pusher.BestConnectedEverStrategy(strategies);
2335
+ }),
2336
+
2337
+ delayed: returnWithOriginalContext(function(_, delay, strategy) {
2338
+ return new Pusher.DelayedStrategy(strategy, { delay: delay });
2339
+ }),
2340
+
2341
+ "if": returnWithOriginalContext(function(_, test, trueBranch, falseBranch) {
2342
+ return new Pusher.IfStrategy(test, trueBranch, falseBranch);
2343
+ }),
2344
+
2345
+ is_supported: returnWithOriginalContext(function(_, strategy) {
2346
+ return function() {
2347
+ return strategy.isSupported();
2348
+ };
2349
+ })
2350
+ };
2351
+
2352
+ // DSL interpreter
2353
+
2354
+ function isSymbol(expression) {
2355
+ return (typeof expression === "string") && expression.charAt(0) === ":";
2356
+ }
2357
+
2358
+ function getSymbolValue(expression, context) {
2359
+ return context[expression.slice(1)];
2360
+ }
2361
+
2362
+ function evaluateListOfExpressions(expressions, context) {
2363
+ if (expressions.length === 0) {
2364
+ return [[], context];
2365
+ }
2366
+ var head = evaluate(expressions[0], context);
2367
+ var tail = evaluateListOfExpressions(expressions.slice(1), head[1]);
2368
+ return [[head[0]].concat(tail[0]), tail[1]];
2369
+ }
2370
+
2371
+ function evaluateString(expression, context) {
2372
+ if (!isSymbol(expression)) {
2373
+ return [expression, context];
2374
+ }
2375
+ var value = getSymbolValue(expression, context);
2376
+ if (value === undefined) {
2377
+ throw "Undefined symbol " + expression;
2378
+ }
2379
+ return [value, context];
2380
+ }
2381
+
2382
+ function evaluateArray(expression, context) {
2383
+ if (isSymbol(expression[0])) {
2384
+ var f = getSymbolValue(expression[0], context);
2385
+ if (expression.length > 1) {
2386
+ if (typeof f !== "function") {
2387
+ throw "Calling non-function " + expression[0];
2388
+ }
2389
+ var args = [Pusher.Util.extend({}, context)].concat(
2390
+ Pusher.Util.map(expression.slice(1), function(arg) {
2391
+ return evaluate(arg, Pusher.Util.extend({}, context))[0];
2392
+ })
2393
+ );
2394
+ return f.apply(this, args);
2395
+ } else {
2396
+ return [f, context];
2397
+ }
2398
+ } else {
2399
+ return evaluateListOfExpressions(expression, context);
2400
+ }
2401
+ }
2402
+
2403
+ function evaluate(expression, context) {
2404
+ var expressionType = typeof expression;
2405
+ if (typeof expression === "string") {
2406
+ return evaluateString(expression, context);
2407
+ } else if (typeof expression === "object") {
2408
+ if (expression instanceof Array && expression.length > 0) {
2409
+ return evaluateArray(expression, context);
2410
+ }
2411
+ }
2412
+ return [expression, context];
2413
+ }
2414
+
2415
+ Pusher.StrategyBuilder = StrategyBuilder;
2416
+ }).call(this);
2417
+
2418
+ ;(function() {
2419
+ /**
2420
+ * Provides functions for handling Pusher protocol-specific messages.
2421
+ */
2422
+ Protocol = {};
2423
+
2424
+ /**
2425
+ * Decodes a message in a Pusher format.
2426
+ *
2427
+ * Throws errors when messages are not parse'able.
2428
+ *
2429
+ * @param {Object} message
2430
+ * @return {Object}
2431
+ */
2432
+ Protocol.decodeMessage = function(message) {
2433
+ try {
2434
+ var params = JSON.parse(message.data);
2435
+ if (typeof params.data === 'string') {
2436
+ try {
2437
+ params.data = JSON.parse(params.data);
2438
+ } catch (e) {
2439
+ if (!(e instanceof SyntaxError)) {
2440
+ // TODO looks like unreachable code
2441
+ // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/JSON/parse
2442
+ throw e;
2443
+ }
2444
+ }
2445
+ }
2446
+ return params;
2447
+ } catch (e) {
2448
+ throw { type: 'MessageParseError', error: e, data: message.data};
2449
+ }
2450
+ };
2451
+
2452
+ /**
2453
+ * Encodes a message to be sent.
2454
+ *
2455
+ * @param {Object} message
2456
+ * @return {String}
2457
+ */
2458
+ Protocol.encodeMessage = function(message) {
2459
+ return JSON.stringify(message);
2460
+ };
2461
+
2462
+ /** Processes a handshake message and returns appropriate actions.
2463
+ *
2464
+ * Returns an object with an 'action' and other action-specific properties.
2465
+ *
2466
+ * There are three outcomes when calling this function. First is a successful
2467
+ * connection attempt, when pusher:connection_established is received, which
2468
+ * results in a 'connected' action with an 'id' property. When passed a
2469
+ * pusher:error event, it returns a result with action appropriate to the
2470
+ * close code and an error. Otherwise, it raises an exception.
2471
+ *
2472
+ * @param {String} message
2473
+ * @result Object
2474
+ */
2475
+ Protocol.processHandshake = function(message) {
2476
+ message = this.decodeMessage(message);
2477
+
2478
+ if (message.event === "pusher:connection_established") {
2479
+ return { action: "connected", id: message.data.socket_id };
2480
+ } else if (message.event === "pusher:error") {
2481
+ // From protocol 6 close codes are sent only once, so this only
2482
+ // happens when connection does not support close codes
2483
+ return {
2484
+ action: this.getCloseAction(message.data),
2485
+ error: this.getCloseError(message.data)
2486
+ };
2487
+ } else {
2488
+ throw "Invalid handshake";
2489
+ }
2490
+ };
2491
+
2492
+ /**
2493
+ * Dispatches the close event and returns an appropriate action name.
2494
+ *
2495
+ * See:
2496
+ * 1. https://developer.mozilla.org/en-US/docs/WebSockets/WebSockets_reference/CloseEvent
2497
+ * 2. http://pusher.com/docs/pusher_protocol
2498
+ *
2499
+ * @param {CloseEvent} closeEvent
2500
+ * @return {String} close action name
2501
+ */
2502
+ Protocol.getCloseAction = function(closeEvent) {
2503
+ if (closeEvent.code < 4000) {
2504
+ // ignore 1000 CLOSE_NORMAL, 1001 CLOSE_GOING_AWAY,
2505
+ // 1005 CLOSE_NO_STATUS, 1006 CLOSE_ABNORMAL
2506
+ // ignore 1007...3999
2507
+ // handle 1002 CLOSE_PROTOCOL_ERROR, 1003 CLOSE_UNSUPPORTED,
2508
+ // 1004 CLOSE_TOO_LARGE
2509
+ if (closeEvent.code >= 1002 && closeEvent.code <= 1004) {
2510
+ return "backoff";
2511
+ } else {
2512
+ return null;
2513
+ }
2514
+ } else if (closeEvent.code === 4000) {
2515
+ return "ssl_only";
2516
+ } else if (closeEvent.code < 4100) {
2517
+ return "refused";
2518
+ } else if (closeEvent.code < 4200) {
2519
+ return "backoff";
2520
+ } else if (closeEvent.code < 4300) {
2521
+ return "retry";
2522
+ } else {
2523
+ // unknown error
2524
+ return "refused";
2525
+ }
2526
+ };
2527
+
2528
+ /**
2529
+ * Returns an error or null basing on the close event.
2530
+ *
2531
+ * Null is returned when connection was closed cleanly. Otherwise, an object
2532
+ * with error details is returned.
2533
+ *
2534
+ * @param {CloseEvent} closeEvent
2535
+ * @return {Object} error object
2536
+ */
2537
+ Protocol.getCloseError = function(closeEvent) {
2538
+ if (closeEvent.code !== 1000 && closeEvent.code !== 1001) {
2539
+ return {
2540
+ type: 'PusherError',
2541
+ data: {
2542
+ code: closeEvent.code,
2543
+ message: closeEvent.reason || closeEvent.message
2544
+ }
2545
+ };
2546
+ } else {
2547
+ return null;
2548
+ }
2549
+ };
2550
+
2551
+ Pusher.Protocol = Protocol;
2552
+ }).call(this);
2553
+
2554
+ ;(function() {
2555
+ /**
2556
+ * Provides Pusher protocol interface for transports.
2557
+ *
2558
+ * Emits following events:
2559
+ * - message - on received messages
2560
+ * - ping - on ping requests
2561
+ * - pong - on pong responses
2562
+ * - error - when the transport emits an error
2563
+ * - closed - after closing the transport
2564
+ *
2565
+ * It also emits more events when connection closes with a code.
2566
+ * See Protocol.getCloseAction to get more details.
2567
+ *
2568
+ * @param {Number} id
2569
+ * @param {AbstractTransport} transport
2570
+ */
2571
+ function Connection(id, transport) {
2572
+ Pusher.EventsDispatcher.call(this);
2573
+
2574
+ this.id = id;
2575
+ this.transport = transport;
2576
+ this.bindListeners();
2577
+ }
2578
+ var prototype = Connection.prototype;
2579
+ Pusher.Util.extend(prototype, Pusher.EventsDispatcher.prototype);
2580
+
2581
+ /** Returns whether used transport handles ping/pong by itself
2582
+ *
2583
+ * @returns {Boolean} true if ping is handled by the transport
2584
+ */
2585
+ prototype.supportsPing = function() {
2586
+ return this.transport.supportsPing();
2587
+ };
2588
+
2589
+ /** Sends raw data.
2590
+ *
2591
+ * @param {String} data
2592
+ */
2593
+ prototype.send = function(data) {
2594
+ return this.transport.send(data);
2595
+ };
2596
+
2597
+ /** Sends an event.
2598
+ *
2599
+ * @param {String} name
2600
+ * @param {String} data
2601
+ * @param {String} [channel]
2602
+ * @returns {Boolean} whether message was sent or not
2603
+ */
2604
+ prototype.send_event = function(name, data, channel) {
2605
+ var message = { event: name, data: data };
2606
+ if (channel) {
2607
+ message.channel = channel;
2608
+ }
2609
+ return this.send(Pusher.Protocol.encodeMessage(message));
2610
+ };
2611
+
2612
+ /** Closes the connection. */
2613
+ prototype.close = function() {
2614
+ this.transport.close();
2615
+ };
2616
+
2617
+ /** @private */
2618
+ prototype.bindListeners = function() {
2619
+ var self = this;
2620
+
2621
+ var onMessage = function(m) {
2622
+ var message;
2623
+ try {
2624
+ message = Pusher.Protocol.decodeMessage(m);
2625
+ } catch(e) {
2626
+ self.emit('error', {
2627
+ type: 'MessageParseError',
2628
+ error: e,
2629
+ data: m.data
2630
+ });
2631
+ }
2632
+
2633
+ if (message !== undefined) {
2634
+ Pusher.debug('Event recd', message);
2635
+
2636
+ switch (message.event) {
2637
+ case 'pusher:error':
2638
+ self.emit('error', { type: 'PusherError', data: message.data });
2639
+ break;
2640
+ case 'pusher:ping':
2641
+ self.emit("ping");
2642
+ break;
2643
+ case 'pusher:pong':
2644
+ self.emit("pong");
2645
+ break;
2646
+ }
2647
+ self.emit('message', message);
2648
+ }
2649
+ };
2650
+ var onPingRequest = function() {
2651
+ self.emit("ping_request");
2652
+ };
2653
+ var onError = function(error) {
2654
+ self.emit("error", { type: "WebSocketError", error: error });
2655
+ };
2656
+ var onClosed = function(closeEvent) {
2657
+ unbindListeners();
2658
+
2659
+ if (closeEvent && closeEvent.code) {
2660
+ self.handleCloseEvent(closeEvent);
2661
+ }
2662
+
2663
+ self.transport = null;
2664
+ self.emit("closed");
2665
+ };
2666
+
2667
+ var unbindListeners = function() {
2668
+ self.transport.unbind("closed", onClosed);
2669
+ self.transport.unbind("error", onError);
2670
+ self.transport.unbind("ping_request", onPingRequest);
2671
+ self.transport.unbind("message", onMessage);
2672
+ };
2673
+
2674
+ self.transport.bind("message", onMessage);
2675
+ self.transport.bind("ping_request", onPingRequest);
2676
+ self.transport.bind("error", onError);
2677
+ self.transport.bind("closed", onClosed);
2678
+ };
2679
+
2680
+ /** @private */
2681
+ prototype.handleCloseEvent = function(closeEvent) {
2682
+ var action = Pusher.Protocol.getCloseAction(closeEvent);
2683
+ var error = Pusher.Protocol.getCloseError(closeEvent);
2684
+ if (error) {
2685
+ this.emit('error', error);
2686
+ }
2687
+ if (action) {
2688
+ this.emit(action);
2689
+ }
2690
+ };
2691
+
2692
+ Pusher.Connection = Connection;
2693
+ }).call(this);
2694
+
2695
+ ;(function() {
2696
+ /**
2697
+ * Handles Pusher protocol handshakes for transports.
2698
+ *
2699
+ * Calls back with a result object after handshake is completed. Results
2700
+ * always have two fields:
2701
+ * - action - string describing action to be taken after the handshake
2702
+ * - transport - the transport object passed to the constructor
2703
+ *
2704
+ * Different actions can set different additional properties on the result.
2705
+ * In the case of 'connected' action, there will be a 'connection' property
2706
+ * containing a Connection object for the transport. Other actions should
2707
+ * carry an 'error' property.
2708
+ *
2709
+ * @param {AbstractTransport} transport
2710
+ * @param {Function} callback
2711
+ */
2712
+ function Handshake(transport, callback) {
2713
+ this.transport = transport;
2714
+ this.callback = callback;
2715
+ this.bindListeners();
2716
+ }
2717
+ var prototype = Handshake.prototype;
2718
+
2719
+ /** @private */
2720
+ prototype.bindListeners = function() {
2721
+ var self = this;
2722
+
2723
+ var unbindListeners = function() {
2724
+ self.transport.unbind("message", onMessage);
2725
+ self.transport.unbind("closed", onClosed);
2726
+ };
2727
+
2728
+ var onMessage = function(m) {
2729
+ unbindListeners();
2730
+ try {
2731
+ var result = Pusher.Protocol.processHandshake(m);
2732
+ if (result.action === "connected") {
2733
+ self.finish("connected", {
2734
+ connection: new Pusher.Connection(result.id, self.transport)
2735
+ });
2736
+ } else {
2737
+ self.finish(result.action, { error: result.error });
2738
+ self.transport.close();
2739
+ }
2740
+ } catch (e) {
2741
+ self.finish("error", { error: e });
2742
+ self.transport.close();
2743
+ }
2744
+ };
2745
+ var onClosed = function(closeEvent) {
2746
+ unbindListeners();
2747
+
2748
+ var action = Pusher.Protocol.getCloseAction(closeEvent) || "backoff";
2749
+ var error = Pusher.Protocol.getCloseError(closeEvent);
2750
+ self.finish(action, { error: error });
2751
+ };
2752
+
2753
+ self.transport.bind("message", onMessage);
2754
+ self.transport.bind("closed", onClosed);
2755
+ };
2756
+
2757
+ /** @private */
2758
+ prototype.finish = function(action, params) {
2759
+ this.callback(
2760
+ Pusher.Util.extend({ transport: this.transport, action: action }, params)
2761
+ );
2762
+ };
2763
+
2764
+ Pusher.Handshake = Handshake;
2765
+ }).call(this);
2766
+
2767
+ ;(function() {
2768
+ /** Manages connection to Pusher.
2769
+ *
2770
+ * Uses a strategy (currently only default), timers and network availability
2771
+ * info to establish a connection and export its state. In case of failures,
2772
+ * manages reconnection attempts.
2773
+ *
2774
+ * Exports state changes as following events:
2775
+ * - "state_change", { previous: p, current: state }
2776
+ * - state
2777
+ *
2778
+ * States:
2779
+ * - initialized - initial state, never transitioned to
2780
+ * - connecting - connection is being established
2781
+ * - connected - connection has been fully established
2782
+ * - disconnected - on requested disconnection or before reconnecting
2783
+ * - unavailable - after connection timeout or when there's no network
2784
+ *
2785
+ * Options:
2786
+ * - unavailableTimeout - time to transition to unavailable state
2787
+ * - activityTimeout - time after which ping message should be sent
2788
+ * - pongTimeout - time for Pusher to respond with pong before reconnecting
2789
+ *
2790
+ * @param {String} key application key
2791
+ * @param {Object} options
2792
+ */
2793
+ function ConnectionManager(key, options) {
2794
+ Pusher.EventsDispatcher.call(this);
2795
+
2796
+ this.key = key;
2797
+ this.options = options || {};
2798
+ this.state = "initialized";
2799
+ this.connection = null;
2800
+ this.encrypted = !!options.encrypted;
2801
+ this.timeline = this.options.getTimeline();
2802
+
2803
+ this.connectionCallbacks = this.buildConnectionCallbacks();
2804
+ this.errorCallbacks = this.buildErrorCallbacks();
2805
+ this.handshakeCallbacks = this.buildHandshakeCallbacks(this.errorCallbacks);
2806
+
2807
+ var self = this;
2808
+
2809
+ Pusher.Network.bind("online", function() {
2810
+ if (self.state === "unavailable") {
2811
+ self.connect();
2812
+ }
2813
+ });
2814
+ Pusher.Network.bind("offline", function() {
2815
+ if (self.shouldRetry()) {
2816
+ self.disconnect();
2817
+ self.updateState("unavailable");
2818
+ }
2819
+ });
2820
+
2821
+ var sendTimeline = function() {
2822
+ if (self.timelineSender) {
2823
+ self.timelineSender.send(function() {});
2824
+ }
2825
+ };
2826
+ this.bind("connected", sendTimeline);
2827
+ setInterval(sendTimeline, 60000);
2828
+
2829
+ this.updateStrategy();
2830
+ }
2831
+ var prototype = ConnectionManager.prototype;
2832
+
2833
+ Pusher.Util.extend(prototype, Pusher.EventsDispatcher.prototype);
2834
+
2835
+ /** Establishes a connection to Pusher.
2836
+ *
2837
+ * Does nothing when connection is already established. See top-level doc
2838
+ * to find events emitted on connection attempts.
2839
+ */
2840
+ prototype.connect = function() {
2841
+ var self = this;
2842
+
2843
+ if (self.connection) {
2844
+ return;
2845
+ }
2846
+ if (self.state === "connecting") {
2847
+ return;
2848
+ }
2849
+
2850
+ if (!self.strategy.isSupported()) {
2851
+ self.updateState("failed");
2852
+ return;
2853
+ }
2854
+ if (Pusher.Network.isOnline() === false) {
2855
+ self.updateState("unavailable");
2856
+ return;
2857
+ }
2858
+
2859
+ self.updateState("connecting");
2860
+ self.timelineSender = self.options.getTimelineSender(
2861
+ self.timeline,
2862
+ { encrypted: self.encrypted },
2863
+ self
2864
+ );
2865
+
2866
+ var callback = function(error, handshake) {
2867
+ if (error) {
2868
+ self.runner = self.strategy.connect(0, callback);
2869
+ } else {
2870
+ // we don't support switching connections yet
2871
+ self.runner.abort();
2872
+ self.handshakeCallbacks[handshake.action](handshake);
2873
+ }
2874
+ };
2875
+ self.runner = self.strategy.connect(0, callback);
2876
+
2877
+ self.setUnavailableTimer();
2878
+ };
2879
+
2880
+ /** Sends raw data.
2881
+ *
2882
+ * @param {String} data
2883
+ */
2884
+ prototype.send = function(data) {
2885
+ if (this.connection) {
2886
+ return this.connection.send(data);
2887
+ } else {
2888
+ return false;
2889
+ }
2890
+ };
2891
+
2892
+ /** Sends an event.
2893
+ *
2894
+ * @param {String} name
2895
+ * @param {String} data
2896
+ * @param {String} [channel]
2897
+ * @returns {Boolean} whether message was sent or not
2898
+ */
2899
+ prototype.send_event = function(name, data, channel) {
2900
+ if (this.connection) {
2901
+ return this.connection.send_event(name, data, channel);
2902
+ } else {
2903
+ return false;
2904
+ }
2905
+ };
2906
+
2907
+ /** Closes the connection. */
2908
+ prototype.disconnect = function() {
2909
+ if (this.runner) {
2910
+ this.runner.abort();
2911
+ }
2912
+ this.clearRetryTimer();
2913
+ this.clearUnavailableTimer();
2914
+ this.stopActivityCheck();
2915
+ this.updateState("disconnected");
2916
+ // we're in disconnected state, so closing will not cause reconnecting
2917
+ if (this.connection) {
2918
+ this.connection.close();
2919
+ this.abandonConnection();
2920
+ }
2921
+ };
2922
+
2923
+ /** @private */
2924
+ prototype.updateStrategy = function() {
2925
+ this.strategy = this.options.getStrategy({
2926
+ key: this.key,
2927
+ timeline: this.timeline,
2928
+ encrypted: this.encrypted
2929
+ });
2930
+ };
2931
+
2932
+ /** @private */
2933
+ prototype.retryIn = function(delay) {
2934
+ var self = this;
2935
+ this.retryTimer = new Pusher.Timer(delay || 0, function() {
2936
+ self.disconnect();
2937
+ self.connect();
2938
+ });
2939
+ };
2940
+
2941
+ /** @private */
2942
+ prototype.clearRetryTimer = function() {
2943
+ if (this.retryTimer) {
2944
+ this.retryTimer.ensureAborted();
2945
+ }
2946
+ };
2947
+
2948
+ /** @private */
2949
+ prototype.setUnavailableTimer = function() {
2950
+ var self = this;
2951
+ self.unavailableTimer = new Pusher.Timer(
2952
+ self.options.unavailableTimeout,
2953
+ function() {
2954
+ self.updateState("unavailable");
2955
+ }
2956
+ );
2957
+ };
2958
+
2959
+ /** @private */
2960
+ prototype.clearUnavailableTimer = function() {
2961
+ if (this.unavailableTimer) {
2962
+ this.unavailableTimer.ensureAborted();
2963
+ }
2964
+ };
2965
+
2966
+ /** @private */
2967
+ prototype.resetActivityCheck = function() {
2968
+ this.stopActivityCheck();
2969
+ // send ping after inactivity
2970
+ if (!this.connection.supportsPing()) {
2971
+ var self = this;
2972
+ self.activityTimer = new Pusher.Timer(
2973
+ self.options.activityTimeout,
2974
+ function() {
2975
+ self.send_event('pusher:ping', {});
2976
+ // wait for pong response
2977
+ self.activityTimer = new Pusher.Timer(
2978
+ self.options.pongTimeout,
2979
+ function() {
2980
+ self.connection.close();
2981
+ }
2982
+ );
2983
+ }
2984
+ );
2985
+ }
2986
+ };
2987
+
2988
+ /** @private */
2989
+ prototype.stopActivityCheck = function() {
2990
+ if (this.activityTimer) {
2991
+ this.activityTimer.ensureAborted();
2992
+ }
2993
+ };
2994
+
2995
+ /** @private */
2996
+ prototype.buildConnectionCallbacks = function() {
2997
+ var self = this;
2998
+ return {
2999
+ message: function(message) {
3000
+ // includes pong messages from server
3001
+ self.resetActivityCheck();
3002
+ self.emit('message', message);
3003
+ },
3004
+ ping: function() {
3005
+ self.send_event('pusher:pong', {});
3006
+ },
3007
+ ping_request: function() {
3008
+ self.send_event('pusher:ping', {});
3009
+ },
3010
+ error: function(error) {
3011
+ // just emit error to user - socket will already be closed by browser
3012
+ self.emit("error", { type: "WebSocketError", error: error });
3013
+ },
3014
+ closed: function() {
3015
+ self.abandonConnection();
3016
+ if (self.shouldRetry()) {
3017
+ self.retryIn(1000);
3018
+ }
3019
+ }
3020
+ };
3021
+ };
3022
+
3023
+ /** @private */
3024
+ prototype.buildHandshakeCallbacks = function(errorCallbacks) {
3025
+ var self = this;
3026
+ return Pusher.Util.extend({}, errorCallbacks, {
3027
+ connected: function(handshake) {
3028
+ self.clearUnavailableTimer();
3029
+ self.setConnection(handshake.connection);
3030
+ self.socket_id = self.connection.id;
3031
+ self.updateState("connected");
3032
+ }
3033
+ });
3034
+ };
3035
+
3036
+ /** @private */
3037
+ prototype.buildErrorCallbacks = function() {
3038
+ var self = this;
3039
+ return {
3040
+ ssl_only: function() {
3041
+ self.encrypted = true;
3042
+ self.updateStrategy();
3043
+ self.retryIn(0);
3044
+ },
3045
+ refused: function() {
3046
+ self.disconnect();
3047
+ },
3048
+ backoff: function() {
3049
+ self.retryIn(1000);
3050
+ },
3051
+ retry: function() {
3052
+ self.retryIn(0);
3053
+ }
3054
+ };
3055
+ };
3056
+
3057
+ /** @private */
3058
+ prototype.setConnection = function(connection) {
3059
+ this.connection = connection;
3060
+ for (var event in this.connectionCallbacks) {
3061
+ this.connection.bind(event, this.connectionCallbacks[event]);
3062
+ }
3063
+ this.resetActivityCheck();
3064
+ };
3065
+
3066
+ /** @private */
3067
+ prototype.abandonConnection = function() {
3068
+ if (!this.connection) {
3069
+ return;
3070
+ }
3071
+ for (var event in this.connectionCallbacks) {
3072
+ this.connection.unbind(event, this.connectionCallbacks[event]);
3073
+ }
3074
+ this.connection = null;
3075
+ };
3076
+
3077
+ /** @private */
3078
+ prototype.updateState = function(newState, data) {
3079
+ var previousState = this.state;
3080
+
3081
+ this.state = newState;
3082
+ // Only emit when the state changes
3083
+ if (previousState !== newState) {
3084
+ Pusher.debug('State changed', previousState + ' -> ' + newState);
3085
+
3086
+ this.emit('state_change', { previous: previousState, current: newState });
3087
+ this.emit(newState, data);
3088
+ }
3089
+ };
3090
+
3091
+ /** @private */
3092
+ prototype.shouldRetry = function() {
3093
+ return this.state === "connecting" || this.state === "connected";
3094
+ };
3095
+
3096
+ Pusher.ConnectionManager = ConnectionManager;
3097
+ }).call(this);
3098
+
3099
+ ;(function() {
3100
+ /** Really basic interface providing network availability info.
3101
+ *
3102
+ * Emits:
3103
+ * - online - when browser goes online
3104
+ * - offline - when browser goes offline
3105
+ */
3106
+ function NetInfo() {
3107
+ Pusher.EventsDispatcher.call(this);
3108
+
3109
+ var self = this;
3110
+ // This is okay, as IE doesn't support this stuff anyway.
3111
+ if (window.addEventListener !== undefined) {
3112
+ window.addEventListener("online", function() {
3113
+ self.emit('online');
3114
+ }, false);
3115
+ window.addEventListener("offline", function() {
3116
+ self.emit('offline');
3117
+ }, false);
3118
+ }
3119
+ }
3120
+ Pusher.Util.extend(NetInfo.prototype, Pusher.EventsDispatcher.prototype);
3121
+
3122
+ var prototype = NetInfo.prototype;
3123
+
3124
+ /** Returns whether browser is online or not
3125
+ *
3126
+ * Offline means definitely offline (no connection to router).
3127
+ * Inverse does NOT mean definitely online (only currently supported in Safari
3128
+ * and even there only means the device has a connection to the router).
3129
+ *
3130
+ * @return {Boolean}
3131
+ */
3132
+ prototype.isOnline = function() {
3133
+ if (window.navigator.onLine === undefined) {
3134
+ return true;
3135
+ } else {
3136
+ return window.navigator.onLine;
3137
+ }
3138
+ };
3139
+
3140
+ Pusher.NetInfo = NetInfo;
3141
+ Pusher.Network = new NetInfo();
3142
+ }).call(this);
3143
+
3144
+ ;(function() {
3145
+ Pusher.Channels = function() {
3146
+ this.channels = {};
3147
+ };
3148
+
3149
+ Pusher.Channels.prototype = {
3150
+ add: function(channel_name, pusher) {
3151
+ var existing_channel = this.find(channel_name);
3152
+ if (!existing_channel) {
3153
+ var channel = Pusher.Channel.factory(channel_name, pusher);
3154
+ this.channels[channel_name] = channel;
3155
+ return channel;
3156
+ } else {
3157
+ return existing_channel;
3158
+ }
3159
+ },
3160
+
3161
+ find: function(channel_name) {
3162
+ return this.channels[channel_name];
3163
+ },
3164
+
3165
+ remove: function(channel_name) {
3166
+ delete this.channels[channel_name];
3167
+ },
3168
+
3169
+ disconnect: function () {
3170
+ for(var channel_name in this.channels){
3171
+ this.channels[channel_name].disconnect()
3172
+ }
3173
+ }
3174
+ };
3175
+
3176
+ Pusher.Channel = function(channel_name, pusher) {
3177
+ var self = this;
3178
+ Pusher.EventsDispatcher.call(this, function(event_name, event_data) {
3179
+ Pusher.debug('No callbacks on ' + channel_name + ' for ' + event_name);
3180
+ });
3181
+
3182
+ this.pusher = pusher;
3183
+ this.name = channel_name;
3184
+ this.subscribed = false;
3185
+
3186
+ this.bind('pusher_internal:subscription_succeeded', function(data) {
3187
+ self.onSubscriptionSucceeded(data);
3188
+ });
3189
+ };
3190
+
3191
+ Pusher.Channel.prototype = {
3192
+ // inheritable constructor
3193
+ init: function() {},
3194
+ disconnect: function() {
3195
+ this.subscribed = false;
3196
+ this.emit("pusher_internal:disconnected");
3197
+ },
3198
+
3199
+ onSubscriptionSucceeded: function(data) {
3200
+ this.subscribed = true;
3201
+ this.emit('pusher:subscription_succeeded');
3202
+ },
3203
+
3204
+ authorize: function(socketId, options, callback){
3205
+ return callback(false, {}); // normal channels don't require auth
3206
+ },
3207
+
3208
+ trigger: function(event, data) {
3209
+ return this.pusher.send_event(event, data, this.name);
3210
+ }
3211
+ };
3212
+
3213
+ Pusher.Util.extend(Pusher.Channel.prototype, Pusher.EventsDispatcher.prototype);
3214
+
3215
+ Pusher.Channel.PrivateChannel = {
3216
+ authorize: function(socketId, options, callback){
3217
+ var self = this;
3218
+ var authorizer = new Pusher.Channel.Authorizer(this, Pusher.channel_auth_transport, options);
3219
+ return authorizer.authorize(socketId, function(err, authData) {
3220
+ if(!err) {
3221
+ self.emit('pusher_internal:authorized', authData);
3222
+ }
3223
+
3224
+ callback(err, authData);
3225
+ });
3226
+ }
3227
+ };
3228
+
3229
+ Pusher.Channel.PresenceChannel = {
3230
+ init: function(){
3231
+ this.members = new Members(this); // leeches off channel events
3232
+ },
3233
+
3234
+ onSubscriptionSucceeded: function(data) {
3235
+ this.subscribed = true;
3236
+ // We override this because we want the Members obj to be responsible for
3237
+ // emitting the pusher:subscription_succeeded. It will do this after it has done its work.
3238
+ }
3239
+ };
3240
+
3241
+ var Members = function(channel) {
3242
+ var self = this;
3243
+ var channelData = null;
3244
+
3245
+ var reset = function() {
3246
+ self._members_map = {};
3247
+ self.count = 0;
3248
+ self.me = null;
3249
+ channelData = null;
3250
+ };
3251
+ reset();
3252
+
3253
+ var subscriptionSucceeded = function(subscriptionData) {
3254
+ self._members_map = subscriptionData.presence.hash;
3255
+ self.count = subscriptionData.presence.count;
3256
+ self.me = self.get(channelData.user_id);
3257
+ channel.emit('pusher:subscription_succeeded', self);
3258
+ };
3259
+
3260
+ channel.bind('pusher_internal:authorized', function(authorizedData) {
3261
+ channelData = JSON.parse(authorizedData.channel_data);
3262
+ channel.bind("pusher_internal:subscription_succeeded", subscriptionSucceeded);
3263
+ });
3264
+
3265
+ channel.bind('pusher_internal:member_added', function(data) {
3266
+ if(self.get(data.user_id) === null) { // only incr if user_id does not already exist
3267
+ self.count++;
3268
+ }
3269
+
3270
+ self._members_map[data.user_id] = data.user_info;
3271
+ channel.emit('pusher:member_added', self.get(data.user_id));
3272
+ });
3273
+
3274
+ channel.bind('pusher_internal:member_removed', function(data) {
3275
+ var member = self.get(data.user_id);
3276
+ if(member) {
3277
+ delete self._members_map[data.user_id];
3278
+ self.count--;
3279
+ channel.emit('pusher:member_removed', member);
3280
+ }
3281
+ });
3282
+
3283
+ channel.bind('pusher_internal:disconnected', function() {
3284
+ reset();
3285
+ channel.unbind("pusher_internal:subscription_succeeded", subscriptionSucceeded);
3286
+ });
3287
+ };
3288
+
3289
+ Members.prototype = {
3290
+ each: function(callback) {
3291
+ for(var i in this._members_map) {
3292
+ callback(this.get(i));
3293
+ }
3294
+ },
3295
+
3296
+ get: function(user_id) {
3297
+ if (this._members_map.hasOwnProperty(user_id)) { // have heard of this user user_id
3298
+ return {
3299
+ id: user_id,
3300
+ info: this._members_map[user_id]
3301
+ }
3302
+ } else { // have never heard of this user
3303
+ return null;
3304
+ }
3305
+ }
3306
+ };
3307
+
3308
+ Pusher.Channel.factory = function(channel_name, pusher){
3309
+ var channel = new Pusher.Channel(channel_name, pusher);
3310
+ if (channel_name.indexOf('private-') === 0) {
3311
+ Pusher.Util.extend(channel, Pusher.Channel.PrivateChannel);
3312
+ } else if (channel_name.indexOf('presence-') === 0) {
3313
+ Pusher.Util.extend(channel, Pusher.Channel.PrivateChannel);
3314
+ Pusher.Util.extend(channel, Pusher.Channel.PresenceChannel);
3315
+ };
3316
+ channel.init();
3317
+ return channel;
3318
+ };
3319
+ }).call(this);
3320
+
3321
+ ;(function() {
3322
+ Pusher.Channel.Authorizer = function(channel, type, options) {
3323
+ this.channel = channel;
3324
+ this.type = type;
3325
+
3326
+ this.authOptions = (options || {}).auth || {};
3327
+ };
3328
+
3329
+ Pusher.Channel.Authorizer.prototype = {
3330
+ composeQuery: function(socketId) {
3331
+ var query = '&socket_id=' + encodeURIComponent(socketId)
3332
+ + '&channel_name=' + encodeURIComponent(this.channel.name);
3333
+
3334
+ for(var i in this.authOptions.params) {
3335
+ query += "&" + encodeURIComponent(i) + "=" + encodeURIComponent(this.authOptions.params[i]);
3336
+ }
3337
+
3338
+ return query;
3339
+ },
3340
+
3341
+ authorize: function(socketId, callback) {
3342
+ return Pusher.authorizers[this.type].call(this, socketId, callback);
3343
+ }
3344
+ };
3345
+
3346
+
3347
+ Pusher.auth_callbacks = {};
3348
+ Pusher.authorizers = {
3349
+ ajax: function(socketId, callback){
3350
+ var self = this, xhr;
3351
+
3352
+ if (Pusher.XHR) {
3353
+ xhr = new Pusher.XHR();
3354
+ } else {
3355
+ xhr = (window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"));
3356
+ }
3357
+
3358
+ xhr.open("POST", Pusher.channel_auth_endpoint, true);
3359
+
3360
+ // add request headers
3361
+ xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
3362
+ for(var headerName in this.authOptions.headers) {
3363
+ xhr.setRequestHeader(headerName, this.authOptions.headers[headerName]);
3364
+ }
3365
+
3366
+ xhr.onreadystatechange = function() {
3367
+ if (xhr.readyState == 4) {
3368
+ if (xhr.status == 200) {
3369
+ var data, parsed = false;
3370
+
3371
+ try {
3372
+ data = JSON.parse(xhr.responseText);
3373
+ parsed = true;
3374
+ } catch (e) {
3375
+ callback(true, 'JSON returned from webapp was invalid, yet status code was 200. Data was: ' + xhr.responseText);
3376
+ }
3377
+
3378
+ if (parsed) { // prevents double execution.
3379
+ callback(false, data);
3380
+ }
3381
+ } else {
3382
+ Pusher.warn("Couldn't get auth info from your webapp", xhr.status);
3383
+ callback(true, xhr.status);
3384
+ }
3385
+ }
3386
+ };
3387
+
3388
+ xhr.send(this.composeQuery(socketId));
3389
+ return xhr;
3390
+ },
3391
+
3392
+ jsonp: function(socketId, callback){
3393
+ if(this.authOptions.headers !== undefined) {
3394
+ Pusher.warn("Warn", "To send headers with the auth request, you must use AJAX, rather than JSONP.");
3395
+ }
3396
+
3397
+ var script = document.createElement("script");
3398
+ // Hacked wrapper.
3399
+ Pusher.auth_callbacks[this.channel.name] = function(data) {
3400
+ callback(false, data);
3401
+ };
3402
+
3403
+ var callback_name = "Pusher.auth_callbacks['" + this.channel.name + "']";
3404
+ script.src = Pusher.channel_auth_endpoint
3405
+ + '?callback='
3406
+ + encodeURIComponent(callback_name)
3407
+ + this.composeQuery(socketId);
3408
+
3409
+ var head = document.getElementsByTagName("head")[0] || document.documentElement;
3410
+ head.insertBefore( script, head.firstChild );
3411
+ }
3412
+ };
3413
+ }).call(this);