pusher-fake 0.9.0 → 0.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1a0baacfdc962376a761f7cae9a985206c7f342b
4
- data.tar.gz: 3437e5854d08159a3bcb9ca3444330e359dfc973
3
+ metadata.gz: 39d56e9bb6902ed10adbe4bca8166649d40749fc
4
+ data.tar.gz: 5a8e70714a86266a036d0a56c31564fbf8972dd2
5
5
  SHA512:
6
- metadata.gz: 87dd985dcb991ca210d6b8a6fa84f45b869fee5c4898cf4810f925fd71c1c45d02148928e1e3c5b1ceebef94deaf0959312b05fc5777366c4da1b0f7c649cda8
7
- data.tar.gz: 91110daa9de7f58e8888672b6ac86fda72802083fc504d92dbb662900d9a97ac9b73087a8c47a265942da6923ee380b29acbebf185d5f6f31fbd8ee1c2a1c63e
6
+ metadata.gz: 31ab418c7631fb803180cef6bca7eddac1ba06b5dc64be52cd35033412404b6e328cf631c962e11a1a8fe8f5a613dc7fa80d834004b2a0df6f3d4ea0eb62149e
7
+ data.tar.gz: bac0c88bd45616f6dc8ef8a829d2fe4682be67879778c95786aca8cc77e4175679c578bbf958b13cf64e064fc059a4a7ac56d4c55963f10ee014997b7141f22d
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Pusher JavaScript Library v2.0.5
2
+ * Pusher JavaScript Library v2.1.2
3
3
  * http://pusherapp.com/
4
4
  *
5
5
  * Copyright 2013, Pusher
@@ -8,70 +8,75 @@
8
8
 
9
9
  ;(function() {
10
10
  function Pusher(app_key, options) {
11
+ checkAppKey(app_key);
12
+ options = options || {};
13
+
11
14
  var self = this;
12
15
 
13
- this.options = options || {};
14
16
  this.key = app_key;
17
+ this.config = Pusher.Util.extend(
18
+ Pusher.getGlobalConfig(),
19
+ options.cluster ? Pusher.getClusterConfig(options.cluster) : {},
20
+ options
21
+ );
22
+
15
23
  this.channels = new Pusher.Channels();
16
24
  this.global_emitter = new Pusher.EventsDispatcher();
17
25
  this.sessionID = Math.floor(Math.random() * 1000000000);
18
26
 
19
- checkAppKey(this.key);
27
+ this.timeline = new Pusher.Timeline(this.key, this.sessionID, {
28
+ features: Pusher.Util.getClientFeatures(),
29
+ params: this.config.timelineParams || {},
30
+ limit: 50,
31
+ level: Pusher.Timeline.INFO,
32
+ version: Pusher.VERSION
33
+ });
34
+ if (!this.config.disableStats) {
35
+ this.timelineSender = new Pusher.TimelineSender(this.timeline, {
36
+ host: this.config.statsHost,
37
+ path: "/timeline"
38
+ });
39
+ }
20
40
 
21
41
  var getStrategy = function(options) {
22
42
  return Pusher.StrategyBuilder.build(
23
- Pusher.getDefaultStrategy(),
24
- Pusher.Util.extend({}, self.options, options)
43
+ Pusher.getDefaultStrategy(self.config),
44
+ Pusher.Util.extend({}, self.config, options)
25
45
  );
26
46
  };
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: 50,
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
 
47
48
  this.connection = new Pusher.ConnectionManager(
48
49
  this.key,
49
50
  Pusher.Util.extend(
50
51
  { getStrategy: getStrategy,
51
- getTimeline: getTimeline,
52
- getTimelineSender: getTimelineSender,
53
- activityTimeout: Pusher.activity_timeout,
54
- pongTimeout: Pusher.pong_timeout,
55
- unavailableTimeout: Pusher.unavailable_timeout
52
+ timeline: this.timeline,
53
+ activityTimeout: this.config.activity_timeout,
54
+ pongTimeout: this.config.pong_timeout,
55
+ unavailableTimeout: this.config.unavailable_timeout
56
56
  },
57
- this.options,
57
+ this.config,
58
58
  { encrypted: this.isEncrypted() }
59
59
  )
60
60
  );
61
61
 
62
62
  this.connection.bind('connected', function() {
63
63
  self.subscribeAll();
64
+ if (self.timelineSender) {
65
+ self.timelineSender.send(self.connection.isEncrypted());
66
+ }
64
67
  });
65
68
  this.connection.bind('message', function(params) {
66
69
  var internal = (params.event.indexOf('pusher_internal:') === 0);
67
70
  if (params.channel) {
68
71
  var channel = self.channel(params.channel);
69
72
  if (channel) {
70
- channel.emit(params.event, params.data);
73
+ channel.handleEvent(params.event, params.data);
71
74
  }
72
75
  }
73
76
  // Emit globaly [deprecated]
74
- if (!internal) self.global_emitter.emit(params.event, params.data);
77
+ if (!internal) {
78
+ self.global_emitter.emit(params.event, params.data);
79
+ }
75
80
  });
76
81
  this.connection.bind('disconnected', function() {
77
82
  self.channels.disconnect();
@@ -125,10 +130,25 @@
125
130
 
126
131
  prototype.connect = function() {
127
132
  this.connection.connect();
133
+
134
+ if (this.timelineSender) {
135
+ if (!this.timelineSenderTimer) {
136
+ var encrypted = this.connection.isEncrypted();
137
+ var timelineSender = this.timelineSender;
138
+ this.timelineSenderTimer = new Pusher.PeriodicTimer(60000, function() {
139
+ timelineSender.send(encrypted);
140
+ });
141
+ }
142
+ }
128
143
  };
129
144
 
130
145
  prototype.disconnect = function() {
131
146
  this.connection.disconnect();
147
+
148
+ if (this.timelineSenderTimer) {
149
+ this.timelineSenderTimer.ensureAborted();
150
+ this.timelineSenderTimer = null;
151
+ }
132
152
  };
133
153
 
134
154
  prototype.bind = function(event_name, callback) {
@@ -151,35 +171,17 @@
151
171
  };
152
172
 
153
173
  prototype.subscribe = function(channel_name) {
154
- var self = this;
155
174
  var channel = this.channels.add(channel_name, this);
156
-
157
175
  if (this.connection.state === 'connected') {
158
- channel.authorize(
159
- this.connection.socket_id,
160
- this.options,
161
- function(err, data) {
162
- if (err) {
163
- channel.emit('pusher:subscription_error', data);
164
- } else {
165
- self.send_event('pusher:subscribe', {
166
- channel: channel_name,
167
- auth: data.auth,
168
- channel_data: data.channel_data
169
- });
170
- }
171
- }
172
- );
176
+ channel.subscribe();
173
177
  }
174
178
  return channel;
175
179
  };
176
180
 
177
181
  prototype.unsubscribe = function(channel_name) {
178
- this.channels.remove(channel_name);
182
+ var channel = this.channels.remove(channel_name);
179
183
  if (this.connection.state === 'connected') {
180
- this.send_event('pusher:unsubscribe', {
181
- channel: channel_name
182
- });
184
+ channel.unsubscribe();
183
185
  }
184
186
  };
185
187
 
@@ -191,7 +193,7 @@
191
193
  if (Pusher.Util.getDocumentLocation().protocol === "https:") {
192
194
  return true;
193
195
  } else {
194
- return !!this.options.encrypted;
196
+ return Boolean(this.config.encrypted);
195
197
  }
196
198
  };
197
199
 
@@ -488,7 +490,11 @@
488
490
  },
489
491
 
490
492
  getLocalStorage: function() {
491
- return window.localStorage;
493
+ try {
494
+ return window.localStorage;
495
+ } catch (e) {
496
+ return undefined;
497
+ }
492
498
  },
493
499
 
494
500
  getClientFeatures: function() {
@@ -503,39 +509,40 @@
503
509
  }).call(this);
504
510
 
505
511
  ;(function() {
506
- Pusher.VERSION = '2.0.5';
512
+ Pusher.VERSION = '2.1.2';
507
513
  Pusher.PROTOCOL = 6;
508
514
 
509
- // WS connection parameters
515
+ // DEPRECATED: WS connection parameters
510
516
  Pusher.host = 'ws.pusherapp.com';
511
517
  Pusher.ws_port = 80;
512
518
  Pusher.wss_port = 443;
513
- // SockJS fallback parameters
519
+ // DEPRECATED: SockJS fallback parameters
514
520
  Pusher.sockjs_host = 'sockjs.pusher.com';
515
521
  Pusher.sockjs_http_port = 80;
516
522
  Pusher.sockjs_https_port = 443;
517
523
  Pusher.sockjs_path = "/pusher";
518
- // Stats
524
+ // DEPRECATED: Stats
519
525
  Pusher.stats_host = 'stats.pusher.com';
520
- // Other settings
526
+ // DEPRECATED: Other settings
521
527
  Pusher.channel_auth_endpoint = '/pusher/auth';
522
- Pusher.cdn_http = 'http://js.pusher.com/';
523
- Pusher.cdn_https = 'https://d3dy5gmtp8yhk7.cloudfront.net/';
524
- Pusher.dependency_suffix = '';
525
528
  Pusher.channel_auth_transport = 'ajax';
526
529
  Pusher.activity_timeout = 120000;
527
530
  Pusher.pong_timeout = 30000;
528
531
  Pusher.unavailable_timeout = 10000;
532
+ // CDN configuration
533
+ Pusher.cdn_http = 'http://js.pusher.com/';
534
+ Pusher.cdn_https = 'https://d3dy5gmtp8yhk7.cloudfront.net/';
535
+ Pusher.dependency_suffix = '';
529
536
 
530
- Pusher.getDefaultStrategy = function() {
537
+ Pusher.getDefaultStrategy = function(config) {
531
538
  return [
532
539
  [":def", "ws_options", {
533
- hostUnencrypted: Pusher.host + ":" + Pusher.ws_port,
534
- hostEncrypted: Pusher.host + ":" + Pusher.wss_port
540
+ hostUnencrypted: config.wsHost + ":" + config.wsPort,
541
+ hostEncrypted: config.wsHost + ":" + config.wssPort
535
542
  }],
536
543
  [":def", "sockjs_options", {
537
- hostUnencrypted: Pusher.sockjs_host + ":" + Pusher.sockjs_http_port,
538
- hostEncrypted: Pusher.sockjs_host + ":" + Pusher.sockjs_https_port
544
+ hostUnencrypted: config.httpHost + ":" + config.httpPort,
545
+ hostEncrypted: config.httpHost + ":" + config.httpsPort
539
546
  }],
540
547
  [":def", "timeouts", {
541
548
  loop: true,
@@ -543,7 +550,12 @@
543
550
  timeoutLimit: 60000
544
551
  }],
545
552
 
546
- [":def", "ws_manager", [":transport_manager", { lives: 2 }]],
553
+ [":def", "ws_manager", [":transport_manager", {
554
+ lives: 2,
555
+ minPingDelay: 10000,
556
+ maxPingDelay: config.activity_timeout
557
+ }]],
558
+
547
559
  [":def_transport", "ws", "ws", 3, ":ws_options", ":ws_manager"],
548
560
  [":def_transport", "flash", "flash", 2, ":ws_options", ":ws_manager"],
549
561
  [":def_transport", "sockjs", "sockjs", 1, ":sockjs_options"],
@@ -569,6 +581,34 @@
569
581
  };
570
582
  }).call(this);
571
583
 
584
+ ;(function() {
585
+ Pusher.getGlobalConfig = function() {
586
+ return {
587
+ wsHost: Pusher.host,
588
+ wsPort: Pusher.ws_port,
589
+ wssPort: Pusher.wss_port,
590
+ httpHost: Pusher.sockjs_host,
591
+ httpPort: Pusher.sockjs_http_port,
592
+ httpsPort: Pusher.sockjs_https_port,
593
+ httpPath: Pusher.sockjs_path,
594
+ statsHost: Pusher.stats_host,
595
+ authEndpoint: Pusher.channel_auth_endpoint,
596
+ authTransport: Pusher.channel_auth_transport,
597
+ // TODO make this consistent with other options in next major version
598
+ activity_timeout: Pusher.activity_timeout,
599
+ pong_timeout: Pusher.pong_timeout,
600
+ unavailable_timeout: Pusher.unavailable_timeout
601
+ };
602
+ };
603
+
604
+ Pusher.getClusterConfig = function(clusterName) {
605
+ return {
606
+ wsHost: "ws-" + clusterName + ".pusher.com",
607
+ httpHost: "sockjs-" + clusterName + ".pusher.com"
608
+ };
609
+ };
610
+ }).call(this);
611
+
572
612
  ;(function() {
573
613
  function buildExceptionClass(name) {
574
614
  var klass = function(message) {
@@ -697,24 +737,22 @@
697
737
 
698
738
  if (this.loaded[name]) {
699
739
  callback();
700
- return;
701
- }
740
+ } else if (this.loading[name] && this.loading[name].length > 0) {
741
+ this.loading[name].push(callback);
742
+ } else {
743
+ this.loading[name] = [callback];
702
744
 
703
- if (!this.loading[name]) {
704
- this.loading[name] = [];
705
- }
706
- this.loading[name].push(callback);
707
- if (this.loading[name].length > 1) {
708
- return;
709
- }
745
+ require(this.getPath(name), function() {
746
+ self.loaded[name] = true;
710
747
 
711
- require(this.getPath(name), function() {
712
- for (var i = 0; i < self.loading[name].length; i++) {
713
- self.loading[name][i]();
714
- }
715
- delete self.loading[name];
716
- self.loaded[name] = true;
717
- });
748
+ if (self.loading[name]) {
749
+ for (var i = 0; i < self.loading[name].length; i++) {
750
+ self.loading[name][i]();
751
+ }
752
+ delete self.loading[name];
753
+ }
754
+ });
755
+ }
718
756
  };
719
757
 
720
758
  /** Returns a root URL for pusher-js CDN.
@@ -850,6 +888,42 @@
850
888
  Pusher.Timer = Timer;
851
889
  }).call(this);
852
890
 
891
+ ;(function() {
892
+ /** Cross-browser compatible periodic timer abstraction.
893
+ *
894
+ * @param {Number} interval
895
+ * @param {Function} callback
896
+ */
897
+ function PeriodicTimer(interval, callback) {
898
+ var self = this;
899
+
900
+ this.interval = setInterval(function() {
901
+ if (self.interval !== null) {
902
+ callback();
903
+ }
904
+ }, interval);
905
+ }
906
+ var prototype = PeriodicTimer.prototype;
907
+
908
+ /** Returns whether the timer is still running.
909
+ *
910
+ * @return {Boolean}
911
+ */
912
+ prototype.isRunning = function() {
913
+ return this.interval !== null;
914
+ };
915
+
916
+ /** Aborts a timer when it's running. */
917
+ prototype.ensureAborted = function() {
918
+ if (this.interval) {
919
+ clearInterval(this.interval);
920
+ this.interval = null;
921
+ }
922
+ };
923
+
924
+ Pusher.PeriodicTimer = PeriodicTimer;
925
+ }).call(this);
926
+
853
927
  (function() {
854
928
 
855
929
  var Base64 = {
@@ -1114,7 +1188,9 @@
1114
1188
  if (!error) {
1115
1189
  self.sent++;
1116
1190
  }
1117
- callback(error, result);
1191
+ if (callback) {
1192
+ callback(error, result);
1193
+ }
1118
1194
  });
1119
1195
 
1120
1196
  return true;
@@ -1135,26 +1211,30 @@
1135
1211
  }
1136
1212
  var prototype = TimelineSender.prototype;
1137
1213
 
1138
- prototype.send = function(callback) {
1214
+ prototype.send = function(encrypted, callback) {
1139
1215
  if (this.timeline.isEmpty()) {
1140
1216
  return;
1141
1217
  }
1142
1218
 
1143
- var options = this.options;
1144
- var scheme = "http" + (this.isEncrypted() ? "s" : "") + "://";
1219
+ var self = this;
1220
+ var scheme = "http" + (encrypted ? "s" : "") + "://";
1145
1221
 
1146
1222
  var sendJSONP = function(data, callback) {
1147
- return Pusher.JSONPRequest.send({
1223
+ var params = {
1148
1224
  data: data,
1149
- url: scheme + options.host + options.path,
1225
+ url: scheme + (self.host || self.options.host) + self.options.path,
1150
1226
  receiver: Pusher.JSONP
1151
- }, callback);
1227
+ };
1228
+ return Pusher.JSONPRequest.send(params, function(error, result) {
1229
+ if (result.host) {
1230
+ self.host = result.host;
1231
+ }
1232
+ if (callback) {
1233
+ callback(error, result);
1234
+ }
1235
+ });
1152
1236
  };
1153
- this.timeline.send(sendJSONP, callback);
1154
- };
1155
-
1156
- prototype.isEncrypted = function() {
1157
- return !!this.options.encrypted;
1237
+ self.timeline.send(sendJSONP, callback);
1158
1238
  };
1159
1239
 
1160
1240
  Pusher.TimelineSender = TimelineSender;
@@ -1826,7 +1906,9 @@
1826
1906
  // Workaround for MobileSafari bug (see https://gist.github.com/2052006)
1827
1907
  var self = this;
1828
1908
  setTimeout(function() {
1829
- self.socket.send(data);
1909
+ if (self.socket) {
1910
+ self.socket.send(data);
1911
+ }
1830
1912
  }, 0);
1831
1913
  return true;
1832
1914
  } else {
@@ -1847,14 +1929,20 @@
1847
1929
  /** @protected */
1848
1930
  prototype.onError = function(error) {
1849
1931
  this.emit("error", { type: 'WebSocketError', error: error });
1850
- this.timeline.error(this.buildTimelineMessage({
1851
- error: getErrorDetails(error)
1852
- }));
1932
+ this.timeline.error(this.buildTimelineMessage({}));
1853
1933
  };
1854
1934
 
1855
1935
  /** @protected */
1856
1936
  prototype.onClose = function(closeEvent) {
1857
- this.changeState("closed", closeEvent);
1937
+ if (closeEvent) {
1938
+ this.changeState("closed", {
1939
+ code: closeEvent.code,
1940
+ reason: closeEvent.reason,
1941
+ wasClean: closeEvent.wasClean
1942
+ });
1943
+ } else {
1944
+ this.changeState("closed");
1945
+ }
1858
1946
  this.socket = undefined;
1859
1947
  };
1860
1948
 
@@ -1926,22 +2014,6 @@
1926
2014
  return Pusher.Util.extend({ cid: this.id }, message);
1927
2015
  };
1928
2016
 
1929
- function getErrorDetails(error) {
1930
- if (typeof error === "string") {
1931
- return error;
1932
- }
1933
- if (typeof error === "object") {
1934
- return Pusher.Util.mapObject(error, function(value) {
1935
- var valueType = typeof value;
1936
- if (valueType === "object" || valueType == "function") {
1937
- return valueType;
1938
- }
1939
- return value;
1940
- });
1941
- }
1942
- return typeof error;
1943
- }
1944
-
1945
2017
  Pusher.AbstractTransport = AbstractTransport;
1946
2018
  }).call(this);
1947
2019
 
@@ -2103,7 +2175,7 @@
2103
2175
 
2104
2176
  /** @protected */
2105
2177
  prototype.getPath = function() {
2106
- return "/pusher";
2178
+ return this.options.httpPath || "/pusher";
2107
2179
  };
2108
2180
 
2109
2181
  /** @protected */
@@ -2178,8 +2250,8 @@
2178
2250
  function AssistantToTheTransportManager(manager, transport, options) {
2179
2251
  this.manager = manager;
2180
2252
  this.transport = transport;
2181
- this.minPingDelay = options.minPingDelay || 10000;
2182
- this.maxPingDelay = options.maxPingDelay || Pusher.activity_timeout;
2253
+ this.minPingDelay = options.minPingDelay;
2254
+ this.maxPingDelay = options.maxPingDelay;
2183
2255
  this.pingDelay = null;
2184
2256
  }
2185
2257
  var prototype = AssistantToTheTransportManager.prototype;
@@ -2214,11 +2286,11 @@
2214
2286
  pingTimer = null;
2215
2287
  }
2216
2288
 
2217
- if (closeEvent.wasClean) {
2218
- return;
2219
- }
2220
-
2221
- if (openTimestamp) {
2289
+ if (closeEvent.code === 1002 || closeEvent.code === 1003) {
2290
+ // we don't want to use transports not obeying the protocol
2291
+ self.manager.reportDeath();
2292
+ } else if (!closeEvent.wasClean && openTimestamp) {
2293
+ // report deaths only for short-living transport
2222
2294
  var lifespan = Pusher.Util.now() - openTimestamp;
2223
2295
  if (lifespan < 2 * self.maxPingDelay) {
2224
2296
  self.manager.reportDeath();
@@ -2822,7 +2894,7 @@
2822
2894
  this.state = "initialized";
2823
2895
  this.connection = null;
2824
2896
  this.encrypted = !!options.encrypted;
2825
- this.timeline = this.options.getTimeline();
2897
+ this.timeline = this.options.timeline;
2826
2898
 
2827
2899
  this.connectionCallbacks = this.buildConnectionCallbacks();
2828
2900
  this.errorCallbacks = this.buildErrorCallbacks();
@@ -2844,14 +2916,6 @@
2844
2916
  }
2845
2917
  });
2846
2918
 
2847
- var sendTimeline = function() {
2848
- if (self.timelineSender) {
2849
- self.timelineSender.send(function() {});
2850
- }
2851
- };
2852
- this.bind("connected", sendTimeline);
2853
- setInterval(sendTimeline, 60000);
2854
-
2855
2919
  this.updateStrategy();
2856
2920
  }
2857
2921
  var prototype = ConnectionManager.prototype;
@@ -2883,19 +2947,18 @@
2883
2947
  }
2884
2948
 
2885
2949
  self.updateState("connecting");
2886
- self.timelineSender = self.options.getTimelineSender(
2887
- self.timeline,
2888
- { encrypted: self.encrypted },
2889
- self
2890
- );
2891
2950
 
2892
2951
  var callback = function(error, handshake) {
2893
2952
  if (error) {
2894
2953
  self.runner = self.strategy.connect(0, callback);
2895
2954
  } else {
2896
- // we don't support switching connections yet
2897
- self.runner.abort();
2898
- self.handshakeCallbacks[handshake.action](handshake);
2955
+ if (handshake.action === "error") {
2956
+ self.timeline.error({ handshakeError: handshake.error });
2957
+ } else {
2958
+ // we don't support switching connections yet
2959
+ self.runner.abort();
2960
+ self.handshakeCallbacks[handshake.action](handshake);
2961
+ }
2899
2962
  }
2900
2963
  };
2901
2964
  self.runner = self.strategy.connect(0, callback);
@@ -2946,6 +3009,10 @@
2946
3009
  }
2947
3010
  };
2948
3011
 
3012
+ prototype.isEncrypted = function() {
3013
+ return this.encrypted;
3014
+ };
3015
+
2949
3016
  /** @private */
2950
3017
  prototype.updateStrategy = function() {
2951
3018
  this.strategy = this.options.getStrategy({
@@ -2959,6 +3026,9 @@
2959
3026
  prototype.retryIn = function(delay) {
2960
3027
  var self = this;
2961
3028
  self.timeline.info({ action: "retry", delay: delay });
3029
+ if (delay > 0) {
3030
+ self.emit("connecting_in", Math.round(delay / 1000));
3031
+ }
2962
3032
  self.retryTimer = new Pusher.Timer(delay || 0, function() {
2963
3033
  self.disconnect();
2964
3034
  self.connect();
@@ -3004,6 +3074,7 @@
3004
3074
  self.activityTimer = new Pusher.Timer(
3005
3075
  self.options.pongTimeout,
3006
3076
  function() {
3077
+ self.timeline.error({ pong_timed_out: self.options.pongTimeout });
3007
3078
  self.connection.close();
3008
3079
  }
3009
3080
  );
@@ -3055,6 +3126,7 @@
3055
3126
  self.clearUnavailableTimer();
3056
3127
  self.setConnection(handshake.connection);
3057
3128
  self.socket_id = self.connection.id;
3129
+ self.timeline.info({ socket_id: self.socket_id });
3058
3130
  self.updateState("connected");
3059
3131
  }
3060
3132
  });
@@ -3180,187 +3252,331 @@
3180
3252
  }).call(this);
3181
3253
 
3182
3254
  ;(function() {
3183
- Pusher.Channels = function() {
3184
- this.channels = {};
3255
+ /** Represents a collection of members of a presence channel. */
3256
+ function Members() {
3257
+ this.reset();
3258
+ }
3259
+ var prototype = Members.prototype;
3260
+
3261
+ /** Returns member's info for given id.
3262
+ *
3263
+ * Resulting object containts two fields - id and info.
3264
+ *
3265
+ * @param {Number} id
3266
+ * @return {Object} member's info or null
3267
+ */
3268
+ prototype.get = function(id) {
3269
+ if (Object.prototype.hasOwnProperty.call(this.members, id)) {
3270
+ return {
3271
+ id: id,
3272
+ info: this.members[id]
3273
+ };
3274
+ } else {
3275
+ return null;
3276
+ }
3185
3277
  };
3186
3278
 
3187
- Pusher.Channels.prototype = {
3188
- add: function(channel_name, pusher) {
3189
- var existing_channel = this.find(channel_name);
3190
- if (!existing_channel) {
3191
- var channel = Pusher.Channel.factory(channel_name, pusher);
3192
- this.channels[channel_name] = channel;
3193
- return channel;
3194
- } else {
3195
- return existing_channel;
3196
- }
3197
- },
3279
+ /** Calls back for each member in unspecified order.
3280
+ *
3281
+ * @param {Function} callback
3282
+ */
3283
+ prototype.each = function(callback) {
3284
+ var self = this;
3285
+ Pusher.Util.objectApply(self.members, function(member, id) {
3286
+ callback(self.get(id));
3287
+ });
3288
+ };
3198
3289
 
3199
- find: function(channel_name) {
3200
- return this.channels[channel_name];
3201
- },
3290
+ /** Updates the id for connected member. For internal use only. */
3291
+ prototype.setMyID = function(id) {
3292
+ this.myID = id;
3293
+ };
3202
3294
 
3203
- remove: function(channel_name) {
3204
- delete this.channels[channel_name];
3205
- },
3295
+ /** Handles subscription data. For internal use only. */
3296
+ prototype.onSubscription = function(subscriptionData) {
3297
+ this.members = subscriptionData.presence.hash;
3298
+ this.count = subscriptionData.presence.count;
3299
+ this.me = this.get(this.myID);
3300
+ };
3206
3301
 
3207
- disconnect: function () {
3208
- for(var channel_name in this.channels){
3209
- this.channels[channel_name].disconnect()
3210
- }
3302
+ /** Adds a new member to the collection. For internal use only. */
3303
+ prototype.addMember = function(memberData) {
3304
+ if (this.get(memberData.user_id) === null) {
3305
+ this.count++;
3211
3306
  }
3307
+ this.members[memberData.user_id] = memberData.user_info;
3308
+ return this.get(memberData.user_id);
3212
3309
  };
3213
3310
 
3214
- Pusher.Channel = function(channel_name, pusher) {
3215
- var self = this;
3216
- Pusher.EventsDispatcher.call(this, function(event_name, event_data) {
3217
- Pusher.debug('No callbacks on ' + channel_name + ' for ' + event_name);
3218
- });
3219
-
3220
- this.pusher = pusher;
3221
- this.name = channel_name;
3222
- this.subscribed = false;
3311
+ /** Adds a member from the collection. For internal use only. */
3312
+ prototype.removeMember = function(memberData) {
3313
+ var member = this.get(memberData.user_id);
3314
+ if (member) {
3315
+ delete this.members[memberData.user_id];
3316
+ this.count--;
3317
+ }
3318
+ return member;
3319
+ };
3223
3320
 
3224
- this.bind('pusher_internal:subscription_succeeded', function(data) {
3225
- self.onSubscriptionSucceeded(data);
3226
- });
3321
+ /** Resets the collection to the initial state. For internal use only. */
3322
+ prototype.reset = function() {
3323
+ this.members = {};
3324
+ this.count = 0;
3325
+ this.myID = null;
3326
+ this.me = null;
3227
3327
  };
3228
3328
 
3229
- Pusher.Channel.prototype = {
3230
- // inheritable constructor
3231
- init: function() {},
3232
- disconnect: function() {
3233
- this.subscribed = false;
3234
- this.emit("pusher_internal:disconnected");
3235
- },
3329
+ Pusher.Members = Members;
3330
+ }).call(this);
3236
3331
 
3237
- onSubscriptionSucceeded: function(data) {
3238
- this.subscribed = true;
3239
- this.emit('pusher:subscription_succeeded');
3240
- },
3332
+ ;(function() {
3333
+ /** Provides base public channel interface with an event emitter.
3334
+ *
3335
+ * Emits:
3336
+ * - pusher:subscription_succeeded - after subscribing successfully
3337
+ * - other non-internal events
3338
+ *
3339
+ * @param {String} name
3340
+ * @param {Pusher} pusher
3341
+ */
3342
+ function Channel(name, pusher) {
3343
+ Pusher.EventsDispatcher.call(this, function(event, data) {
3344
+ Pusher.debug('No callbacks on ' + name + ' for ' + event);
3345
+ });
3241
3346
 
3242
- authorize: function(socketId, options, callback){
3243
- return callback(false, {}); // normal channels don't require auth
3244
- },
3347
+ this.name = name;
3348
+ this.pusher = pusher;
3349
+ this.subscribed = false;
3350
+ }
3351
+ var prototype = Channel.prototype;
3352
+ Pusher.Util.extend(prototype, Pusher.EventsDispatcher.prototype);
3245
3353
 
3246
- trigger: function(event, data) {
3247
- return this.pusher.send_event(event, data, this.name);
3248
- }
3354
+ /** Skips authorization, since public channels don't require it.
3355
+ *
3356
+ * @param {Function} callback
3357
+ */
3358
+ prototype.authorize = function(socketId, callback) {
3359
+ return callback(false, {});
3249
3360
  };
3250
3361
 
3251
- Pusher.Util.extend(Pusher.Channel.prototype, Pusher.EventsDispatcher.prototype);
3362
+ /** Triggers an event */
3363
+ prototype.trigger = function(event, data) {
3364
+ return this.pusher.send_event(event, data, this.name);
3365
+ };
3252
3366
 
3253
- Pusher.Channel.PrivateChannel = {
3254
- authorize: function(socketId, options, callback){
3255
- var self = this;
3256
- var authorizer = new Pusher.Channel.Authorizer(this, Pusher.channel_auth_transport, options);
3257
- return authorizer.authorize(socketId, function(err, authData) {
3258
- if(!err) {
3259
- self.emit('pusher_internal:authorized', authData);
3260
- }
3367
+ /** Signals disconnection to the channel. For internal use only. */
3368
+ prototype.disconnect = function() {
3369
+ this.subscribed = false;
3370
+ };
3261
3371
 
3262
- callback(err, authData);
3263
- });
3372
+ /** Handles an event. For internal use only.
3373
+ *
3374
+ * @param {String} event
3375
+ * @param {*} data
3376
+ */
3377
+ prototype.handleEvent = function(event, data) {
3378
+ if (event.indexOf("pusher_internal:") === 0) {
3379
+ if (event === "pusher_internal:subscription_succeeded") {
3380
+ this.subscribed = true;
3381
+ this.emit("pusher:subscription_succeeded", data);
3382
+ }
3383
+ } else {
3384
+ this.emit(event, data);
3264
3385
  }
3265
3386
  };
3266
3387
 
3267
- Pusher.Channel.PresenceChannel = {
3268
- init: function(){
3269
- this.members = new Members(this); // leeches off channel events
3270
- },
3388
+ /** Sends a subscription request. For internal use only. */
3389
+ prototype.subscribe = function() {
3390
+ var self = this;
3271
3391
 
3272
- onSubscriptionSucceeded: function(data) {
3273
- this.subscribed = true;
3274
- // We override this because we want the Members obj to be responsible for
3275
- // emitting the pusher:subscription_succeeded. It will do this after it has done its work.
3276
- }
3392
+ self.authorize(self.pusher.connection.socket_id, function(error, data) {
3393
+ if (error) {
3394
+ self.handleEvent('pusher:subscription_error', data);
3395
+ } else {
3396
+ self.pusher.send_event('pusher:subscribe', {
3397
+ auth: data.auth,
3398
+ channel_data: data.channel_data,
3399
+ channel: self.name
3400
+ });
3401
+ }
3402
+ });
3277
3403
  };
3278
3404
 
3279
- var Members = function(channel) {
3280
- var self = this;
3281
- var channelData = null;
3405
+ /** Sends an unsubscription request. For internal use only. */
3406
+ prototype.unsubscribe = function() {
3407
+ this.pusher.send_event('pusher:unsubscribe', {
3408
+ channel: this.name
3409
+ });
3410
+ };
3282
3411
 
3283
- var reset = function() {
3284
- self._members_map = {};
3285
- self.count = 0;
3286
- self.me = null;
3287
- channelData = null;
3288
- };
3289
- reset();
3412
+ Pusher.Channel = Channel;
3413
+ }).call(this);
3290
3414
 
3291
- var subscriptionSucceeded = function(subscriptionData) {
3292
- self._members_map = subscriptionData.presence.hash;
3293
- self.count = subscriptionData.presence.count;
3294
- self.me = self.get(channelData.user_id);
3295
- channel.emit('pusher:subscription_succeeded', self);
3296
- };
3415
+ ;(function() {
3416
+ /** Extends public channels to provide private channel interface.
3417
+ *
3418
+ * @param {String} name
3419
+ * @param {Pusher} pusher
3420
+ */
3421
+ function PrivateChannel(name, pusher) {
3422
+ Pusher.Channel.call(this, name, pusher);
3423
+ }
3424
+ var prototype = PrivateChannel.prototype;
3425
+ Pusher.Util.extend(prototype, Pusher.Channel.prototype);
3297
3426
 
3298
- channel.bind('pusher_internal:authorized', function(authorizedData) {
3299
- channelData = JSON.parse(authorizedData.channel_data);
3300
- channel.bind("pusher_internal:subscription_succeeded", subscriptionSucceeded);
3301
- });
3427
+ /** Authorizes the connection to use the channel.
3428
+ *
3429
+ * @param {String} socketId
3430
+ * @param {Function} callback
3431
+ */
3432
+ prototype.authorize = function(socketId, callback) {
3433
+ var authorizer = new Pusher.Channel.Authorizer(this, this.pusher.config);
3434
+ return authorizer.authorize(socketId, callback);
3435
+ };
3302
3436
 
3303
- channel.bind('pusher_internal:member_added', function(data) {
3304
- if(self.get(data.user_id) === null) { // only incr if user_id does not already exist
3305
- self.count++;
3306
- }
3437
+ Pusher.PrivateChannel = PrivateChannel;
3438
+ }).call(this);
3307
3439
 
3308
- self._members_map[data.user_id] = data.user_info;
3309
- channel.emit('pusher:member_added', self.get(data.user_id));
3310
- });
3440
+ ;(function() {
3441
+ /** Adds presence channel functionality to private channels.
3442
+ *
3443
+ * @param {String} name
3444
+ * @param {Pusher} pusher
3445
+ */
3446
+ function PresenceChannel(name, pusher) {
3447
+ Pusher.PrivateChannel.call(this, name, pusher);
3448
+ this.members = new Pusher.Members();
3449
+ }
3450
+ var prototype = PresenceChannel.prototype;
3451
+ Pusher.Util.extend(prototype, Pusher.PrivateChannel.prototype);
3311
3452
 
3312
- channel.bind('pusher_internal:member_removed', function(data) {
3313
- var member = self.get(data.user_id);
3314
- if(member) {
3315
- delete self._members_map[data.user_id];
3316
- self.count--;
3317
- channel.emit('pusher:member_removed', member);
3453
+ /** Authenticates the connection as a member of the channel.
3454
+ *
3455
+ * @param {String} socketId
3456
+ * @param {Function} callback
3457
+ */
3458
+ prototype.authorize = function(socketId, callback) {
3459
+ var _super = Pusher.PrivateChannel.prototype.authorize;
3460
+ var self = this;
3461
+ _super.call(self, socketId, function(error, authData) {
3462
+ if (!error) {
3463
+ if (authData.channel_data === undefined) {
3464
+ Pusher.warn(
3465
+ "Invalid auth response for channel '" +
3466
+ self.name +
3467
+ "', expected 'channel_data' field"
3468
+ );
3469
+ callback("Invalid auth response");
3470
+ return;
3471
+ }
3472
+ var channelData = JSON.parse(authData.channel_data);
3473
+ self.members.setMyID(channelData.user_id);
3318
3474
  }
3475
+ callback(error, authData);
3319
3476
  });
3477
+ };
3320
3478
 
3321
- channel.bind('pusher_internal:disconnected', function() {
3322
- reset();
3323
- channel.unbind("pusher_internal:subscription_succeeded", subscriptionSucceeded);
3324
- });
3479
+ /** Handles presence and subscription events. For internal use only.
3480
+ *
3481
+ * @param {String} event
3482
+ * @param {*} data
3483
+ */
3484
+ prototype.handleEvent = function(event, data) {
3485
+ switch (event) {
3486
+ case "pusher_internal:subscription_succeeded":
3487
+ this.members.onSubscription(data);
3488
+ this.subscribed = true;
3489
+ this.emit("pusher:subscription_succeeded", this.members);
3490
+ break;
3491
+ case "pusher_internal:member_added":
3492
+ var addedMember = this.members.addMember(data);
3493
+ this.emit('pusher:member_added', addedMember);
3494
+ break;
3495
+ case "pusher_internal:member_removed":
3496
+ var removedMember = this.members.removeMember(data);
3497
+ if (removedMember) {
3498
+ this.emit('pusher:member_removed', removedMember);
3499
+ }
3500
+ break;
3501
+ default:
3502
+ Pusher.PrivateChannel.prototype.handleEvent.call(this, event, data);
3503
+ }
3325
3504
  };
3326
3505
 
3327
- Members.prototype = {
3328
- each: function(callback) {
3329
- for(var i in this._members_map) {
3330
- callback(this.get(i));
3331
- }
3332
- },
3506
+ /** Resets the channel state, including members map. For internal use only. */
3507
+ prototype.disconnect = function() {
3508
+ this.members.reset();
3509
+ Pusher.PrivateChannel.prototype.disconnect.call(this);
3510
+ };
3333
3511
 
3334
- get: function(user_id) {
3335
- if (this._members_map.hasOwnProperty(user_id)) { // have heard of this user user_id
3336
- return {
3337
- id: user_id,
3338
- info: this._members_map[user_id]
3339
- }
3340
- } else { // have never heard of this user
3341
- return null;
3342
- }
3512
+ Pusher.PresenceChannel = PresenceChannel;
3513
+ }).call(this);
3514
+
3515
+ ;(function() {
3516
+ /** Handles a channel map. */
3517
+ function Channels() {
3518
+ this.channels = {};
3519
+ }
3520
+ var prototype = Channels.prototype;
3521
+
3522
+ /** Creates or retrieves an existing channel by its name.
3523
+ *
3524
+ * @param {String} name
3525
+ * @param {Pusher} pusher
3526
+ * @return {Channel}
3527
+ */
3528
+ prototype.add = function(name, pusher) {
3529
+ if (!this.channels[name]) {
3530
+ this.channels[name] = createChannel(name, pusher);
3343
3531
  }
3532
+ return this.channels[name];
3344
3533
  };
3345
3534
 
3346
- Pusher.Channel.factory = function(channel_name, pusher){
3347
- var channel = new Pusher.Channel(channel_name, pusher);
3348
- if (channel_name.indexOf('private-') === 0) {
3349
- Pusher.Util.extend(channel, Pusher.Channel.PrivateChannel);
3350
- } else if (channel_name.indexOf('presence-') === 0) {
3351
- Pusher.Util.extend(channel, Pusher.Channel.PrivateChannel);
3352
- Pusher.Util.extend(channel, Pusher.Channel.PresenceChannel);
3353
- };
3354
- channel.init();
3535
+ /** Finds a channel by its name.
3536
+ *
3537
+ * @param {String} name
3538
+ * @return {Channel} channel or null if it doesn't exist
3539
+ */
3540
+ prototype.find = function(name) {
3541
+ return this.channels[name];
3542
+ };
3543
+
3544
+ /** Removes a channel from the map.
3545
+ *
3546
+ * @param {String} name
3547
+ */
3548
+ prototype.remove = function(name) {
3549
+ var channel = this.channels[name];
3550
+ delete this.channels[name];
3355
3551
  return channel;
3356
3552
  };
3553
+
3554
+ /** Proxies disconnection signal to all channels. */
3555
+ prototype.disconnect = function() {
3556
+ Pusher.Util.objectApply(this.channels, function(channel) {
3557
+ channel.disconnect();
3558
+ });
3559
+ };
3560
+
3561
+ function createChannel(name, pusher) {
3562
+ if (name.indexOf('private-') === 0) {
3563
+ return new Pusher.PrivateChannel(name, pusher);
3564
+ } else if (name.indexOf('presence-') === 0) {
3565
+ return new Pusher.PresenceChannel(name, pusher);
3566
+ } else {
3567
+ return new Pusher.Channel(name, pusher);
3568
+ }
3569
+ }
3570
+
3571
+ Pusher.Channels = Channels;
3357
3572
  }).call(this);
3358
3573
 
3359
3574
  ;(function() {
3360
- Pusher.Channel.Authorizer = function(channel, type, options) {
3575
+ Pusher.Channel.Authorizer = function(channel, options) {
3361
3576
  this.channel = channel;
3362
- this.type = type;
3577
+ this.type = options.authTransport;
3363
3578
 
3579
+ this.options = options;
3364
3580
  this.authOptions = (options || {}).auth || {};
3365
3581
  };
3366
3582
 
@@ -3394,7 +3610,7 @@
3394
3610
  xhr = (window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"));
3395
3611
  }
3396
3612
 
3397
- xhr.open("POST", Pusher.channel_auth_endpoint, true);
3613
+ xhr.open("POST", self.options.authEndpoint, true);
3398
3614
 
3399
3615
  // add request headers
3400
3616
  xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
@@ -3436,6 +3652,7 @@
3436
3652
  var callbackName = nextAuthCallbackID.toString();
3437
3653
  nextAuthCallbackID++;
3438
3654
 
3655
+ var document = Pusher.Util.getDocument();
3439
3656
  var script = document.createElement("script");
3440
3657
  // Hacked wrapper.
3441
3658
  Pusher.auth_callbacks[callbackName] = function(data) {
@@ -3443,7 +3660,7 @@
3443
3660
  };
3444
3661
 
3445
3662
  var callback_name = "Pusher.auth_callbacks['" + callbackName + "']";
3446
- script.src = Pusher.channel_auth_endpoint +
3663
+ script.src = this.options.authEndpoint +
3447
3664
  '?callback=' +
3448
3665
  encodeURIComponent(callback_name) +
3449
3666
  this.composeQuery(socketId);