pusher-fake 0.14.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/features/step_definitions/api_steps.rb +3 -3
- data/features/step_definitions/channel_steps.rb +1 -1
- data/features/step_definitions/event_steps.rb +1 -1
- data/features/support/application/public/javascripts/vendor/{pusher-2.1.6.js → pusher-2.2.1.js} +890 -624
- data/features/support/application/views/index.erb +1 -1
- data/features/support/webhooks.rb +1 -7
- data/lib/pusher-fake.rb +1 -1
- data/lib/pusher-fake/channel/private.rb +1 -1
- data/lib/pusher-fake/channel/public.rb +1 -1
- data/lib/pusher-fake/connection.rb +34 -4
- data/lib/pusher-fake/server/application.rb +10 -3
- data/lib/pusher-fake/webhook.rb +1 -0
- data/spec/lib/pusher-fake/channel/private_spec.rb +3 -4
- data/spec/lib/pusher-fake/channel/public_spec.rb +4 -6
- data/spec/lib/pusher-fake/connection_spec.rb +186 -132
- data/spec/lib/pusher-fake/server/application_spec.rb +37 -16
- data/spec/lib/pusher-fake/webhook_spec.rb +7 -0
- data/spec/lib/pusher_fake_spec.rb +1 -1
- data/spec/support/have_configuration_option_matcher.rb +1 -1
- metadata +21 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4018221dfac239178547457cb5f01ff6de4fa6a5
|
4
|
+
data.tar.gz: 96ddd96fce5d6b969c965ffa197fc5b38aa51d40
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 069edb3dc3fe2a368023b154b7f24fae69a4083cfc536f6552e0471ed40255ba749f8138597ddae3e9901b60ab9121fc82c4f70bd3cb2751b3e471fa4e846d95
|
7
|
+
data.tar.gz: e99741e224800cf12a1c814a1d25f233172956490232e8020540336b0584ce930fe7cd146b4d5a169e428e30000cb07cd677e69e43dbbd2841138822c5b247f9
|
@@ -29,12 +29,12 @@ end
|
|
29
29
|
|
30
30
|
Then /^I should receive JSON for (\d+) users?$/ do |count|
|
31
31
|
@response[:users].tap do |users|
|
32
|
-
expect(users).to
|
32
|
+
expect(users.length).to eq(count.to_i)
|
33
33
|
|
34
34
|
users.map do |user|
|
35
|
-
ObjectSpace._id2ref(user["id"])
|
35
|
+
ObjectSpace._id2ref(user["id"].to_i)
|
36
36
|
end.each do |object|
|
37
|
-
expect(object).to be_a(
|
37
|
+
expect(object).to be_a(EventMachine::WebSocket::Connection)
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
@@ -38,7 +38,7 @@ Then /^([^ ]+) should receive a "([^"]+)" event on the "([^"]+)" channel$/ do |n
|
|
38
38
|
wait do
|
39
39
|
events = page.evaluate_script("Pusher.instance.events[#{MultiJson.dump([channel, event].join(":"))}]")
|
40
40
|
|
41
|
-
expect(events).to
|
41
|
+
expect(events.length).to eq(1)
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
data/features/support/application/public/javascripts/vendor/{pusher-2.1.6.js → pusher-2.2.1.js}
RENAMED
@@ -1,8 +1,8 @@
|
|
1
1
|
/*!
|
2
|
-
* Pusher JavaScript Library v2.1
|
3
|
-
* http://
|
2
|
+
* Pusher JavaScript Library v2.2.1
|
3
|
+
* http://pusher.com/
|
4
4
|
*
|
5
|
-
* Copyright
|
5
|
+
* Copyright 2014, Pusher
|
6
6
|
* Released under the MIT licence.
|
7
7
|
*/
|
8
8
|
|
@@ -25,6 +25,7 @@
|
|
25
25
|
this.sessionID = Math.floor(Math.random() * 1000000000);
|
26
26
|
|
27
27
|
this.timeline = new Pusher.Timeline(this.key, this.sessionID, {
|
28
|
+
cluster: this.config.cluster,
|
28
29
|
features: Pusher.Util.getClientFeatures(),
|
29
30
|
params: this.config.timelineParams || {},
|
30
31
|
limit: 50,
|
@@ -34,14 +35,14 @@
|
|
34
35
|
if (!this.config.disableStats) {
|
35
36
|
this.timelineSender = new Pusher.TimelineSender(this.timeline, {
|
36
37
|
host: this.config.statsHost,
|
37
|
-
path: "/timeline"
|
38
|
+
path: "/timeline/v2/jsonp"
|
38
39
|
});
|
39
40
|
}
|
40
41
|
|
41
42
|
var getStrategy = function(options) {
|
43
|
+
var config = Pusher.Util.extend({}, self.config, options);
|
42
44
|
return Pusher.StrategyBuilder.build(
|
43
|
-
Pusher.getDefaultStrategy(
|
44
|
-
Pusher.Util.extend({}, self.config, options)
|
45
|
+
Pusher.getDefaultStrategy(config), config
|
45
46
|
);
|
46
47
|
};
|
47
48
|
|
@@ -88,7 +89,9 @@
|
|
88
89
|
Pusher.instances.push(this);
|
89
90
|
this.timeline.info({ instances: Pusher.instances.length });
|
90
91
|
|
91
|
-
if (Pusher.isReady)
|
92
|
+
if (Pusher.isReady) {
|
93
|
+
self.connect();
|
94
|
+
}
|
92
95
|
}
|
93
96
|
var prototype = Pusher.prototype;
|
94
97
|
|
@@ -195,7 +198,7 @@
|
|
195
198
|
};
|
196
199
|
|
197
200
|
prototype.isEncrypted = function() {
|
198
|
-
if (Pusher.Util.
|
201
|
+
if (Pusher.Util.getDocument().location.protocol === "https:") {
|
199
202
|
return true;
|
200
203
|
} else {
|
201
204
|
return Boolean(this.config.encrypted);
|
@@ -210,80 +213,71 @@
|
|
210
213
|
}
|
211
214
|
}
|
212
215
|
|
216
|
+
Pusher.HTTP = {};
|
217
|
+
|
213
218
|
this.Pusher = Pusher;
|
214
219
|
}).call(this);
|
215
220
|
|
216
221
|
;(function() {
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
222
|
+
// We need to bind clear functions this way to avoid exceptions on IE8
|
223
|
+
function clearTimeout(timer) {
|
224
|
+
window.clearTimeout(timer);
|
225
|
+
}
|
226
|
+
function clearInterval(timer) {
|
227
|
+
window.clearInterval(timer);
|
228
|
+
}
|
229
|
+
|
230
|
+
function GenericTimer(set, clear, delay, callback) {
|
223
231
|
var self = this;
|
224
232
|
|
225
|
-
this.
|
226
|
-
|
227
|
-
|
228
|
-
self.
|
233
|
+
this.clear = clear;
|
234
|
+
this.timer = set(function() {
|
235
|
+
if (self.timer !== null) {
|
236
|
+
self.timer = callback(self.timer);
|
229
237
|
}
|
230
238
|
}, delay);
|
231
239
|
}
|
232
|
-
var prototype =
|
240
|
+
var prototype = GenericTimer.prototype;
|
233
241
|
|
234
242
|
/** Returns whether the timer is still running.
|
235
243
|
*
|
236
244
|
* @return {Boolean}
|
237
245
|
*/
|
238
246
|
prototype.isRunning = function() {
|
239
|
-
return this.
|
247
|
+
return this.timer !== null;
|
240
248
|
};
|
241
249
|
|
242
250
|
/** Aborts a timer when it's running. */
|
243
251
|
prototype.ensureAborted = function() {
|
244
|
-
if (this.
|
245
|
-
|
246
|
-
this.
|
252
|
+
if (this.timer) {
|
253
|
+
// Clear function is already bound
|
254
|
+
this.clear(this.timer);
|
255
|
+
this.timer = null;
|
247
256
|
}
|
248
257
|
};
|
249
258
|
|
250
|
-
|
251
|
-
}).call(this);
|
252
|
-
|
253
|
-
;(function() {
|
254
|
-
/** Cross-browser compatible periodic timer abstraction.
|
259
|
+
/** Cross-browser compatible one-off timer abstraction.
|
255
260
|
*
|
256
|
-
* @param {Number}
|
261
|
+
* @param {Number} delay
|
257
262
|
* @param {Function} callback
|
258
263
|
*/
|
259
|
-
function
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
}, interval);
|
267
|
-
}
|
268
|
-
var prototype = PeriodicTimer.prototype;
|
269
|
-
|
270
|
-
/** Returns whether the timer is still running.
|
264
|
+
Pusher.Timer = function(delay, callback) {
|
265
|
+
return new GenericTimer(setTimeout, clearTimeout, delay, function(timer) {
|
266
|
+
callback();
|
267
|
+
return null;
|
268
|
+
});
|
269
|
+
};
|
270
|
+
/** Cross-browser compatible periodic timer abstraction.
|
271
271
|
*
|
272
|
-
* @
|
272
|
+
* @param {Number} delay
|
273
|
+
* @param {Function} callback
|
273
274
|
*/
|
274
|
-
|
275
|
-
return
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
prototype.ensureAborted = function() {
|
280
|
-
if (this.interval) {
|
281
|
-
clearInterval(this.interval);
|
282
|
-
this.interval = null;
|
283
|
-
}
|
275
|
+
Pusher.PeriodicTimer = function(delay, callback) {
|
276
|
+
return new GenericTimer(setInterval, clearInterval, delay, function(timer) {
|
277
|
+
callback();
|
278
|
+
return timer;
|
279
|
+
});
|
284
280
|
};
|
285
|
-
|
286
|
-
Pusher.PeriodicTimer = PeriodicTimer;
|
287
281
|
}).call(this);
|
288
282
|
|
289
283
|
;(function() {
|
@@ -417,9 +411,9 @@
|
|
417
411
|
* @param {Array} array
|
418
412
|
* @param {Function} f
|
419
413
|
*/
|
420
|
-
apply: function(array, f) {
|
414
|
+
apply: function(array, f, context) {
|
421
415
|
for (var i = 0; i < array.length; i++) {
|
422
|
-
f(array[i], i, array);
|
416
|
+
f.call(context || window, array[i], i, array);
|
423
417
|
}
|
424
418
|
},
|
425
419
|
|
@@ -571,12 +565,16 @@
|
|
571
565
|
};
|
572
566
|
},
|
573
567
|
|
568
|
+
getWindow: function() {
|
569
|
+
return window;
|
570
|
+
},
|
571
|
+
|
574
572
|
getDocument: function() {
|
575
573
|
return document;
|
576
574
|
},
|
577
575
|
|
578
|
-
|
579
|
-
return
|
576
|
+
getNavigator: function() {
|
577
|
+
return navigator;
|
580
578
|
},
|
581
579
|
|
582
580
|
getLocalStorage: function() {
|
@@ -591,15 +589,44 @@
|
|
591
589
|
return Pusher.Util.keys(
|
592
590
|
Pusher.Util.filterObject(
|
593
591
|
{ "ws": Pusher.WSTransport, "flash": Pusher.FlashTransport },
|
594
|
-
function (t) { return t.isSupported(); }
|
592
|
+
function (t) { return t.isSupported({}); }
|
595
593
|
)
|
596
594
|
);
|
595
|
+
},
|
596
|
+
|
597
|
+
addWindowListener: function(event, listener) {
|
598
|
+
var _window = Pusher.Util.getWindow();
|
599
|
+
if (_window.addEventListener !== undefined) {
|
600
|
+
_window.addEventListener(event, listener, false);
|
601
|
+
} else {
|
602
|
+
_window.attachEvent("on" + event, listener);
|
603
|
+
}
|
604
|
+
},
|
605
|
+
|
606
|
+
removeWindowListener: function(event, listener) {
|
607
|
+
var _window = Pusher.Util.getWindow();
|
608
|
+
if (_window.addEventListener !== undefined) {
|
609
|
+
_window.removeEventListener(event, listener, false);
|
610
|
+
} else {
|
611
|
+
_window.detachEvent("on" + event, listener);
|
612
|
+
}
|
613
|
+
},
|
614
|
+
|
615
|
+
isXHRSupported: function() {
|
616
|
+
var XHR = window.XMLHttpRequest;
|
617
|
+
return Boolean(XHR) && (new XHR()).withCredentials !== undefined;
|
618
|
+
},
|
619
|
+
|
620
|
+
isXDRSupported: function(encrypted) {
|
621
|
+
var protocol = encrypted ? "https:" : "http:";
|
622
|
+
var documentProtocol = Pusher.Util.getDocument().location.protocol;
|
623
|
+
return Boolean(window.XDomainRequest) && documentProtocol === protocol;
|
597
624
|
}
|
598
625
|
};
|
599
626
|
}).call(this);
|
600
627
|
|
601
628
|
;(function() {
|
602
|
-
Pusher.VERSION = '2.1
|
629
|
+
Pusher.VERSION = '2.2.1';
|
603
630
|
Pusher.PROTOCOL = 7;
|
604
631
|
|
605
632
|
// DEPRECATED: WS connection parameters
|
@@ -625,11 +652,30 @@
|
|
625
652
|
Pusher.dependency_suffix = '';
|
626
653
|
|
627
654
|
Pusher.getDefaultStrategy = function(config) {
|
655
|
+
var wsStrategy;
|
656
|
+
if (config.encrypted) {
|
657
|
+
wsStrategy = [
|
658
|
+
":best_connected_ever",
|
659
|
+
":ws_loop",
|
660
|
+
[":delayed", 2000, [":http_fallback_loop"]]
|
661
|
+
];
|
662
|
+
} else {
|
663
|
+
wsStrategy = [
|
664
|
+
":best_connected_ever",
|
665
|
+
":ws_loop",
|
666
|
+
[":delayed", 2000, [":wss_loop"]],
|
667
|
+
[":delayed", 5000, [":http_fallback_loop"]]
|
668
|
+
];
|
669
|
+
}
|
670
|
+
|
628
671
|
return [
|
629
672
|
[":def", "ws_options", {
|
630
673
|
hostUnencrypted: config.wsHost + ":" + config.wsPort,
|
631
674
|
hostEncrypted: config.wsHost + ":" + config.wssPort
|
632
675
|
}],
|
676
|
+
[":def", "wss_options", [":extend", ":ws_options", {
|
677
|
+
encrypted: true
|
678
|
+
}]],
|
633
679
|
[":def", "sockjs_options", {
|
634
680
|
hostUnencrypted: config.httpHost + ":" + config.httpPort,
|
635
681
|
hostEncrypted: config.httpHost + ":" + config.httpsPort
|
@@ -645,25 +691,67 @@
|
|
645
691
|
minPingDelay: 10000,
|
646
692
|
maxPingDelay: config.activity_timeout
|
647
693
|
}]],
|
694
|
+
[":def", "streaming_manager", [":transport_manager", {
|
695
|
+
lives: 2,
|
696
|
+
minPingDelay: 10000,
|
697
|
+
maxPingDelay: config.activity_timeout
|
698
|
+
}]],
|
648
699
|
|
649
700
|
[":def_transport", "ws", "ws", 3, ":ws_options", ":ws_manager"],
|
701
|
+
[":def_transport", "wss", "ws", 3, ":wss_options", ":ws_manager"],
|
650
702
|
[":def_transport", "flash", "flash", 2, ":ws_options", ":ws_manager"],
|
651
703
|
[":def_transport", "sockjs", "sockjs", 1, ":sockjs_options"],
|
704
|
+
[":def_transport", "xhr_streaming", "xhr_streaming", 1, ":sockjs_options", ":streaming_manager"],
|
705
|
+
[":def_transport", "xdr_streaming", "xdr_streaming", 1, ":sockjs_options", ":streaming_manager"],
|
706
|
+
[":def_transport", "xhr_polling", "xhr_polling", 1, ":sockjs_options"],
|
707
|
+
[":def_transport", "xdr_polling", "xdr_polling", 1, ":sockjs_options"],
|
708
|
+
|
652
709
|
[":def", "ws_loop", [":sequential", ":timeouts", ":ws"]],
|
710
|
+
[":def", "wss_loop", [":sequential", ":timeouts", ":wss"]],
|
653
711
|
[":def", "flash_loop", [":sequential", ":timeouts", ":flash"]],
|
654
712
|
[":def", "sockjs_loop", [":sequential", ":timeouts", ":sockjs"]],
|
655
713
|
|
714
|
+
[":def", "streaming_loop", [":sequential", ":timeouts",
|
715
|
+
[":if", [":is_supported", ":xhr_streaming"],
|
716
|
+
":xhr_streaming",
|
717
|
+
":xdr_streaming"
|
718
|
+
]
|
719
|
+
]],
|
720
|
+
[":def", "polling_loop", [":sequential", ":timeouts",
|
721
|
+
[":if", [":is_supported", ":xhr_polling"],
|
722
|
+
":xhr_polling",
|
723
|
+
":xdr_polling"
|
724
|
+
]
|
725
|
+
]],
|
726
|
+
|
727
|
+
[":def", "http_loop", [":if", [":is_supported", ":streaming_loop"], [
|
728
|
+
":best_connected_ever",
|
729
|
+
":streaming_loop",
|
730
|
+
[":delayed", 4000, [":polling_loop"]]
|
731
|
+
], [
|
732
|
+
":polling_loop"
|
733
|
+
]]],
|
734
|
+
|
735
|
+
[":def", "http_fallback_loop",
|
736
|
+
[":if", [":is_supported", ":http_loop"], [
|
737
|
+
":http_loop"
|
738
|
+
], [
|
739
|
+
":sockjs_loop"
|
740
|
+
]]
|
741
|
+
],
|
742
|
+
|
656
743
|
[":def", "strategy",
|
657
744
|
[":cached", 1800000,
|
658
745
|
[":first_connected",
|
659
|
-
[":if", [":is_supported", ":ws"],
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
746
|
+
[":if", [":is_supported", ":ws"],
|
747
|
+
wsStrategy,
|
748
|
+
[":if", [":is_supported", ":flash"], [
|
749
|
+
":best_connected_ever",
|
750
|
+
":flash_loop",
|
751
|
+
[":delayed", 2000, [":http_fallback_loop"]]
|
752
|
+
], [
|
753
|
+
":http_fallback_loop"
|
754
|
+
]]]
|
667
755
|
]
|
668
756
|
]
|
669
757
|
]
|
@@ -701,22 +789,23 @@
|
|
701
789
|
|
702
790
|
;(function() {
|
703
791
|
function buildExceptionClass(name) {
|
704
|
-
var
|
792
|
+
var constructor = function(message) {
|
705
793
|
Error.call(this, message);
|
706
794
|
this.name = name;
|
707
795
|
};
|
708
|
-
Pusher.Util.extend(
|
796
|
+
Pusher.Util.extend(constructor.prototype, Error.prototype);
|
709
797
|
|
710
|
-
return
|
798
|
+
return constructor;
|
711
799
|
}
|
712
800
|
|
713
801
|
/** Error classes used throughout pusher-js library. */
|
714
802
|
Pusher.Errors = {
|
715
803
|
BadEventName: buildExceptionClass("BadEventName"),
|
716
|
-
|
717
|
-
UnsupportedStrategy: buildExceptionClass("UnsupportedStrategy"),
|
804
|
+
RequestTimedOut: buildExceptionClass("RequestTimedOut"),
|
718
805
|
TransportPriorityTooLow: buildExceptionClass("TransportPriorityTooLow"),
|
719
|
-
TransportClosed: buildExceptionClass("TransportClosed")
|
806
|
+
TransportClosed: buildExceptionClass("TransportClosed"),
|
807
|
+
UnsupportedTransport: buildExceptionClass("UnsupportedTransport"),
|
808
|
+
UnsupportedStrategy: buildExceptionClass("UnsupportedStrategy")
|
720
809
|
};
|
721
810
|
}).call(this);
|
722
811
|
|
@@ -732,8 +821,8 @@
|
|
732
821
|
}
|
733
822
|
var prototype = EventsDispatcher.prototype;
|
734
823
|
|
735
|
-
prototype.bind = function(eventName, callback) {
|
736
|
-
this.callbacks.add(eventName, callback);
|
824
|
+
prototype.bind = function(eventName, callback, context) {
|
825
|
+
this.callbacks.add(eventName, callback, context);
|
737
826
|
return this;
|
738
827
|
};
|
739
828
|
|
@@ -742,7 +831,12 @@
|
|
742
831
|
return this;
|
743
832
|
};
|
744
833
|
|
745
|
-
prototype.unbind = function(eventName, callback) {
|
834
|
+
prototype.unbind = function(eventName, callback, context) {
|
835
|
+
this.callbacks.remove(eventName, callback, context);
|
836
|
+
return this;
|
837
|
+
};
|
838
|
+
|
839
|
+
prototype.unbind_all = function(eventName, callback) {
|
746
840
|
this.callbacks.remove(eventName, callback);
|
747
841
|
return this;
|
748
842
|
};
|
@@ -757,7 +851,7 @@
|
|
757
851
|
var callbacks = this.callbacks.get(eventName);
|
758
852
|
if (callbacks && callbacks.length > 0) {
|
759
853
|
for (i = 0; i < callbacks.length; i++) {
|
760
|
-
callbacks[i](data);
|
854
|
+
callbacks[i].fn.call(callbacks[i].context || window, data);
|
761
855
|
}
|
762
856
|
} else if (this.failThrough) {
|
763
857
|
this.failThrough(eventName, data);
|
@@ -772,36 +866,205 @@
|
|
772
866
|
this._callbacks = {};
|
773
867
|
}
|
774
868
|
|
775
|
-
CallbackRegistry.prototype.get = function(
|
776
|
-
return this._callbacks[
|
869
|
+
CallbackRegistry.prototype.get = function(name) {
|
870
|
+
return this._callbacks[prefix(name)];
|
777
871
|
};
|
778
872
|
|
779
|
-
CallbackRegistry.prototype.add = function(
|
780
|
-
var prefixedEventName =
|
873
|
+
CallbackRegistry.prototype.add = function(name, callback, context) {
|
874
|
+
var prefixedEventName = prefix(name);
|
781
875
|
this._callbacks[prefixedEventName] = this._callbacks[prefixedEventName] || [];
|
782
|
-
this._callbacks[prefixedEventName].push(
|
876
|
+
this._callbacks[prefixedEventName].push({
|
877
|
+
fn: callback,
|
878
|
+
context: context
|
879
|
+
});
|
880
|
+
};
|
881
|
+
|
882
|
+
CallbackRegistry.prototype.remove = function(name, callback, context) {
|
883
|
+
if (!name && !callback && !context) {
|
884
|
+
this._callbacks = {};
|
885
|
+
return;
|
886
|
+
}
|
887
|
+
|
888
|
+
var names = name ? [prefix(name)] : Pusher.Util.keys(this._callbacks);
|
889
|
+
|
890
|
+
if (callback || context) {
|
891
|
+
Pusher.Util.apply(names, function(name) {
|
892
|
+
this._callbacks[name] = Pusher.Util.filter(
|
893
|
+
this._callbacks[name] || [],
|
894
|
+
function(binding) {
|
895
|
+
return (callback && callback !== binding.fn) ||
|
896
|
+
(context && context !== binding.context);
|
897
|
+
}
|
898
|
+
);
|
899
|
+
if (this._callbacks[name].length === 0) {
|
900
|
+
delete this._callbacks[name];
|
901
|
+
}
|
902
|
+
}, this);
|
903
|
+
} else {
|
904
|
+
Pusher.Util.apply(names, function(name) {
|
905
|
+
delete this._callbacks[name];
|
906
|
+
}, this);
|
907
|
+
}
|
783
908
|
};
|
784
909
|
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
910
|
+
function prefix(name) {
|
911
|
+
return "_" + name;
|
912
|
+
}
|
913
|
+
|
914
|
+
Pusher.EventsDispatcher = EventsDispatcher;
|
915
|
+
}).call(this);
|
916
|
+
|
917
|
+
(function() {
|
918
|
+
/** Builds receivers for JSONP and Script requests.
|
919
|
+
*
|
920
|
+
* Each receiver is an object with following fields:
|
921
|
+
* - number - unique (for the factory instance), numerical id of the receiver
|
922
|
+
* - id - a string ID that can be used in DOM attributes
|
923
|
+
* - name - name of the function triggering the receiver
|
924
|
+
* - callback - callback function
|
925
|
+
*
|
926
|
+
* Receivers are triggered only once, on the first callback call.
|
927
|
+
*
|
928
|
+
* Receivers can be called by their name or by accessing factory object
|
929
|
+
* by the number key.
|
930
|
+
*
|
931
|
+
* @param {String} prefix the prefix used in ids
|
932
|
+
* @param {String} name the name of the object
|
933
|
+
*/
|
934
|
+
function ScriptReceiverFactory(prefix, name) {
|
935
|
+
this.lastId = 0;
|
936
|
+
this.prefix = prefix;
|
937
|
+
this.name = name;
|
938
|
+
}
|
939
|
+
var prototype = ScriptReceiverFactory.prototype;
|
940
|
+
|
941
|
+
/** Creates a script receiver.
|
942
|
+
*
|
943
|
+
* @param {Function} callback
|
944
|
+
* @return {ScriptReceiver}
|
945
|
+
*/
|
946
|
+
prototype.create = function(callback) {
|
947
|
+
this.lastId++;
|
948
|
+
|
949
|
+
var number = this.lastId;
|
950
|
+
var id = this.prefix + number;
|
951
|
+
var name = this.name + "[" + number + "]";
|
952
|
+
|
953
|
+
var called = false;
|
954
|
+
var callbackWrapper = function() {
|
955
|
+
if (!called) {
|
956
|
+
callback.apply(null, arguments);
|
957
|
+
called = true;
|
792
958
|
}
|
959
|
+
};
|
960
|
+
|
961
|
+
this[number] = callbackWrapper;
|
962
|
+
return { number: number, id: id, name: name, callback: callbackWrapper };
|
963
|
+
};
|
964
|
+
|
965
|
+
/** Removes the script receiver from the list.
|
966
|
+
*
|
967
|
+
* @param {ScriptReceiver} receiver
|
968
|
+
*/
|
969
|
+
prototype.remove = function(receiver) {
|
970
|
+
delete this[receiver.number];
|
971
|
+
};
|
972
|
+
|
973
|
+
Pusher.ScriptReceiverFactory = ScriptReceiverFactory;
|
974
|
+
Pusher.ScriptReceivers = new ScriptReceiverFactory(
|
975
|
+
"_pusher_script_", "Pusher.ScriptReceivers"
|
976
|
+
);
|
977
|
+
}).call(this);
|
978
|
+
|
979
|
+
(function() {
|
980
|
+
/** Sends a generic HTTP GET request using a script tag.
|
981
|
+
*
|
982
|
+
* By constructing URL in a specific way, it can be used for loading
|
983
|
+
* JavaScript resources or JSONP requests. It can notify about errors, but
|
984
|
+
* only in certain environments. Please take care of monitoring the state of
|
985
|
+
* the request yourself.
|
986
|
+
*
|
987
|
+
* @param {String} src
|
988
|
+
*/
|
989
|
+
function ScriptRequest(src) {
|
990
|
+
this.src = src;
|
991
|
+
}
|
992
|
+
var prototype = ScriptRequest.prototype;
|
993
|
+
|
994
|
+
/** Sends the actual script request.
|
995
|
+
*
|
996
|
+
* @param {ScriptReceiver} receiver
|
997
|
+
*/
|
998
|
+
prototype.send = function(receiver) {
|
999
|
+
var self = this;
|
1000
|
+
var errorString = "Error loading " + self.src;
|
1001
|
+
|
1002
|
+
self.script = document.createElement("script");
|
1003
|
+
self.script.id = receiver.id;
|
1004
|
+
self.script.src = self.src;
|
1005
|
+
self.script.type = "text/javascript";
|
1006
|
+
self.script.charset = "UTF-8";
|
1007
|
+
|
1008
|
+
if (self.script.addEventListener) {
|
1009
|
+
self.script.onerror = function() {
|
1010
|
+
receiver.callback(errorString);
|
1011
|
+
};
|
1012
|
+
self.script.onload = function() {
|
1013
|
+
receiver.callback(null);
|
1014
|
+
};
|
1015
|
+
} else {
|
1016
|
+
self.script.onreadystatechange = function() {
|
1017
|
+
if (self.script.readyState === 'loaded' ||
|
1018
|
+
self.script.readyState === 'complete') {
|
1019
|
+
receiver.callback(null);
|
1020
|
+
}
|
1021
|
+
};
|
1022
|
+
}
|
1023
|
+
|
1024
|
+
// Opera<11.6 hack for missing onerror callback
|
1025
|
+
if (self.script.async === undefined && document.attachEvent &&
|
1026
|
+
/opera/i.test(navigator.userAgent)) {
|
1027
|
+
self.errorScript = document.createElement("script");
|
1028
|
+
self.errorScript.id = receiver.id + "_error";
|
1029
|
+
self.errorScript.text = receiver.name + "('" + errorString + "');";
|
1030
|
+
self.script.async = self.errorScript.async = false;
|
1031
|
+
} else {
|
1032
|
+
self.script.async = true;
|
1033
|
+
}
|
1034
|
+
|
1035
|
+
var head = document.getElementsByTagName('head')[0];
|
1036
|
+
head.insertBefore(self.script, head.firstChild);
|
1037
|
+
if (self.errorScript) {
|
1038
|
+
head.insertBefore(self.errorScript, self.script.nextSibling);
|
793
1039
|
}
|
794
1040
|
};
|
795
1041
|
|
796
|
-
|
797
|
-
|
1042
|
+
/** Cleans up the DOM remains of the script request. */
|
1043
|
+
prototype.cleanup = function() {
|
1044
|
+
if (this.script) {
|
1045
|
+
this.script.onload = this.script.onerror = null;
|
1046
|
+
this.script.onreadystatechange = null;
|
1047
|
+
}
|
1048
|
+
if (this.script && this.script.parentNode) {
|
1049
|
+
this.script.parentNode.removeChild(this.script);
|
1050
|
+
}
|
1051
|
+
if (this.errorScript && this.errorScript.parentNode) {
|
1052
|
+
this.errorScript.parentNode.removeChild(this.errorScript);
|
1053
|
+
}
|
1054
|
+
this.script = null;
|
1055
|
+
this.errorScript = null;
|
798
1056
|
};
|
799
1057
|
|
800
|
-
Pusher.
|
1058
|
+
Pusher.ScriptRequest = ScriptRequest;
|
801
1059
|
}).call(this);
|
802
1060
|
|
803
1061
|
;(function() {
|
804
1062
|
/** Handles loading dependency files.
|
1063
|
+
*
|
1064
|
+
* Dependency loaders don't remember whether a resource has been loaded or
|
1065
|
+
* not. It is caller's responsibility to make sure the resource is not loaded
|
1066
|
+
* twice. This is because it's impossible to detect resource loading status
|
1067
|
+
* without knowing its content.
|
805
1068
|
*
|
806
1069
|
* Options:
|
807
1070
|
* - cdn_http - url to HTTP CND
|
@@ -813,8 +1076,8 @@
|
|
813
1076
|
*/
|
814
1077
|
function DependencyLoader(options) {
|
815
1078
|
this.options = options;
|
1079
|
+
this.receivers = options.receivers || Pusher.ScriptReceivers;
|
816
1080
|
this.loading = {};
|
817
|
-
this.loaded = {};
|
818
1081
|
}
|
819
1082
|
var prototype = DependencyLoader.prototype;
|
820
1083
|
|
@@ -826,23 +1089,30 @@
|
|
826
1089
|
prototype.load = function(name, callback) {
|
827
1090
|
var self = this;
|
828
1091
|
|
829
|
-
if (
|
830
|
-
callback
|
831
|
-
} else if (this.loading[name] && this.loading[name].length > 0) {
|
832
|
-
this.loading[name].push(callback);
|
1092
|
+
if (self.loading[name] && self.loading[name].length > 0) {
|
1093
|
+
self.loading[name].push(callback);
|
833
1094
|
} else {
|
834
|
-
|
1095
|
+
self.loading[name] = [callback];
|
835
1096
|
|
836
|
-
|
837
|
-
|
1097
|
+
var request = new Pusher.ScriptRequest(self.getPath(name));
|
1098
|
+
var receiver = self.receivers.create(function(error) {
|
1099
|
+
self.receivers.remove(receiver);
|
838
1100
|
|
839
1101
|
if (self.loading[name]) {
|
840
|
-
|
841
|
-
self.loading[name][i]();
|
842
|
-
}
|
1102
|
+
var callbacks = self.loading[name];
|
843
1103
|
delete self.loading[name];
|
1104
|
+
|
1105
|
+
var successCallback = function(wasSuccessful) {
|
1106
|
+
if (!wasSuccessful) {
|
1107
|
+
request.cleanup();
|
1108
|
+
}
|
1109
|
+
};
|
1110
|
+
for (var i = 0; i < callbacks.length; i++) {
|
1111
|
+
callbacks[i](error, successCallback);
|
1112
|
+
}
|
844
1113
|
}
|
845
1114
|
});
|
1115
|
+
request.send(receiver);
|
846
1116
|
}
|
847
1117
|
};
|
848
1118
|
|
@@ -852,7 +1122,7 @@
|
|
852
1122
|
*/
|
853
1123
|
prototype.getRoot = function(options) {
|
854
1124
|
var cdn;
|
855
|
-
var protocol = Pusher.Util.
|
1125
|
+
var protocol = Pusher.Util.getDocument().location.protocol;
|
856
1126
|
if ((options && options.encrypted) || protocol === "https:") {
|
857
1127
|
cdn = this.options.cdn_https;
|
858
1128
|
} else {
|
@@ -871,51 +1141,21 @@
|
|
871
1141
|
return this.getRoot(options) + '/' + name + this.options.suffix + '.js';
|
872
1142
|
};
|
873
1143
|
|
874
|
-
function handleScriptLoaded(elem, callback) {
|
875
|
-
if (Pusher.Util.getDocument().addEventListener) {
|
876
|
-
elem.addEventListener('load', callback, false);
|
877
|
-
} else {
|
878
|
-
elem.attachEvent('onreadystatechange', function () {
|
879
|
-
if (elem.readyState === 'loaded' || elem.readyState === 'complete') {
|
880
|
-
callback();
|
881
|
-
}
|
882
|
-
});
|
883
|
-
}
|
884
|
-
}
|
885
|
-
|
886
|
-
function require(src, callback) {
|
887
|
-
var document = Pusher.Util.getDocument();
|
888
|
-
var head = document.getElementsByTagName('head')[0];
|
889
|
-
var script = document.createElement('script');
|
890
|
-
|
891
|
-
script.setAttribute('src', src);
|
892
|
-
script.setAttribute("type","text/javascript");
|
893
|
-
script.setAttribute('async', true);
|
894
|
-
|
895
|
-
handleScriptLoaded(script, function() {
|
896
|
-
// workaround for an Opera issue
|
897
|
-
setTimeout(callback, 0);
|
898
|
-
});
|
899
|
-
|
900
|
-
head.appendChild(script);
|
901
|
-
}
|
902
|
-
|
903
1144
|
Pusher.DependencyLoader = DependencyLoader;
|
904
1145
|
}).call(this);
|
905
1146
|
|
906
1147
|
;(function() {
|
1148
|
+
Pusher.DependenciesReceivers = new Pusher.ScriptReceiverFactory(
|
1149
|
+
"_pusher_dependencies", "Pusher.DependenciesReceivers"
|
1150
|
+
);
|
907
1151
|
Pusher.Dependencies = new Pusher.DependencyLoader({
|
908
1152
|
cdn_http: Pusher.cdn_http,
|
909
1153
|
cdn_https: Pusher.cdn_https,
|
910
1154
|
version: Pusher.VERSION,
|
911
|
-
suffix: Pusher.dependency_suffix
|
1155
|
+
suffix: Pusher.dependency_suffix,
|
1156
|
+
receivers: Pusher.DependenciesReceivers
|
912
1157
|
});
|
913
1158
|
|
914
|
-
// Support Firefox versions which prefix WebSocket
|
915
|
-
if (!window.WebSocket && window.MozWebSocket) {
|
916
|
-
window.WebSocket = window.MozWebSocket;
|
917
|
-
}
|
918
|
-
|
919
1159
|
function initialize() {
|
920
1160
|
Pusher.ready();
|
921
1161
|
}
|
@@ -996,98 +1236,56 @@
|
|
996
1236
|
}).call(this);
|
997
1237
|
|
998
1238
|
(function() {
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1239
|
+
/** Sends data via JSONP.
|
1240
|
+
*
|
1241
|
+
* Data is a key-value map. Its values are JSON-encoded and then passed
|
1242
|
+
* through base64. Finally, keys and encoded values are appended to the query
|
1243
|
+
* string.
|
1244
|
+
*
|
1245
|
+
* The class itself does not guarantee raising errors on failures, as it's not
|
1246
|
+
* possible to support such feature on all browsers. Instead, JSONP endpoint
|
1247
|
+
* should call back in a way that's easy to distinguish from browser calls,
|
1248
|
+
* for example by passing a second argument to the receiver.
|
1249
|
+
*
|
1250
|
+
* @param {String} url
|
1251
|
+
* @param {Object} data key-value map of data to be submitted
|
1252
|
+
*/
|
1253
|
+
function JSONPRequest(url, data) {
|
1254
|
+
this.url = url;
|
1255
|
+
this.data = data;
|
1002
1256
|
}
|
1003
|
-
|
1004
|
-
JSONPRequest.send = function(options, callback) {
|
1005
|
-
var request = new Pusher.JSONPRequest({
|
1006
|
-
url: options.url,
|
1007
|
-
receiver: options.receiverName,
|
1008
|
-
tagPrefix: options.tagPrefix
|
1009
|
-
});
|
1010
|
-
var id = options.receiver.register(function(error, result) {
|
1011
|
-
request.cleanup();
|
1012
|
-
callback(error, result);
|
1013
|
-
});
|
1014
|
-
|
1015
|
-
return request.send(id, options.data, function(error) {
|
1016
|
-
var callback = options.receiver.unregister(id);
|
1017
|
-
if (callback) {
|
1018
|
-
callback(error);
|
1019
|
-
}
|
1020
|
-
});
|
1021
|
-
};
|
1022
|
-
|
1023
1257
|
var prototype = JSONPRequest.prototype;
|
1024
1258
|
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1259
|
+
/** Sends the actual JSONP request.
|
1260
|
+
*
|
1261
|
+
* @param {ScriptReceiver} receiver
|
1262
|
+
*/
|
1263
|
+
prototype.send = function(receiver) {
|
1264
|
+
if (this.request) {
|
1265
|
+
return;
|
1028
1266
|
}
|
1029
1267
|
|
1030
|
-
var
|
1031
|
-
|
1032
|
-
|
1033
|
-
{}, data, { receiver: this.options.receiver }
|
1034
|
-
);
|
1268
|
+
var params = Pusher.Util.filterObject(this.data, function(value) {
|
1269
|
+
return value !== undefined;
|
1270
|
+
});
|
1035
1271
|
var query = Pusher.Util.map(
|
1036
|
-
Pusher.Util.flatten(
|
1037
|
-
encodeData(
|
1038
|
-
Pusher.Util.filterObject(params, function(value) {
|
1039
|
-
return value !== undefined;
|
1040
|
-
})
|
1041
|
-
)
|
1042
|
-
),
|
1272
|
+
Pusher.Util.flatten(encodeParamsObject(params)),
|
1043
1273
|
Pusher.Util.method("join", "=")
|
1044
1274
|
).join("&");
|
1275
|
+
var url = this.url + "/" + receiver.number + "?" + query;
|
1045
1276
|
|
1046
|
-
this.
|
1047
|
-
this.
|
1048
|
-
this.script.src = this.options.url + "/" + id + "?" + query;
|
1049
|
-
this.script.type = "text/javascript";
|
1050
|
-
this.script.charset = "UTF-8";
|
1051
|
-
this.script.onerror = this.script.onload = callback;
|
1052
|
-
|
1053
|
-
// Opera<11.6 hack for missing onerror callback
|
1054
|
-
if (this.script.async === undefined && document.attachEvent) {
|
1055
|
-
if (/opera/i.test(navigator.userAgent)) {
|
1056
|
-
var receiverName = this.options.receiver || "Pusher.JSONP.receive";
|
1057
|
-
this.errorScript = document.createElement("script");
|
1058
|
-
this.errorScript.text = receiverName + "(" + id + ", true);";
|
1059
|
-
this.script.async = this.errorScript.async = false;
|
1060
|
-
}
|
1061
|
-
}
|
1062
|
-
|
1063
|
-
var self = this;
|
1064
|
-
this.script.onreadystatechange = function() {
|
1065
|
-
if (self.script && /loaded|complete/.test(self.script.readyState)) {
|
1066
|
-
callback(true);
|
1067
|
-
}
|
1068
|
-
};
|
1069
|
-
|
1070
|
-
var head = document.getElementsByTagName('head')[0];
|
1071
|
-
head.insertBefore(this.script, head.firstChild);
|
1072
|
-
if (this.errorScript) {
|
1073
|
-
head.insertBefore(this.errorScript, this.script.nextSibling);
|
1074
|
-
}
|
1075
|
-
|
1076
|
-
return true;
|
1277
|
+
this.request = new Pusher.ScriptRequest(url);
|
1278
|
+
this.request.send(receiver);
|
1077
1279
|
};
|
1078
1280
|
|
1281
|
+
/** Cleans up the DOM remains of the JSONP request. */
|
1079
1282
|
prototype.cleanup = function() {
|
1080
|
-
if (this.
|
1081
|
-
this.
|
1082
|
-
this.script = null;
|
1083
|
-
}
|
1084
|
-
if (this.errorScript && this.errorScript.parentNode) {
|
1085
|
-
this.errorScript.parentNode.removeChild(this.errorScript);
|
1086
|
-
this.errorScript = null;
|
1283
|
+
if (this.request) {
|
1284
|
+
this.request.cleanup();
|
1087
1285
|
}
|
1088
1286
|
};
|
1089
1287
|
|
1090
|
-
function
|
1288
|
+
function encodeParamsObject(data) {
|
1091
1289
|
return Pusher.Util.mapObject(data, function(value) {
|
1092
1290
|
if (typeof value === "object") {
|
1093
1291
|
value = JSON.stringify(value);
|
@@ -1097,45 +1295,6 @@
|
|
1097
1295
|
}
|
1098
1296
|
|
1099
1297
|
Pusher.JSONPRequest = JSONPRequest;
|
1100
|
-
|
1101
|
-
}).call(this);
|
1102
|
-
|
1103
|
-
(function() {
|
1104
|
-
|
1105
|
-
function JSONPReceiver() {
|
1106
|
-
this.lastId = 0;
|
1107
|
-
this.callbacks = {};
|
1108
|
-
}
|
1109
|
-
|
1110
|
-
var prototype = JSONPReceiver.prototype;
|
1111
|
-
|
1112
|
-
prototype.register = function(callback) {
|
1113
|
-
this.lastId++;
|
1114
|
-
var id = this.lastId;
|
1115
|
-
this.callbacks[id] = callback;
|
1116
|
-
return id;
|
1117
|
-
};
|
1118
|
-
|
1119
|
-
prototype.unregister = function(id) {
|
1120
|
-
if (this.callbacks[id]) {
|
1121
|
-
var callback = this.callbacks[id];
|
1122
|
-
delete this.callbacks[id];
|
1123
|
-
return callback;
|
1124
|
-
} else {
|
1125
|
-
return null;
|
1126
|
-
}
|
1127
|
-
};
|
1128
|
-
|
1129
|
-
prototype.receive = function(id, error, data) {
|
1130
|
-
var callback = this.unregister(id);
|
1131
|
-
if (callback) {
|
1132
|
-
callback(error, data);
|
1133
|
-
}
|
1134
|
-
};
|
1135
|
-
|
1136
|
-
Pusher.JSONPReceiver = JSONPReceiver;
|
1137
|
-
Pusher.JSONP = new JSONPReceiver();
|
1138
|
-
|
1139
1298
|
}).call(this);
|
1140
1299
|
|
1141
1300
|
(function() {
|
@@ -1155,12 +1314,9 @@
|
|
1155
1314
|
Timeline.DEBUG = 7;
|
1156
1315
|
|
1157
1316
|
prototype.log = function(level, event) {
|
1158
|
-
if (
|
1317
|
+
if (level <= this.options.level) {
|
1159
1318
|
this.events.push(
|
1160
|
-
Pusher.Util.extend({}, event, {
|
1161
|
-
timestamp: Pusher.Util.now(),
|
1162
|
-
level: (level !== Timeline.INFO ? level : undefined)
|
1163
|
-
})
|
1319
|
+
Pusher.Util.extend({}, event, { timestamp: Pusher.Util.now() })
|
1164
1320
|
);
|
1165
1321
|
if (this.options.limit && this.events.length > this.options.limit) {
|
1166
1322
|
this.events.shift();
|
@@ -1193,6 +1349,7 @@
|
|
1193
1349
|
key: self.key,
|
1194
1350
|
lib: "js",
|
1195
1351
|
version: self.options.version,
|
1352
|
+
cluster: self.options.cluster,
|
1196
1353
|
features: self.options.features,
|
1197
1354
|
timeline: self.events
|
1198
1355
|
}, self.options.params);
|
@@ -1226,22 +1383,21 @@
|
|
1226
1383
|
var prototype = TimelineSender.prototype;
|
1227
1384
|
|
1228
1385
|
prototype.send = function(encrypted, callback) {
|
1229
|
-
|
1386
|
+
var self = this;
|
1387
|
+
|
1388
|
+
if (self.timeline.isEmpty()) {
|
1230
1389
|
return;
|
1231
1390
|
}
|
1232
1391
|
|
1233
|
-
var self = this;
|
1234
|
-
var scheme = "http" + (encrypted ? "s" : "") + "://";
|
1235
|
-
|
1236
1392
|
var sendJSONP = function(data, callback) {
|
1237
|
-
var
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1393
|
+
var scheme = "http" + (encrypted ? "s" : "") + "://";
|
1394
|
+
var url = scheme + (self.host || self.options.host) + self.options.path;
|
1395
|
+
var request = new Pusher.JSONPRequest(url, data);
|
1396
|
+
|
1397
|
+
var receiver = Pusher.ScriptReceivers.create(function(error, result) {
|
1398
|
+
Pusher.ScriptReceivers.remove(receiver);
|
1399
|
+
request.cleanup();
|
1400
|
+
|
1245
1401
|
if (result && result.host) {
|
1246
1402
|
self.host = result.host;
|
1247
1403
|
}
|
@@ -1249,6 +1405,7 @@
|
|
1249
1405
|
callback(error, result);
|
1250
1406
|
}
|
1251
1407
|
});
|
1408
|
+
request.send(receiver);
|
1252
1409
|
};
|
1253
1410
|
self.timeline.send(sendJSONP, callback);
|
1254
1411
|
};
|
@@ -1359,9 +1516,13 @@
|
|
1359
1516
|
if (info && info.timestamp + this.ttl >= Pusher.Util.now()) {
|
1360
1517
|
var transport = this.transports[info.transport];
|
1361
1518
|
if (transport) {
|
1362
|
-
this.timeline.info({
|
1519
|
+
this.timeline.info({
|
1520
|
+
cached: true,
|
1521
|
+
transport: info.transport,
|
1522
|
+
latency: info.latency
|
1523
|
+
});
|
1363
1524
|
strategies.push(new Pusher.SequentialStrategy([transport], {
|
1364
|
-
timeout: info.latency * 2,
|
1525
|
+
timeout: info.latency * 2 + 1000,
|
1365
1526
|
failFast: true
|
1366
1527
|
}));
|
1367
1528
|
}
|
@@ -1693,7 +1854,9 @@
|
|
1693
1854
|
* @returns {Boolean}
|
1694
1855
|
*/
|
1695
1856
|
prototype.isSupported = function() {
|
1696
|
-
return this.transport.isSupported(
|
1857
|
+
return this.transport.isSupported({
|
1858
|
+
encrypted: this.options.encrypted
|
1859
|
+
});
|
1697
1860
|
};
|
1698
1861
|
|
1699
1862
|
/** Launches a connection attempt and returns a strategy runner.
|
@@ -1791,17 +1954,65 @@
|
|
1791
1954
|
Pusher.TransportStrategy = TransportStrategy;
|
1792
1955
|
}).call(this);
|
1793
1956
|
|
1794
|
-
|
1795
|
-
|
1957
|
+
(function() {
|
1958
|
+
function getGenericURL(baseScheme, params, path) {
|
1959
|
+
var scheme = baseScheme + (params.encrypted ? "s" : "");
|
1960
|
+
var host = params.encrypted ? params.hostEncrypted : params.hostUnencrypted;
|
1961
|
+
return scheme + "://" + host + path;
|
1962
|
+
}
|
1963
|
+
|
1964
|
+
function getGenericPath(key, queryString) {
|
1965
|
+
var path = "/app/" + key;
|
1966
|
+
var query =
|
1967
|
+
"?protocol=" + Pusher.PROTOCOL +
|
1968
|
+
"&client=js" +
|
1969
|
+
"&version=" + Pusher.VERSION +
|
1970
|
+
(queryString ? ("&" + queryString) : "");
|
1971
|
+
return path + query;
|
1972
|
+
}
|
1973
|
+
|
1974
|
+
/** URL schemes for different transport types. */
|
1975
|
+
Pusher.URLSchemes = {
|
1976
|
+
/** Standard WebSocket URL scheme. */
|
1977
|
+
ws: {
|
1978
|
+
getInitial: function(key, params) {
|
1979
|
+
return getGenericURL("ws", params, getGenericPath(key, "flash=false"));
|
1980
|
+
}
|
1981
|
+
},
|
1982
|
+
/** URL scheme for Flash. Same as WebSocket, but with a flash parameter. */
|
1983
|
+
flash: {
|
1984
|
+
getInitial: function(key, params) {
|
1985
|
+
return getGenericURL("ws", params, getGenericPath(key, "flash=true"));
|
1986
|
+
}
|
1987
|
+
},
|
1988
|
+
/** SockJS URL scheme. Supplies the path separately from the initial URL. */
|
1989
|
+
sockjs: {
|
1990
|
+
getInitial: function(key, params) {
|
1991
|
+
return getGenericURL("http", params, params.httpPath || "/pusher", "");
|
1992
|
+
},
|
1993
|
+
getPath: function(key, params) {
|
1994
|
+
return getGenericPath(key);
|
1995
|
+
}
|
1996
|
+
},
|
1997
|
+
/** URL scheme for HTTP transports. Basically, WS scheme with a prefix. */
|
1998
|
+
http: {
|
1999
|
+
getInitial: function(key, params) {
|
2000
|
+
var path = (params.httpPath || "/pusher") + getGenericPath(key);
|
2001
|
+
return getGenericURL("http", params, path);
|
2002
|
+
}
|
2003
|
+
}
|
2004
|
+
};
|
2005
|
+
}).call(this);
|
2006
|
+
|
2007
|
+
(function() {
|
2008
|
+
/** Provides universal API for transport connections.
|
1796
2009
|
*
|
1797
|
-
* Transport is a low-level
|
2010
|
+
* Transport connection is a low-level object that wraps a connection method
|
1798
2011
|
* and exposes a simple evented interface for the connection state and
|
1799
2012
|
* messaging. It does not implement Pusher-specific WebSocket protocol.
|
1800
2013
|
*
|
1801
2014
|
* Additionally, it fetches resources needed for transport to work and exposes
|
1802
|
-
* an interface for querying transport
|
1803
|
-
*
|
1804
|
-
* This is an abstract class, please do not instantiate it.
|
2015
|
+
* an interface for querying transport features.
|
1805
2016
|
*
|
1806
2017
|
* States:
|
1807
2018
|
* - new - initial state after constructing the object
|
@@ -1822,40 +2033,37 @@
|
|
1822
2033
|
* @param {String} key application key
|
1823
2034
|
* @param {Object} options
|
1824
2035
|
*/
|
1825
|
-
function
|
2036
|
+
function TransportConnection(hooks, name, priority, key, options) {
|
1826
2037
|
Pusher.EventsDispatcher.call(this);
|
1827
2038
|
|
2039
|
+
this.hooks = hooks;
|
1828
2040
|
this.name = name;
|
1829
2041
|
this.priority = priority;
|
1830
2042
|
this.key = key;
|
2043
|
+
this.options = options;
|
2044
|
+
|
1831
2045
|
this.state = "new";
|
1832
2046
|
this.timeline = options.timeline;
|
1833
2047
|
this.activityTimeout = options.activityTimeout;
|
1834
2048
|
this.id = this.timeline.generateUniqueID();
|
1835
|
-
|
1836
|
-
this.options = {
|
1837
|
-
encrypted: Boolean(options.encrypted),
|
1838
|
-
hostUnencrypted: options.hostUnencrypted,
|
1839
|
-
hostEncrypted: options.hostEncrypted
|
1840
|
-
};
|
1841
2049
|
}
|
1842
|
-
var prototype =
|
2050
|
+
var prototype = TransportConnection.prototype;
|
1843
2051
|
Pusher.Util.extend(prototype, Pusher.EventsDispatcher.prototype);
|
1844
2052
|
|
1845
|
-
/** Checks whether the transport
|
2053
|
+
/** Checks whether the transport handles activity checks by itself.
|
1846
2054
|
*
|
1847
|
-
* @
|
2055
|
+
* @return {Boolean}
|
1848
2056
|
*/
|
1849
|
-
|
1850
|
-
return
|
2057
|
+
prototype.handlesActivityChecks = function() {
|
2058
|
+
return Boolean(this.hooks.handlesActivityChecks);
|
1851
2059
|
};
|
1852
2060
|
|
1853
|
-
/** Checks whether the transport
|
2061
|
+
/** Checks whether the transport supports the ping/pong API.
|
1854
2062
|
*
|
1855
2063
|
* @return {Boolean}
|
1856
2064
|
*/
|
1857
2065
|
prototype.supportsPing = function() {
|
1858
|
-
return
|
2066
|
+
return Boolean(this.hooks.supportsPing);
|
1859
2067
|
};
|
1860
2068
|
|
1861
2069
|
/** Initializes the transport.
|
@@ -1863,12 +2071,35 @@
|
|
1863
2071
|
* Fetches resources if needed and then transitions to initialized.
|
1864
2072
|
*/
|
1865
2073
|
prototype.initialize = function() {
|
1866
|
-
this
|
1867
|
-
|
2074
|
+
var self = this;
|
2075
|
+
|
2076
|
+
self.timeline.info(self.buildTimelineMessage({
|
2077
|
+
transport: self.name + (self.options.encrypted ? "s" : "")
|
1868
2078
|
}));
|
1869
|
-
this.timeline.debug(this.buildTimelineMessage({ method: "initialize" }));
|
1870
2079
|
|
1871
|
-
|
2080
|
+
if (self.hooks.beforeInitialize) {
|
2081
|
+
self.hooks.beforeInitialize();
|
2082
|
+
}
|
2083
|
+
|
2084
|
+
if (self.hooks.isInitialized()) {
|
2085
|
+
self.changeState("initialized");
|
2086
|
+
} else if (self.hooks.file) {
|
2087
|
+
self.changeState("initializing");
|
2088
|
+
Pusher.Dependencies.load(self.hooks.file, function(error, callback) {
|
2089
|
+
if (self.hooks.isInitialized()) {
|
2090
|
+
self.changeState("initialized");
|
2091
|
+
callback(true);
|
2092
|
+
} else {
|
2093
|
+
if (error) {
|
2094
|
+
self.onError(error);
|
2095
|
+
}
|
2096
|
+
self.onClose();
|
2097
|
+
callback(false);
|
2098
|
+
}
|
2099
|
+
});
|
2100
|
+
} else {
|
2101
|
+
self.onClose();
|
2102
|
+
}
|
1872
2103
|
};
|
1873
2104
|
|
1874
2105
|
/** Tries to establish a connection.
|
@@ -1876,20 +2107,16 @@
|
|
1876
2107
|
* @returns {Boolean} false if transport is in invalid state
|
1877
2108
|
*/
|
1878
2109
|
prototype.connect = function() {
|
1879
|
-
var
|
1880
|
-
this.timeline.debug(this.buildTimelineMessage({
|
1881
|
-
method: "connect",
|
1882
|
-
url: url
|
1883
|
-
}));
|
2110
|
+
var self = this;
|
1884
2111
|
|
1885
|
-
if (
|
2112
|
+
if (self.socket || self.state !== "initialized") {
|
1886
2113
|
return false;
|
1887
2114
|
}
|
1888
2115
|
|
2116
|
+
var url = self.hooks.urls.getInitial(self.key, self.options);
|
1889
2117
|
try {
|
1890
|
-
|
2118
|
+
self.socket = self.hooks.getSocket(url, self.options);
|
1891
2119
|
} catch (e) {
|
1892
|
-
var self = this;
|
1893
2120
|
Pusher.Util.defer(function() {
|
1894
2121
|
self.onError(e);
|
1895
2122
|
self.changeState("closed");
|
@@ -1897,10 +2124,10 @@
|
|
1897
2124
|
return false;
|
1898
2125
|
}
|
1899
2126
|
|
1900
|
-
|
2127
|
+
self.bindListeners();
|
1901
2128
|
|
1902
|
-
Pusher.debug("Connecting", { transport:
|
1903
|
-
|
2129
|
+
Pusher.debug("Connecting", { transport: self.name, url: url });
|
2130
|
+
self.changeState("connecting");
|
1904
2131
|
return true;
|
1905
2132
|
};
|
1906
2133
|
|
@@ -1909,8 +2136,6 @@
|
|
1909
2136
|
* @return {Boolean} true if there was a connection to close
|
1910
2137
|
*/
|
1911
2138
|
prototype.close = function() {
|
1912
|
-
this.timeline.debug(this.buildTimelineMessage({ method: "close" }));
|
1913
|
-
|
1914
2139
|
if (this.socket) {
|
1915
2140
|
this.socket.close();
|
1916
2141
|
return true;
|
@@ -1925,38 +2150,46 @@
|
|
1925
2150
|
* @return {Boolean} true only when in the "open" state
|
1926
2151
|
*/
|
1927
2152
|
prototype.send = function(data) {
|
1928
|
-
this
|
1929
|
-
method: "send",
|
1930
|
-
data: data
|
1931
|
-
}));
|
2153
|
+
var self = this;
|
1932
2154
|
|
1933
|
-
if (
|
2155
|
+
if (self.state === "open") {
|
1934
2156
|
// Workaround for MobileSafari bug (see https://gist.github.com/2052006)
|
1935
|
-
|
1936
|
-
setTimeout(function() {
|
2157
|
+
Pusher.Util.defer(function() {
|
1937
2158
|
if (self.socket) {
|
1938
2159
|
self.socket.send(data);
|
1939
2160
|
}
|
1940
|
-
}
|
2161
|
+
});
|
1941
2162
|
return true;
|
1942
2163
|
} else {
|
1943
2164
|
return false;
|
1944
2165
|
}
|
1945
2166
|
};
|
1946
2167
|
|
1947
|
-
/**
|
2168
|
+
/** Sends a ping if the connection is open and transport supports it. */
|
2169
|
+
prototype.ping = function() {
|
2170
|
+
if (this.state === "open" && this.supportsPing()) {
|
2171
|
+
this.socket.ping();
|
2172
|
+
}
|
2173
|
+
};
|
2174
|
+
|
2175
|
+
/** @private */
|
1948
2176
|
prototype.onOpen = function() {
|
2177
|
+
if (this.hooks.beforeOpen) {
|
2178
|
+
this.hooks.beforeOpen(
|
2179
|
+
this.socket, this.hooks.urls.getPath(this.key, this.options)
|
2180
|
+
);
|
2181
|
+
}
|
1949
2182
|
this.changeState("open");
|
1950
2183
|
this.socket.onopen = undefined;
|
1951
2184
|
};
|
1952
2185
|
|
1953
|
-
/** @
|
2186
|
+
/** @private */
|
1954
2187
|
prototype.onError = function(error) {
|
1955
2188
|
this.emit("error", { type: 'WebSocketError', error: error });
|
1956
|
-
this.timeline.error(this.buildTimelineMessage({}));
|
2189
|
+
this.timeline.error(this.buildTimelineMessage({ error: error.toString() }));
|
1957
2190
|
};
|
1958
2191
|
|
1959
|
-
/** @
|
2192
|
+
/** @private */
|
1960
2193
|
prototype.onClose = function(closeEvent) {
|
1961
2194
|
if (closeEvent) {
|
1962
2195
|
this.changeState("closed", {
|
@@ -1967,63 +2200,56 @@
|
|
1967
2200
|
} else {
|
1968
2201
|
this.changeState("closed");
|
1969
2202
|
}
|
2203
|
+
this.unbindListeners();
|
1970
2204
|
this.socket = undefined;
|
1971
2205
|
};
|
1972
2206
|
|
1973
|
-
/** @
|
2207
|
+
/** @private */
|
1974
2208
|
prototype.onMessage = function(message) {
|
1975
|
-
this.timeline.debug(this.buildTimelineMessage({ message: message.data }));
|
1976
2209
|
this.emit("message", message);
|
1977
2210
|
};
|
1978
2211
|
|
1979
|
-
/** @
|
1980
|
-
prototype.
|
1981
|
-
|
1982
|
-
|
1983
|
-
this.socket.onopen = function() { self.onOpen(); };
|
1984
|
-
this.socket.onerror = function(error) { self.onError(error); };
|
1985
|
-
this.socket.onclose = function(closeEvent) { self.onClose(closeEvent); };
|
1986
|
-
this.socket.onmessage = function(message) { self.onMessage(message); };
|
2212
|
+
/** @private */
|
2213
|
+
prototype.onActivity = function() {
|
2214
|
+
this.emit("activity");
|
1987
2215
|
};
|
1988
2216
|
|
1989
|
-
/** @
|
1990
|
-
prototype.
|
1991
|
-
|
1992
|
-
};
|
2217
|
+
/** @private */
|
2218
|
+
prototype.bindListeners = function() {
|
2219
|
+
var self = this;
|
1993
2220
|
|
1994
|
-
|
1995
|
-
|
1996
|
-
|
1997
|
-
|
2221
|
+
self.socket.onopen = function() {
|
2222
|
+
self.onOpen();
|
2223
|
+
};
|
2224
|
+
self.socket.onerror = function(error) {
|
2225
|
+
self.onError(error);
|
2226
|
+
};
|
2227
|
+
self.socket.onclose = function(closeEvent) {
|
2228
|
+
self.onClose(closeEvent);
|
2229
|
+
};
|
2230
|
+
self.socket.onmessage = function(message) {
|
2231
|
+
self.onMessage(message);
|
2232
|
+
};
|
1998
2233
|
|
1999
|
-
|
2000
|
-
|
2001
|
-
var host;
|
2002
|
-
if (this.options.encrypted) {
|
2003
|
-
host = this.options.hostEncrypted;
|
2004
|
-
} else {
|
2005
|
-
host = this.options.hostUnencrypted;
|
2234
|
+
if (self.supportsPing()) {
|
2235
|
+
self.socket.onactivity = function() { self.onActivity(); };
|
2006
2236
|
}
|
2007
|
-
return this.getScheme() + "://" + host;
|
2008
|
-
};
|
2009
|
-
|
2010
|
-
/** @protected */
|
2011
|
-
prototype.getPath = function() {
|
2012
|
-
return "/app/" + this.key;
|
2013
|
-
};
|
2014
|
-
|
2015
|
-
/** @protected */
|
2016
|
-
prototype.getQueryString = function() {
|
2017
|
-
return "?protocol=" + Pusher.PROTOCOL +
|
2018
|
-
"&client=js&version=" + Pusher.VERSION;
|
2019
2237
|
};
|
2020
2238
|
|
2021
|
-
/** @
|
2022
|
-
prototype.
|
2023
|
-
|
2239
|
+
/** @private */
|
2240
|
+
prototype.unbindListeners = function() {
|
2241
|
+
if (this.socket) {
|
2242
|
+
this.socket.onopen = undefined;
|
2243
|
+
this.socket.onerror = undefined;
|
2244
|
+
this.socket.onclose = undefined;
|
2245
|
+
this.socket.onmessage = undefined;
|
2246
|
+
if (this.supportsPing()) {
|
2247
|
+
this.socket.onactivity = undefined;
|
2248
|
+
}
|
2249
|
+
}
|
2024
2250
|
};
|
2025
2251
|
|
2026
|
-
/** @
|
2252
|
+
/** @private */
|
2027
2253
|
prototype.changeState = function(state, params) {
|
2028
2254
|
this.state = state;
|
2029
2255
|
this.timeline.info(this.buildTimelineMessage({
|
@@ -2033,245 +2259,219 @@
|
|
2033
2259
|
this.emit(state, params);
|
2034
2260
|
};
|
2035
2261
|
|
2036
|
-
/** @
|
2262
|
+
/** @private */
|
2037
2263
|
prototype.buildTimelineMessage = function(message) {
|
2038
2264
|
return Pusher.Util.extend({ cid: this.id }, message);
|
2039
2265
|
};
|
2040
2266
|
|
2041
|
-
Pusher.
|
2267
|
+
Pusher.TransportConnection = TransportConnection;
|
2042
2268
|
}).call(this);
|
2043
2269
|
|
2044
|
-
|
2045
|
-
/**
|
2046
|
-
*
|
2047
|
-
* @see AbstractTransport
|
2048
|
-
*/
|
2049
|
-
function FlashTransport(name, priority, key, options) {
|
2050
|
-
Pusher.AbstractTransport.call(this, name, priority, key, options);
|
2051
|
-
}
|
2052
|
-
var prototype = FlashTransport.prototype;
|
2053
|
-
Pusher.Util.extend(prototype, Pusher.AbstractTransport.prototype);
|
2054
|
-
|
2055
|
-
/** Creates a new instance of FlashTransport.
|
2056
|
-
*
|
2057
|
-
* @param {String} key
|
2058
|
-
* @param {Object} options
|
2059
|
-
* @return {FlashTransport}
|
2060
|
-
*/
|
2061
|
-
FlashTransport.createConnection = function(name, priority, key, options) {
|
2062
|
-
return new FlashTransport(name, priority, key, options);
|
2063
|
-
};
|
2064
|
-
|
2065
|
-
/** Checks whether Flash is supported in the browser.
|
2066
|
-
*
|
2067
|
-
* It is possible to disable flash by passing an envrionment object with the
|
2068
|
-
* disableFlash property set to true.
|
2270
|
+
(function() {
|
2271
|
+
/** Provides interface for transport connection instantiation.
|
2069
2272
|
*
|
2070
|
-
*
|
2071
|
-
*
|
2072
|
-
* @returns {Boolean}
|
2073
|
-
*/
|
2074
|
-
FlashTransport.isSupported = function() {
|
2075
|
-
try {
|
2076
|
-
return Boolean(new ActiveXObject('ShockwaveFlash.ShockwaveFlash'));
|
2077
|
-
} catch (e) {
|
2078
|
-
try {
|
2079
|
-
return Boolean(
|
2080
|
-
navigator &&
|
2081
|
-
navigator.mimeTypes &&
|
2082
|
-
navigator.mimeTypes["application/x-shockwave-flash"] !== undefined
|
2083
|
-
);
|
2084
|
-
} catch(e) {
|
2085
|
-
return false;
|
2086
|
-
}
|
2087
|
-
}
|
2088
|
-
};
|
2089
|
-
|
2090
|
-
/** Fetches flashfallback dependency if needed.
|
2273
|
+
* Takes transport-specific hooks as the only argument, which allow checking
|
2274
|
+
* for transport support and creating its connections.
|
2091
2275
|
*
|
2092
|
-
*
|
2093
|
-
*
|
2276
|
+
* Supported hooks:
|
2277
|
+
* - file - the name of the file to be fetched during initialization
|
2278
|
+
* - urls - URL scheme to be used by transport
|
2279
|
+
* - handlesActivityCheck - true when the transport handles activity checks
|
2280
|
+
* - supportsPing - true when the transport has a ping/activity API
|
2281
|
+
* - isSupported - tells whether the transport is supported in the environment
|
2282
|
+
* - getSocket - creates a WebSocket-compatible transport socket
|
2094
2283
|
*
|
2095
|
-
*
|
2096
|
-
*/
|
2097
|
-
prototype.initialize = function() {
|
2098
|
-
var self = this;
|
2099
|
-
|
2100
|
-
this.timeline.info(this.buildTimelineMessage({
|
2101
|
-
transport: this.name + (this.options.encrypted ? "s" : "")
|
2102
|
-
}));
|
2103
|
-
this.timeline.debug(this.buildTimelineMessage({ method: "initialize" }));
|
2104
|
-
this.changeState("initializing");
|
2105
|
-
|
2106
|
-
if (window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR === undefined) {
|
2107
|
-
window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;
|
2108
|
-
}
|
2109
|
-
window.WEB_SOCKET_SWF_LOCATION = Pusher.Dependencies.getRoot() +
|
2110
|
-
"/WebSocketMain.swf";
|
2111
|
-
Pusher.Dependencies.load("flashfallback", function() {
|
2112
|
-
self.changeState("initialized");
|
2113
|
-
});
|
2114
|
-
};
|
2115
|
-
|
2116
|
-
/** @protected */
|
2117
|
-
prototype.createSocket = function(url) {
|
2118
|
-
return new FlashWebSocket(url);
|
2119
|
-
};
|
2120
|
-
|
2121
|
-
/** @protected */
|
2122
|
-
prototype.getQueryString = function() {
|
2123
|
-
return Pusher.AbstractTransport.prototype.getQueryString.call(this) +
|
2124
|
-
"&flash=true";
|
2125
|
-
};
|
2126
|
-
|
2127
|
-
Pusher.FlashTransport = FlashTransport;
|
2128
|
-
}).call(this);
|
2129
|
-
|
2130
|
-
;(function() {
|
2131
|
-
/** Fallback transport using SockJS.
|
2284
|
+
* See transports.js for specific implementations.
|
2132
2285
|
*
|
2133
|
-
* @
|
2286
|
+
* @param {Object} hooks object containing all needed transport hooks
|
2134
2287
|
*/
|
2135
|
-
function
|
2136
|
-
|
2137
|
-
this.options.ignoreNullOrigin = options.ignoreNullOrigin;
|
2288
|
+
function Transport(hooks) {
|
2289
|
+
this.hooks = hooks;
|
2138
2290
|
}
|
2139
|
-
var prototype =
|
2140
|
-
Pusher.Util.extend(prototype, Pusher.AbstractTransport.prototype);
|
2291
|
+
var prototype = Transport.prototype;
|
2141
2292
|
|
2142
|
-
/**
|
2293
|
+
/** Returns whether the transport is supported in the environment.
|
2143
2294
|
*
|
2144
|
-
* @param
|
2145
|
-
* @
|
2146
|
-
* @return {SockJSTransport}
|
2295
|
+
* @param {Object} environment the environment details (encryption, settings)
|
2296
|
+
* @returns {Boolean} true when the transport is supported
|
2147
2297
|
*/
|
2148
|
-
|
2149
|
-
return
|
2298
|
+
prototype.isSupported = function(environment) {
|
2299
|
+
return this.hooks.isSupported(environment);
|
2150
2300
|
};
|
2151
2301
|
|
2152
|
-
/**
|
2302
|
+
/** Creates a transport connection.
|
2153
2303
|
*
|
2154
|
-
* @
|
2304
|
+
* @param {String} name
|
2305
|
+
* @param {Number} priority
|
2306
|
+
* @param {String} key the application key
|
2307
|
+
* @param {Object} options
|
2308
|
+
* @returns {TransportConnection}
|
2155
2309
|
*/
|
2156
|
-
|
2157
|
-
return
|
2310
|
+
prototype.createConnection = function(name, priority, key, options) {
|
2311
|
+
return new Pusher.TransportConnection(
|
2312
|
+
this.hooks, name, priority, key, options
|
2313
|
+
);
|
2158
2314
|
};
|
2159
2315
|
|
2160
|
-
|
2161
|
-
|
2162
|
-
* @see AbstractTransport.prototype.initialize
|
2163
|
-
*/
|
2164
|
-
prototype.initialize = function() {
|
2165
|
-
var self = this;
|
2166
|
-
|
2167
|
-
this.timeline.info(this.buildTimelineMessage({
|
2168
|
-
transport: this.name + (this.options.encrypted ? "s" : "")
|
2169
|
-
}));
|
2170
|
-
this.timeline.debug(this.buildTimelineMessage({ method: "initialize" }));
|
2171
|
-
|
2172
|
-
this.changeState("initializing");
|
2173
|
-
Pusher.Dependencies.load("sockjs", function() {
|
2174
|
-
self.changeState("initialized");
|
2175
|
-
});
|
2176
|
-
};
|
2316
|
+
Pusher.Transport = Transport;
|
2317
|
+
}).call(this);
|
2177
2318
|
|
2178
|
-
|
2319
|
+
(function() {
|
2320
|
+
/** WebSocket transport.
|
2179
2321
|
*
|
2180
|
-
*
|
2322
|
+
* Uses native WebSocket implementation, including MozWebSocket supported by
|
2323
|
+
* earlier Firefox versions.
|
2181
2324
|
*/
|
2182
|
-
|
2183
|
-
|
2184
|
-
|
2325
|
+
Pusher.WSTransport = new Pusher.Transport({
|
2326
|
+
urls: Pusher.URLSchemes.ws,
|
2327
|
+
handlesActivityChecks: false,
|
2328
|
+
supportsPing: false,
|
2185
2329
|
|
2186
|
-
|
2187
|
-
|
2188
|
-
|
2189
|
-
|
2190
|
-
|
2191
|
-
|
2192
|
-
|
2193
|
-
|
2194
|
-
|
2330
|
+
isInitialized: function() {
|
2331
|
+
return Boolean(window.WebSocket || window.MozWebSocket);
|
2332
|
+
},
|
2333
|
+
isSupported: function() {
|
2334
|
+
return Boolean(window.WebSocket || window.MozWebSocket);
|
2335
|
+
},
|
2336
|
+
getSocket: function(url) {
|
2337
|
+
var Constructor = window.WebSocket || window.MozWebSocket;
|
2338
|
+
return new Constructor(url);
|
2339
|
+
}
|
2340
|
+
});
|
2195
2341
|
|
2196
|
-
/**
|
2197
|
-
|
2198
|
-
|
2199
|
-
|
2342
|
+
/** Flash transport using the WebSocket protocol. */
|
2343
|
+
Pusher.FlashTransport = new Pusher.Transport({
|
2344
|
+
file: "flashfallback",
|
2345
|
+
urls: Pusher.URLSchemes.flash,
|
2346
|
+
handlesActivityChecks: false,
|
2347
|
+
supportsPing: false,
|
2200
2348
|
|
2201
|
-
|
2202
|
-
|
2203
|
-
|
2204
|
-
|
2349
|
+
isSupported: function() {
|
2350
|
+
try {
|
2351
|
+
return Boolean(new ActiveXObject('ShockwaveFlash.ShockwaveFlash'));
|
2352
|
+
} catch (e1) {
|
2353
|
+
try {
|
2354
|
+
var nav = Pusher.Util.getNavigator();
|
2355
|
+
return Boolean(
|
2356
|
+
nav &&
|
2357
|
+
nav.mimeTypes &&
|
2358
|
+
nav.mimeTypes["application/x-shockwave-flash"] !== undefined
|
2359
|
+
);
|
2360
|
+
} catch (e2) {
|
2361
|
+
return false;
|
2362
|
+
}
|
2363
|
+
}
|
2364
|
+
},
|
2365
|
+
beforeInitialize: function() {
|
2366
|
+
if (window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR === undefined) {
|
2367
|
+
window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;
|
2368
|
+
}
|
2369
|
+
window.WEB_SOCKET_SWF_LOCATION = Pusher.Dependencies.getRoot() +
|
2370
|
+
"/WebSocketMain.swf";
|
2371
|
+
},
|
2372
|
+
isInitialized: function() {
|
2373
|
+
return window.FlashWebSocket !== undefined;
|
2374
|
+
},
|
2375
|
+
getSocket: function(url) {
|
2376
|
+
return new FlashWebSocket(url);
|
2377
|
+
}
|
2378
|
+
});
|
2205
2379
|
|
2206
|
-
/**
|
2207
|
-
|
2208
|
-
|
2209
|
-
|
2380
|
+
/** SockJS transport. */
|
2381
|
+
Pusher.SockJSTransport = new Pusher.Transport({
|
2382
|
+
file: "sockjs",
|
2383
|
+
urls: Pusher.URLSchemes.sockjs,
|
2384
|
+
handlesActivityChecks: true,
|
2385
|
+
supportsPing: false,
|
2210
2386
|
|
2211
|
-
|
2212
|
-
|
2213
|
-
|
2214
|
-
|
2215
|
-
|
2216
|
-
|
2217
|
-
|
2218
|
-
|
2219
|
-
|
2220
|
-
|
2221
|
-
|
2222
|
-
|
2223
|
-
|
2224
|
-
|
2387
|
+
isSupported: function() {
|
2388
|
+
return true;
|
2389
|
+
},
|
2390
|
+
isInitialized: function() {
|
2391
|
+
return window.SockJS !== undefined;
|
2392
|
+
},
|
2393
|
+
getSocket: function(url, options) {
|
2394
|
+
return new SockJS(url, null, {
|
2395
|
+
js_path: Pusher.Dependencies.getPath("sockjs", {
|
2396
|
+
encrypted: options.encrypted
|
2397
|
+
}),
|
2398
|
+
ignore_null_origin: options.ignoreNullOrigin
|
2399
|
+
});
|
2400
|
+
},
|
2401
|
+
beforeOpen: function(socket, path) {
|
2402
|
+
socket.send(JSON.stringify({
|
2403
|
+
path: path
|
2404
|
+
}));
|
2405
|
+
}
|
2406
|
+
});
|
2407
|
+
|
2408
|
+
var httpConfiguration = {
|
2409
|
+
urls: Pusher.URLSchemes.http,
|
2410
|
+
handlesActivityChecks: false,
|
2411
|
+
supportsPing: true,
|
2412
|
+
isInitialized: function() {
|
2413
|
+
return Boolean(Pusher.HTTP.Socket);
|
2414
|
+
}
|
2225
2415
|
};
|
2226
2416
|
|
2227
|
-
|
2417
|
+
var streamingConfiguration = Pusher.Util.extend(
|
2418
|
+
{ getSocket: function(url) {
|
2419
|
+
return Pusher.HTTP.getStreamingSocket(url);
|
2420
|
+
}
|
2421
|
+
},
|
2422
|
+
httpConfiguration
|
2423
|
+
);
|
2424
|
+
var pollingConfiguration = Pusher.Util.extend(
|
2425
|
+
{ getSocket: function(url) {
|
2426
|
+
return Pusher.HTTP.getPollingSocket(url);
|
2427
|
+
}
|
2428
|
+
},
|
2429
|
+
httpConfiguration
|
2430
|
+
);
|
2431
|
+
|
2432
|
+
var xhrConfiguration = {
|
2433
|
+
file: "xhr",
|
2434
|
+
isSupported: Pusher.Util.isXHRSupported
|
2435
|
+
};
|
2436
|
+
var xdrConfiguration = {
|
2437
|
+
file: "xdr",
|
2438
|
+
isSupported: function(environment) {
|
2439
|
+
return Pusher.Util.isXDRSupported(environment.encrypted);
|
2440
|
+
}
|
2441
|
+
};
|
2442
|
+
|
2443
|
+
/** HTTP streaming transport using CORS-enabled XMLHttpRequest. */
|
2444
|
+
Pusher.XHRStreamingTransport = new Pusher.Transport(
|
2445
|
+
Pusher.Util.extend({}, streamingConfiguration, xhrConfiguration)
|
2446
|
+
);
|
2447
|
+
/** HTTP streaming transport using XDomainRequest (IE 8,9). */
|
2448
|
+
Pusher.XDRStreamingTransport = new Pusher.Transport(
|
2449
|
+
Pusher.Util.extend({}, streamingConfiguration, xdrConfiguration)
|
2450
|
+
);
|
2451
|
+
/** HTTP long-polling transport using CORS-enabled XMLHttpRequest. */
|
2452
|
+
Pusher.XHRPollingTransport = new Pusher.Transport(
|
2453
|
+
Pusher.Util.extend({}, pollingConfiguration, xhrConfiguration)
|
2454
|
+
);
|
2455
|
+
/** HTTP long-polling transport using XDomainRequest (IE 8,9). */
|
2456
|
+
Pusher.XDRPollingTransport = new Pusher.Transport(
|
2457
|
+
Pusher.Util.extend({}, pollingConfiguration, xdrConfiguration)
|
2458
|
+
);
|
2228
2459
|
}).call(this);
|
2229
2460
|
|
2230
2461
|
;(function() {
|
2231
|
-
/**
|
2232
|
-
*
|
2233
|
-
* @see AbstractTransport
|
2234
|
-
*/
|
2235
|
-
function WSTransport(name, priority, key, options) {
|
2236
|
-
Pusher.AbstractTransport.call(this, name, priority, key, options);
|
2237
|
-
}
|
2238
|
-
var prototype = WSTransport.prototype;
|
2239
|
-
Pusher.Util.extend(prototype, Pusher.AbstractTransport.prototype);
|
2240
|
-
|
2241
|
-
/** Creates a new instance of WSTransport.
|
2462
|
+
/** Creates transport connections monitored by a transport manager.
|
2242
2463
|
*
|
2243
|
-
*
|
2244
|
-
*
|
2245
|
-
*
|
2246
|
-
|
2247
|
-
|
2248
|
-
|
2249
|
-
};
|
2250
|
-
|
2251
|
-
/** Checks whether the browser supports WebSockets in any form.
|
2464
|
+
* When a transport is closed, it might mean the environment does not support
|
2465
|
+
* it. It's possible that messages get stuck in an intermediate buffer or
|
2466
|
+
* proxies terminate inactive connections. To combat these problems,
|
2467
|
+
* assistants monitor the connection lifetime, report unclean exits and
|
2468
|
+
* adjust ping timeouts to keep the connection active. The decision to disable
|
2469
|
+
* a transport is the manager's responsibility.
|
2252
2470
|
*
|
2253
|
-
* @
|
2471
|
+
* @param {TransportManager} manager
|
2472
|
+
* @param {TransportConnection} transport
|
2473
|
+
* @param {Object} options
|
2254
2474
|
*/
|
2255
|
-
WSTransport.isSupported = function() {
|
2256
|
-
return window.WebSocket !== undefined || window.MozWebSocket !== undefined;
|
2257
|
-
};
|
2258
|
-
|
2259
|
-
/** @protected */
|
2260
|
-
prototype.createSocket = function(url) {
|
2261
|
-
var constructor = window.WebSocket || window.MozWebSocket;
|
2262
|
-
return new constructor(url);
|
2263
|
-
};
|
2264
|
-
|
2265
|
-
/** @protected */
|
2266
|
-
prototype.getQueryString = function() {
|
2267
|
-
return Pusher.AbstractTransport.prototype.getQueryString.call(this) +
|
2268
|
-
"&flash=false";
|
2269
|
-
};
|
2270
|
-
|
2271
|
-
Pusher.WSTransport = WSTransport;
|
2272
|
-
}).call(this);
|
2273
|
-
|
2274
|
-
;(function() {
|
2275
2475
|
function AssistantToTheTransportManager(manager, transport, options) {
|
2276
2476
|
this.manager = manager;
|
2277
2477
|
this.transport = transport;
|
@@ -2281,10 +2481,20 @@
|
|
2281
2481
|
}
|
2282
2482
|
var prototype = AssistantToTheTransportManager.prototype;
|
2283
2483
|
|
2484
|
+
/** Creates a transport connection.
|
2485
|
+
*
|
2486
|
+
* This function has the same API as Transport#createConnection.
|
2487
|
+
*
|
2488
|
+
* @param {String} name
|
2489
|
+
* @param {Number} priority
|
2490
|
+
* @param {String} key the application key
|
2491
|
+
* @param {Object} options
|
2492
|
+
* @returns {TransportConnection}
|
2493
|
+
*/
|
2284
2494
|
prototype.createConnection = function(name, priority, key, options) {
|
2285
2495
|
var self = this;
|
2286
2496
|
|
2287
|
-
|
2497
|
+
options = Pusher.Util.extend({}, options, {
|
2288
2498
|
activityTimeout: self.pingDelay
|
2289
2499
|
});
|
2290
2500
|
var connection = self.transport.createConnection(
|
@@ -2318,20 +2528,42 @@
|
|
2318
2528
|
return connection;
|
2319
2529
|
};
|
2320
2530
|
|
2321
|
-
|
2322
|
-
|
2531
|
+
/** Returns whether the transport is supported in the environment.
|
2532
|
+
*
|
2533
|
+
* This function has the same API as Transport#isSupported. Might return false
|
2534
|
+
* when the manager decides to kill the transport.
|
2535
|
+
*
|
2536
|
+
* @param {Object} environment the environment details (encryption, settings)
|
2537
|
+
* @returns {Boolean} true when the transport is supported
|
2538
|
+
*/
|
2539
|
+
prototype.isSupported = function(environment) {
|
2540
|
+
return this.manager.isAlive() && this.transport.isSupported(environment);
|
2323
2541
|
};
|
2324
2542
|
|
2325
2543
|
Pusher.AssistantToTheTransportManager = AssistantToTheTransportManager;
|
2326
2544
|
}).call(this);
|
2327
2545
|
|
2328
2546
|
;(function() {
|
2547
|
+
/** Keeps track of the number of lives left for a transport.
|
2548
|
+
*
|
2549
|
+
* In the beginning of a session, transports may be assigned a number of
|
2550
|
+
* lives. When an AssistantToTheTransportManager instance reports a transport
|
2551
|
+
* connection closed uncleanly, the transport loses a life. When the number
|
2552
|
+
* of lives drops to zero, the transport gets disabled by its manager.
|
2553
|
+
*
|
2554
|
+
* @param {Object} options
|
2555
|
+
*/
|
2329
2556
|
function TransportManager(options) {
|
2330
2557
|
this.options = options || {};
|
2331
2558
|
this.livesLeft = this.options.lives || Infinity;
|
2332
2559
|
}
|
2333
2560
|
var prototype = TransportManager.prototype;
|
2334
2561
|
|
2562
|
+
/** Creates a assistant for the transport.
|
2563
|
+
*
|
2564
|
+
* @param {Transport} transport
|
2565
|
+
* @returns {AssistantToTheTransportManager}
|
2566
|
+
*/
|
2335
2567
|
prototype.getAssistant = function(transport) {
|
2336
2568
|
return new Pusher.AssistantToTheTransportManager(this, transport, {
|
2337
2569
|
minPingDelay: this.options.minPingDelay,
|
@@ -2339,10 +2571,15 @@
|
|
2339
2571
|
});
|
2340
2572
|
};
|
2341
2573
|
|
2574
|
+
/** Returns whether the transport has any lives left.
|
2575
|
+
*
|
2576
|
+
* @returns {Boolean}
|
2577
|
+
*/
|
2342
2578
|
prototype.isAlive = function() {
|
2343
2579
|
return this.livesLeft > 0;
|
2344
2580
|
};
|
2345
2581
|
|
2582
|
+
/** Takes one life from the transport. */
|
2346
2583
|
prototype.reportDeath = function() {
|
2347
2584
|
this.livesLeft -= 1;
|
2348
2585
|
};
|
@@ -2367,7 +2604,11 @@
|
|
2367
2604
|
var transports = {
|
2368
2605
|
ws: Pusher.WSTransport,
|
2369
2606
|
flash: Pusher.FlashTransport,
|
2370
|
-
sockjs: Pusher.SockJSTransport
|
2607
|
+
sockjs: Pusher.SockJSTransport,
|
2608
|
+
xhr_streaming: Pusher.XHRStreamingTransport,
|
2609
|
+
xdr_streaming: Pusher.XDRStreamingTransport,
|
2610
|
+
xhr_polling: Pusher.XHRPollingTransport,
|
2611
|
+
xdr_polling: Pusher.XDRPollingTransport
|
2371
2612
|
};
|
2372
2613
|
|
2373
2614
|
var UnsupportedStrategy = {
|
@@ -2396,6 +2637,10 @@
|
|
2396
2637
|
}
|
2397
2638
|
|
2398
2639
|
var globalContext = {
|
2640
|
+
extend: function(context, first, second) {
|
2641
|
+
return [Pusher.Util.extend({}, first, second), context];
|
2642
|
+
},
|
2643
|
+
|
2399
2644
|
def: function(context, name, value) {
|
2400
2645
|
if (context[name] !== undefined) {
|
2401
2646
|
throw "Redefining symbol " + name;
|
@@ -2718,12 +2963,12 @@
|
|
2718
2963
|
var prototype = Connection.prototype;
|
2719
2964
|
Pusher.Util.extend(prototype, Pusher.EventsDispatcher.prototype);
|
2720
2965
|
|
2721
|
-
/** Returns whether used transport handles
|
2966
|
+
/** Returns whether used transport handles activity checks by itself
|
2722
2967
|
*
|
2723
|
-
* @returns {Boolean} true if
|
2968
|
+
* @returns {Boolean} true if activity checks are handled by the transport
|
2724
2969
|
*/
|
2725
|
-
prototype.
|
2726
|
-
return this.transport.
|
2970
|
+
prototype.handlesActivityChecks = function() {
|
2971
|
+
return this.transport.handlesActivityChecks();
|
2727
2972
|
};
|
2728
2973
|
|
2729
2974
|
/** Sends raw data.
|
@@ -2750,6 +2995,19 @@
|
|
2750
2995
|
return this.send(Pusher.Protocol.encodeMessage(message));
|
2751
2996
|
};
|
2752
2997
|
|
2998
|
+
/** Sends a ping message to the server.
|
2999
|
+
*
|
3000
|
+
* Basing on the underlying transport, it might send either transport's
|
3001
|
+
* protocol-specific ping or pusher:ping event.
|
3002
|
+
*/
|
3003
|
+
prototype.ping = function() {
|
3004
|
+
if (this.transport.supportsPing()) {
|
3005
|
+
this.transport.ping();
|
3006
|
+
} else {
|
3007
|
+
this.send_event('pusher:ping', {});
|
3008
|
+
}
|
3009
|
+
};
|
3010
|
+
|
2753
3011
|
/** Closes the connection. */
|
2754
3012
|
prototype.close = function() {
|
2755
3013
|
this.transport.close();
|
@@ -2759,58 +3017,63 @@
|
|
2759
3017
|
prototype.bindListeners = function() {
|
2760
3018
|
var self = this;
|
2761
3019
|
|
2762
|
-
var
|
2763
|
-
|
2764
|
-
|
2765
|
-
|
2766
|
-
|
2767
|
-
|
2768
|
-
|
2769
|
-
|
2770
|
-
|
2771
|
-
|
2772
|
-
|
3020
|
+
var listeners = {
|
3021
|
+
message: function(m) {
|
3022
|
+
var message;
|
3023
|
+
try {
|
3024
|
+
message = Pusher.Protocol.decodeMessage(m);
|
3025
|
+
} catch(e) {
|
3026
|
+
self.emit('error', {
|
3027
|
+
type: 'MessageParseError',
|
3028
|
+
error: e,
|
3029
|
+
data: m.data
|
3030
|
+
});
|
3031
|
+
}
|
2773
3032
|
|
2774
|
-
|
2775
|
-
|
3033
|
+
if (message !== undefined) {
|
3034
|
+
Pusher.debug('Event recd', message);
|
3035
|
+
|
3036
|
+
switch (message.event) {
|
3037
|
+
case 'pusher:error':
|
3038
|
+
self.emit('error', { type: 'PusherError', data: message.data });
|
3039
|
+
break;
|
3040
|
+
case 'pusher:ping':
|
3041
|
+
self.emit("ping");
|
3042
|
+
break;
|
3043
|
+
case 'pusher:pong':
|
3044
|
+
self.emit("pong");
|
3045
|
+
break;
|
3046
|
+
}
|
3047
|
+
self.emit('message', message);
|
3048
|
+
}
|
3049
|
+
},
|
3050
|
+
activity: function() {
|
3051
|
+
self.emit("activity");
|
3052
|
+
},
|
3053
|
+
error: function(error) {
|
3054
|
+
self.emit("error", { type: "WebSocketError", error: error });
|
3055
|
+
},
|
3056
|
+
closed: function(closeEvent) {
|
3057
|
+
unbindListeners();
|
2776
3058
|
|
2777
|
-
|
2778
|
-
|
2779
|
-
self.emit('error', { type: 'PusherError', data: message.data });
|
2780
|
-
break;
|
2781
|
-
case 'pusher:ping':
|
2782
|
-
self.emit("ping");
|
2783
|
-
break;
|
2784
|
-
case 'pusher:pong':
|
2785
|
-
self.emit("pong");
|
2786
|
-
break;
|
3059
|
+
if (closeEvent && closeEvent.code) {
|
3060
|
+
self.handleCloseEvent(closeEvent);
|
2787
3061
|
}
|
2788
|
-
self.emit('message', message);
|
2789
|
-
}
|
2790
|
-
};
|
2791
|
-
var onError = function(error) {
|
2792
|
-
self.emit("error", { type: "WebSocketError", error: error });
|
2793
|
-
};
|
2794
|
-
var onClosed = function(closeEvent) {
|
2795
|
-
unbindListeners();
|
2796
3062
|
|
2797
|
-
|
2798
|
-
self.
|
3063
|
+
self.transport = null;
|
3064
|
+
self.emit("closed");
|
2799
3065
|
}
|
2800
|
-
|
2801
|
-
self.transport = null;
|
2802
|
-
self.emit("closed");
|
2803
3066
|
};
|
2804
3067
|
|
2805
3068
|
var unbindListeners = function() {
|
2806
|
-
|
2807
|
-
|
2808
|
-
|
3069
|
+
Pusher.Util.objectApply(listeners, function(listener, event) {
|
3070
|
+
self.transport.unbind(event, listener);
|
3071
|
+
});
|
2809
3072
|
};
|
2810
3073
|
|
2811
|
-
|
2812
|
-
|
2813
|
-
|
3074
|
+
Pusher.Util.objectApply(listeners, function(listener, event) {
|
3075
|
+
self.transport.bind(event, listener);
|
3076
|
+
});
|
2814
3077
|
};
|
2815
3078
|
|
2816
3079
|
/** @private */
|
@@ -2960,7 +3223,7 @@
|
|
2960
3223
|
});
|
2961
3224
|
Pusher.Network.bind("offline", function() {
|
2962
3225
|
self.timeline.info({ netinfo: "offline" });
|
2963
|
-
if (self.
|
3226
|
+
if (self.connection) {
|
2964
3227
|
self.sendActivityCheck();
|
2965
3228
|
}
|
2966
3229
|
});
|
@@ -3058,7 +3321,6 @@
|
|
3058
3321
|
this.abortConnecting();
|
3059
3322
|
this.clearRetryTimer();
|
3060
3323
|
this.clearUnavailableTimer();
|
3061
|
-
this.stopActivityCheck();
|
3062
3324
|
if (this.connection) {
|
3063
3325
|
var connection = this.abandonConnection();
|
3064
3326
|
connection.close();
|
@@ -3117,7 +3379,7 @@
|
|
3117
3379
|
prototype.sendActivityCheck = function() {
|
3118
3380
|
var self = this;
|
3119
3381
|
self.stopActivityCheck();
|
3120
|
-
self.
|
3382
|
+
self.connection.ping();
|
3121
3383
|
// wait for pong response
|
3122
3384
|
self.activityTimer = new Pusher.Timer(
|
3123
3385
|
self.options.pongTimeout,
|
@@ -3133,7 +3395,7 @@
|
|
3133
3395
|
var self = this;
|
3134
3396
|
self.stopActivityCheck();
|
3135
3397
|
// send ping after inactivity
|
3136
|
-
if (!self.connection.
|
3398
|
+
if (!self.connection.handlesActivityChecks()) {
|
3137
3399
|
self.activityTimer = new Pusher.Timer(self.activityTimeout, function() {
|
3138
3400
|
self.sendActivityCheck();
|
3139
3401
|
});
|
@@ -3159,6 +3421,9 @@
|
|
3159
3421
|
ping: function() {
|
3160
3422
|
self.send_event('pusher:pong', {});
|
3161
3423
|
},
|
3424
|
+
activity: function() {
|
3425
|
+
self.resetActivityCheck();
|
3426
|
+
},
|
3162
3427
|
error: function(error) {
|
3163
3428
|
// just emit error to user - socket will already be closed by browser
|
3164
3429
|
self.emit("error", { type: "WebSocketError", error: error });
|
@@ -3235,6 +3500,7 @@
|
|
3235
3500
|
if (!this.connection) {
|
3236
3501
|
return;
|
3237
3502
|
}
|
3503
|
+
this.stopActivityCheck();
|
3238
3504
|
for (var event in this.connectionCallbacks) {
|
3239
3505
|
this.connection.unbind(event, this.connectionCallbacks[event]);
|
3240
3506
|
}
|
@@ -3689,8 +3955,8 @@
|
|
3689
3955
|
}
|
3690
3956
|
|
3691
3957
|
xhr.onreadystatechange = function() {
|
3692
|
-
if (xhr.readyState
|
3693
|
-
if (xhr.status
|
3958
|
+
if (xhr.readyState === 4) {
|
3959
|
+
if (xhr.status === 200) {
|
3694
3960
|
var data, parsed = false;
|
3695
3961
|
|
3696
3962
|
try {
|