pusher-fake 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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);