pusher_rails 0.2.0 → 0.2.2

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.
@@ -1,3 +1,12 @@
1
+ v0.2.2
2
+ ======
3
+ - Upgraded to pusher.js 1.12.0
4
+ - Upgraded to backpusher.js 0.0.2
5
+
6
+ v0.2.1
7
+ ======
8
+ - Upgraded to pusher-gem 0.9.2
9
+
1
10
  v0.2.0
2
11
  ======
3
12
  - Upgraded to pusher-gem 0.9.1
data/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
  =====================
3
3
 
4
4
  Adds:
5
- - [pusher-gem v0.9.1](https://github.com/pusher/pusher-gem/tree/v0.9.1)
6
- - [pusher.js v1.11.0](https://github.com/pusher/pusher-js/tree/v1.11.0)
7
- - [backpusher.js](https://github.com/pusher/backpusher/commit/e61c9d7a761fcb48f312416408d1bf4ed418735b#diff-1)
5
+ - [pusher-gem v0.9.2](https://github.com/pusher/pusher-gem/tree/v0.9.2)
6
+ - [pusher.js v1.12.0](https://github.com/pusher/pusher-js/tree/v1.12.0)
7
+ - [backpusher.js](https://github.com/pusher/backpusher)
8
8
 
9
9
  This pulls in the *pusher-gem* as well as adding *pusher.js* and *backpusher.js* to the assets pipeline of your Rails 3.1+ app.
10
10
 
@@ -21,16 +21,16 @@ Licenses
21
21
  ========
22
22
 
23
23
  /*!
24
- * Pusher JavaScript Library v1.11.0
24
+ * Pusher JavaScript Library v1.12.0
25
25
  * http://pusherapp.com/
26
26
  *
27
27
  * Copyright 2011, Pusher
28
28
  * Released under the MIT licence.
29
29
  */
30
30
 
31
- // Backpusher.js 0.0.1
32
- // (c) 2011 Pusher.
33
- // Backpusher may be freely distributed under the MIT license.
34
- // For all details and documentation:
35
- // http://github.com/pusher/backpusher
31
+ // Backpusher.js 0.0.2
32
+ // (c) 2011-2012 Pusher.
33
+ // Backpusher may be freely distributed under the MIT license.
34
+ // For all details and documentation:
35
+ // http://github.com/pusher/backpusher
36
36
 
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = 'pusher_rails'
6
- s.version = '0.2.0'
6
+ s.version = '0.2.2'
7
7
  s.platform = Gem::Platform::RUBY
8
8
  s.authors = ["David Grandinetti"]
9
9
  s.email = ["dave@wegoto12.com"]
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
11
11
  s.description = 'Adds pusher.js/backpusher.js to the asset pipeline and pusher-gem to to your app.'
12
12
  s.homepage = 'https://github.com/dbgrandi/pusher_rails'
13
13
 
14
- s.add_dependency "pusher", "~> 0.9.1"
14
+ s.add_dependency "pusher", "~> 0.9.2"
15
15
 
16
16
  s.files = `git ls-files`.split("\n")
17
17
  s.require_paths = ["lib"]
@@ -1,5 +1,5 @@
1
- // Backpusher.js 0.0.1
2
- // (c) 2011 Pusher.
1
+ // Backpusher.js 0.0.2
2
+ // (c) 2011-2012 Pusher.
3
3
  // Backpusher may be freely distributed under the MIT license.
4
4
  // For all details and documentation:
5
5
  // http://github.com/pusher/backpusher
@@ -92,65 +92,17 @@
92
92
  }
93
93
  };
94
94
 
95
- // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
96
- var methodMap = {
97
- 'create': 'POST',
98
- 'update': 'PUT',
99
- 'delete': 'DELETE',
100
- 'read' : 'GET'
101
- };
102
-
95
+ // Add socket ID to every Backbone.sync request
96
+ var origBackboneSync = Backbone.sync;
103
97
  Backbone.sync = function(method, model, options) {
104
- var type = methodMap[method];
105
-
106
- // Default JSON-request options.
107
- var params = _.extend({
108
- type: type,
109
- dataType: 'json'
110
- }, options);
111
-
112
- if (!(model && model.url)) {
113
- throw new Error("A 'url' property or function must be specified");
114
- }
115
-
116
- if (!params.url) {
117
- params.url = _.isFunction(model.url) ? model.url() : model.url;
118
- params.url += '?socket_id=' + Backbone.pusher_socket_id;
119
- }
120
-
121
- // Ensure that we have the appropriate request data.
122
- if (!params.data && model && (method == 'create' || method == 'update')) {
123
- params.contentType = 'application/json';
124
- params.data = JSON.stringify(model.toJSON());
125
- }
126
-
127
- // For older servers, emulate JSON by encoding the request into an HTML-form.
128
- if (Backbone.emulateJSON) {
129
- params.contentType = 'application/x-www-form-urlencoded';
130
- params.data = params.data ? {model : params.data} : {};
131
- }
132
-
133
- // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
134
- // And an `X-HTTP-Method-Override` header.
135
- if (Backbone.emulateHTTP) {
136
- if (type === 'PUT' || type === 'DELETE') {
137
- if (Backbone.emulateJSON) params.data._method = type;
138
- params.type = 'POST';
139
- params.beforeSend = function(xhr) {
140
- xhr.setRequestHeader('X-HTTP-Method-Override', type);
141
- };
142
- }
143
- }
144
-
145
- // Don't process data on a non-GET request.
146
- if (params.type !== 'GET' && !Backbone.emulateJSON) {
147
- params.processData = false;
148
- }
98
+ options.headers = _.extend(
99
+ { 'X-Pusher-Socket-ID': Backbone.pusher_socket_id },
100
+ options.headers
101
+ );
149
102
 
150
- // Make the request.
151
- return $.ajax(params);
103
+ return origBackboneSync(method, model, options);
152
104
  };
153
105
 
154
106
  // Export:
155
107
  exports.Backpusher = Backpusher;
156
- })((typeof exports !== 'undefined' ? exports : this));
108
+ })((typeof exports !== 'undefined' ? exports : this));
@@ -1,206 +1,211 @@
1
1
  /*!
2
- * Pusher JavaScript Library v1.11.0
2
+ * Pusher JavaScript Library v1.12.0
3
3
  * http://pusherapp.com/
4
4
  *
5
5
  * Copyright 2011, Pusher
6
6
  * Released under the MIT licence.
7
7
  */
8
8
 
9
- if (Function.prototype.scopedTo === undefined) {
10
- Function.prototype.scopedTo = function(context, args) {
11
- var f = this;
12
- return function() {
13
- return f.apply(context, Array.prototype.slice.call(args || [])
14
- .concat(Array.prototype.slice.call(arguments)));
9
+ ;(function() {
10
+ if (Function.prototype.scopedTo === undefined) {
11
+ Function.prototype.scopedTo = function(context, args) {
12
+ var f = this;
13
+ return function() {
14
+ return f.apply(context, Array.prototype.slice.call(args || [])
15
+ .concat(Array.prototype.slice.call(arguments)));
16
+ };
15
17
  };
16
- };
17
- }
18
-
19
- var Pusher = function(app_key, options) {
20
- this.options = options || {};
21
- this.key = app_key;
22
- this.channels = new Pusher.Channels();
23
- this.global_emitter = new Pusher.EventsDispatcher()
24
-
25
- var self = this;
26
-
27
- this.checkAppKey();
28
-
29
- this.connection = new Pusher.Connection(this.key, this.options);
30
-
31
- // Setup / teardown connection
32
- this.connection
33
- .bind('connected', function() {
34
- self.subscribeAll();
35
- })
36
- .bind('message', function(params) {
37
- var internal = (params.event.indexOf('pusher_internal:') === 0);
38
- if (params.channel) {
39
- var channel;
40
- if (channel = self.channel(params.channel)) {
41
- channel.emit(params.event, params.data);
18
+ }
19
+
20
+ var Pusher = function(app_key, options) {
21
+ this.options = options || {};
22
+ this.key = app_key;
23
+ this.channels = new Pusher.Channels();
24
+ this.global_emitter = new Pusher.EventsDispatcher()
25
+
26
+ var self = this;
27
+
28
+ this.checkAppKey();
29
+
30
+ this.connection = new Pusher.Connection(this.key, this.options);
31
+
32
+ // Setup / teardown connection
33
+ this.connection
34
+ .bind('connected', function() {
35
+ self.subscribeAll();
36
+ })
37
+ .bind('message', function(params) {
38
+ var internal = (params.event.indexOf('pusher_internal:') === 0);
39
+ if (params.channel) {
40
+ var channel;
41
+ if (channel = self.channel(params.channel)) {
42
+ channel.emit(params.event, params.data);
43
+ }
42
44
  }
43
- }
44
- // Emit globaly [deprecated]
45
- if (!internal) self.global_emitter.emit(params.event, params.data);
46
- })
47
- .bind('disconnected', function() {
48
- self.channels.disconnect();
49
- })
50
- .bind('error', function(err) {
51
- Pusher.warn('Error', err);
52
- });
45
+ // Emit globaly [deprecated]
46
+ if (!internal) self.global_emitter.emit(params.event, params.data);
47
+ })
48
+ .bind('disconnected', function() {
49
+ self.channels.disconnect();
50
+ })
51
+ .bind('error', function(err) {
52
+ Pusher.warn('Error', err);
53
+ });
53
54
 
54
- Pusher.instances.push(this);
55
+ Pusher.instances.push(this);
55
56
 
56
- if (Pusher.isReady) self.connect();
57
- };
58
- Pusher.instances = [];
59
- Pusher.prototype = {
60
- channel: function(name) {
61
- return this.channels.find(name);
62
- },
57
+ if (Pusher.isReady) self.connect();
58
+ };
59
+ Pusher.instances = [];
60
+ Pusher.prototype = {
61
+ channel: function(name) {
62
+ return this.channels.find(name);
63
+ },
63
64
 
64
- connect: function() {
65
- this.connection.connect();
66
- },
65
+ connect: function() {
66
+ this.connection.connect();
67
+ },
67
68
 
68
- disconnect: function() {
69
- this.connection.disconnect();
70
- },
69
+ disconnect: function() {
70
+ this.connection.disconnect();
71
+ },
71
72
 
72
- bind: function(event_name, callback) {
73
- this.global_emitter.bind(event_name, callback);
74
- return this;
75
- },
73
+ bind: function(event_name, callback) {
74
+ this.global_emitter.bind(event_name, callback);
75
+ return this;
76
+ },
76
77
 
77
- bind_all: function(callback) {
78
- this.global_emitter.bind_all(callback);
79
- return this;
80
- },
78
+ bind_all: function(callback) {
79
+ this.global_emitter.bind_all(callback);
80
+ return this;
81
+ },
81
82
 
82
- subscribeAll: function() {
83
- var channel;
84
- for (channel in this.channels.channels) {
85
- if (this.channels.channels.hasOwnProperty(channel)) {
86
- this.subscribe(channel);
83
+ subscribeAll: function() {
84
+ var channel;
85
+ for (channelName in this.channels.channels) {
86
+ if (this.channels.channels.hasOwnProperty(channelName)) {
87
+ this.subscribe(channelName);
88
+ }
87
89
  }
88
- }
89
- },
90
+ },
90
91
 
91
- subscribe: function(channel_name) {
92
- var self = this;
93
- var channel = this.channels.add(channel_name, this);
94
- if (this.connection.state === 'connected') {
95
- channel.authorize(this, function(err, data) {
96
- if (err) {
97
- channel.emit('pusher:subscription_error', data);
98
- } else {
99
- self.send_event('pusher:subscribe', {
100
- channel: channel_name,
101
- auth: data.auth,
102
- channel_data: data.channel_data
103
- });
104
- }
105
- });
106
- }
107
- return channel;
108
- },
92
+ subscribe: function(channel_name) {
93
+ var self = this;
94
+ var channel = this.channels.add(channel_name, this);
95
+
96
+ if (this.connection.state === 'connected') {
97
+ channel.authorize(this.connection.socket_id, this.options, function(err, data) {
98
+ if (err) {
99
+ channel.emit('pusher:subscription_error', data);
100
+ } else {
101
+ self.send_event('pusher:subscribe', {
102
+ channel: channel_name,
103
+ auth: data.auth,
104
+ channel_data: data.channel_data
105
+ });
106
+ }
107
+ });
108
+ }
109
+ return channel;
110
+ },
109
111
 
110
- unsubscribe: function(channel_name) {
111
- this.channels.remove(channel_name);
112
- if (this.connection.state === 'connected') {
113
- this.send_event('pusher:unsubscribe', {
114
- channel: channel_name
115
- });
116
- }
117
- },
112
+ unsubscribe: function(channel_name) {
113
+ this.channels.remove(channel_name);
114
+ if (this.connection.state === 'connected') {
115
+ this.send_event('pusher:unsubscribe', {
116
+ channel: channel_name
117
+ });
118
+ }
119
+ },
118
120
 
119
- send_event: function(event_name, data, channel) {
120
- return this.connection.send_event(event_name, data, channel);
121
- },
121
+ send_event: function(event_name, data, channel) {
122
+ return this.connection.send_event(event_name, data, channel);
123
+ },
122
124
 
123
- checkAppKey: function() {
124
- if(this.key === null || this.key === undefined) {
125
- Pusher.warn('Warning', 'You must pass your app key when you instantiate Pusher.');
126
- }
127
- }
128
- };
129
-
130
- Pusher.Util = {
131
- extend: function extend(target, extensions) {
132
- for (var property in extensions) {
133
- if (extensions[property] && extensions[property].constructor &&
134
- extensions[property].constructor === Object) {
135
- target[property] = extend(target[property] || {}, extensions[property]);
136
- } else {
137
- target[property] = extensions[property];
125
+ checkAppKey: function() {
126
+ if(this.key === null || this.key === undefined) {
127
+ Pusher.warn('Warning', 'You must pass your app key when you instantiate Pusher.');
138
128
  }
139
129
  }
140
- return target;
141
- },
142
-
143
- stringify: function stringify() {
144
- var m = ["Pusher"]
145
- for (var i = 0; i < arguments.length; i++){
146
- if (typeof arguments[i] === "string") {
147
- m.push(arguments[i])
148
- } else {
149
- if (window['JSON'] == undefined) {
150
- m.push(arguments[i].toString());
130
+ };
131
+
132
+ Pusher.Util = {
133
+ extend: function extend(target, extensions) {
134
+ for (var property in extensions) {
135
+ if (extensions[property] && extensions[property].constructor &&
136
+ extensions[property].constructor === Object) {
137
+ target[property] = extend(target[property] || {}, extensions[property]);
151
138
  } else {
152
- m.push(JSON.stringify(arguments[i]))
139
+ target[property] = extensions[property];
153
140
  }
154
141
  }
155
- };
156
- return m.join(" : ")
157
- },
158
-
159
- arrayIndexOf: function(array, item) { // MSIE doesn't have array.indexOf
160
- var nativeIndexOf = Array.prototype.indexOf;
161
- if (array == null) return -1;
162
- if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
163
- for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
164
- return -1;
165
- }
166
- };
167
-
168
- // To receive log output provide a Pusher.log function, for example
169
- // Pusher.log = function(m){console.log(m)}
170
- Pusher.debug = function() {
171
- if (!Pusher.log) return
172
- Pusher.log(Pusher.Util.stringify.apply(this, arguments))
173
- }
174
- Pusher.warn = function() {
175
- if (window.console && window.console.warn) {
176
- window.console.warn(Pusher.Util.stringify.apply(this, arguments));
177
- } else {
142
+ return target;
143
+ },
144
+
145
+ stringify: function stringify() {
146
+ var m = ["Pusher"]
147
+ for (var i = 0; i < arguments.length; i++){
148
+ if (typeof arguments[i] === "string") {
149
+ m.push(arguments[i])
150
+ } else {
151
+ if (window['JSON'] == undefined) {
152
+ m.push(arguments[i].toString());
153
+ } else {
154
+ m.push(JSON.stringify(arguments[i]))
155
+ }
156
+ }
157
+ };
158
+ return m.join(" : ")
159
+ },
160
+
161
+ arrayIndexOf: function(array, item) { // MSIE doesn't have array.indexOf
162
+ var nativeIndexOf = Array.prototype.indexOf;
163
+ if (array == null) return -1;
164
+ if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
165
+ for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
166
+ return -1;
167
+ }
168
+ };
169
+
170
+ // To receive log output provide a Pusher.log function, for example
171
+ // Pusher.log = function(m){console.log(m)}
172
+ Pusher.debug = function() {
178
173
  if (!Pusher.log) return
179
- Pusher.log(Pusher.Util.stringify.apply(this, arguments));
174
+ Pusher.log(Pusher.Util.stringify.apply(this, arguments))
180
175
  }
181
- };
182
-
183
- // Pusher defaults
184
- Pusher.VERSION = '1.11.0';
185
-
186
- Pusher.host = 'ws.pusherapp.com';
187
- Pusher.ws_port = 80;
188
- Pusher.wss_port = 443;
189
- Pusher.channel_auth_endpoint = '/pusher/auth';
190
- Pusher.cdn_http = 'http://js.pusher.com/'
191
- Pusher.cdn_https = 'https://d3dy5gmtp8yhk7.cloudfront.net/'
192
- Pusher.dependency_suffix = '';
193
- Pusher.channel_auth_transport = 'ajax';
194
- Pusher.activity_timeout = 120000;
195
- Pusher.pong_timeout = 30000;
196
-
197
- Pusher.isReady = false;
198
- Pusher.ready = function() {
199
- Pusher.isReady = true;
200
- for (var i = 0, l = Pusher.instances.length; i < l; i++) {
201
- Pusher.instances[i].connect();
202
- }
203
- };
176
+ Pusher.warn = function() {
177
+ if (window.console && window.console.warn) {
178
+ window.console.warn(Pusher.Util.stringify.apply(this, arguments));
179
+ } else {
180
+ if (!Pusher.log) return
181
+ Pusher.log(Pusher.Util.stringify.apply(this, arguments));
182
+ }
183
+ };
184
+
185
+ // Pusher defaults
186
+ Pusher.VERSION = '1.12.0';
187
+
188
+ Pusher.host = 'ws.pusherapp.com';
189
+ Pusher.ws_port = 80;
190
+ Pusher.wss_port = 443;
191
+ Pusher.channel_auth_endpoint = '/pusher/auth';
192
+ Pusher.cdn_http = 'http://js.pusher.com/'
193
+ Pusher.cdn_https = 'https://d3dy5gmtp8yhk7.cloudfront.net/'
194
+ Pusher.dependency_suffix = '';
195
+ Pusher.channel_auth_transport = 'ajax';
196
+ Pusher.activity_timeout = 120000;
197
+ Pusher.pong_timeout = 30000;
198
+
199
+ Pusher.isReady = false;
200
+ Pusher.ready = function() {
201
+ Pusher.isReady = true;
202
+ for (var i = 0, l = Pusher.instances.length; i < l; i++) {
203
+ Pusher.instances[i].connect();
204
+ }
205
+ };
206
+
207
+ this.Pusher = Pusher;
208
+ }).call(this);
204
209
 
205
210
  ;(function() {
206
211
  /* Abstract event binding
@@ -230,7 +235,7 @@ Example:
230
235
  this.callbacks[event_name].push(callback);
231
236
  return this;// chainable
232
237
  };
233
-
238
+
234
239
  EventsDispatcher.prototype.unbind = function(eventName, callback) {
235
240
  if(this.callbacks[eventName]) {
236
241
  var index = Pusher.Util.arrayIndexOf(this.callbacks[eventName], callback);
@@ -307,6 +312,11 @@ Example:
307
312
  var stateCallbacks = this.stateActions;
308
313
 
309
314
  if (prevState && (Pusher.Util.arrayIndexOf(this.transitions[prevState], nextState) == -1)) {
315
+ this.emit('invalid_transition_attempt', {
316
+ oldState: prevState,
317
+ newState: nextState
318
+ });
319
+
310
320
  throw new Error('Invalid transition [' + prevState + ' to ' + nextState + ']');
311
321
  }
312
322
 
@@ -350,6 +360,7 @@ Example:
350
360
  A little bauble to interface with window.navigator.onLine,
351
361
  window.ononline and window.onoffline. Easier to mock.
352
362
  */
363
+
353
364
  var NetInfo = function() {
354
365
  var self = this;
355
366
  Pusher.EventsDispatcher.call(this);
@@ -376,7 +387,7 @@ Example:
376
387
  };
377
388
 
378
389
  Pusher.Util.extend(NetInfo.prototype, Pusher.EventsDispatcher.prototype);
379
-
390
+
380
391
  this.Pusher.NetInfo = NetInfo;
381
392
  }).call(this);
382
393
 
@@ -431,7 +442,7 @@ Example:
431
442
  this.netInfo.bind('online', function(){
432
443
  if (self._machine.is('waiting')) {
433
444
  self._machine.transition('connecting');
434
- triggerStateChange('connecting');
445
+ updateState('connecting');
435
446
  }
436
447
  });
437
448
 
@@ -452,8 +463,6 @@ Example:
452
463
 
453
464
  // define the state machine that runs the connection
454
465
  this._machine = new Pusher.Machine('initialized', machineTransitions, {
455
-
456
- // TODO: Use the constructor for this.
457
466
  initializedPre: function() {
458
467
  self.compulsorySecure = self.options.encrypted;
459
468
 
@@ -469,16 +478,18 @@ Example:
469
478
  self.emit('connecting_in', self.connectionWait);
470
479
  }
471
480
 
472
- if (self.netInfo.isOnLine() === false || self.connectionAttempts > 4){
473
- triggerStateChange('unavailable');
481
+ if (self.netInfo.isOnLine() && self.connectionAttempts <= 4) {
482
+ updateState('connecting');
474
483
  } else {
475
- triggerStateChange('connecting');
484
+ updateState('unavailable');
476
485
  }
477
486
 
478
- if (self.netInfo.isOnLine() === true) {
487
+ // When in the unavailable state we attempt to connect, but don't
488
+ // broadcast that fact
489
+ if (self.netInfo.isOnLine()) {
479
490
  self._waitingTimer = setTimeout(function() {
480
491
  self._machine.transition('connecting');
481
- }, self.connectionWait);
492
+ }, connectionDelay());
482
493
  }
483
494
  },
484
495
 
@@ -491,7 +502,7 @@ Example:
491
502
  // state even when offline.
492
503
  if (self.netInfo.isOnLine() === false) {
493
504
  self._machine.transition('waiting');
494
- triggerStateChange('unavailable');
505
+ updateState('unavailable');
495
506
 
496
507
  return;
497
508
  }
@@ -513,6 +524,7 @@ Example:
513
524
 
514
525
  connectingExit: function() {
515
526
  clearTimeout(self._connectingTimer);
527
+ self.socket.onopen = undefined; // unbind to avoid open events that are no longer relevant
516
528
  },
517
529
 
518
530
  connectingToWaiting: function() {
@@ -538,6 +550,7 @@ Example:
538
550
 
539
551
  openExit: function() {
540
552
  clearTimeout(self._openTimer);
553
+ self.socket.onmessage = undefined; // unbind to avoid messages that are no longer relevant
541
554
  },
542
555
 
543
556
  openToWaiting: function() {
@@ -556,17 +569,18 @@ Example:
556
569
  self.socket.onclose = transitionToWaiting;
557
570
 
558
571
  resetConnectionParameters(self);
572
+ self.connectedAt = new Date().getTime();
559
573
 
560
574
  resetActivityCheck();
561
575
  },
562
576
 
563
577
  connectedPost: function() {
564
- triggerStateChange('connected');
578
+ updateState('connected');
565
579
  },
566
580
 
567
581
  connectedExit: function() {
568
582
  stopActivityCheck();
569
- triggerStateChange('disconnected');
583
+ updateState('disconnected');
570
584
  },
571
585
 
572
586
  impermanentlyClosingPost: function() {
@@ -591,8 +605,12 @@ Example:
591
605
  },
592
606
 
593
607
  failedPre: function() {
594
- triggerStateChange('failed');
608
+ updateState('failed');
595
609
  Pusher.debug('WebSockets are not available in this browser.');
610
+ },
611
+
612
+ permanentlyClosedPost: function() {
613
+ updateState('disconnected');
596
614
  }
597
615
  });
598
616
 
@@ -633,7 +651,11 @@ Example:
633
651
  protocol = 'wss://';
634
652
  }
635
653
 
636
- return protocol + Pusher.host + ':' + port + '/app/' + key + '?client=js&version=' + Pusher.VERSION;
654
+ var flash = (Pusher.TransportType === "flash") ? "true" : "false";
655
+
656
+ return protocol + Pusher.host + ':' + port + '/app/' + key + '?protocol=5&client=js'
657
+ + '&version=' + Pusher.VERSION
658
+ + '&flash=' + flash;
637
659
  }
638
660
 
639
661
  // callback for close and retry. Used on timeouts.
@@ -657,6 +679,25 @@ Example:
657
679
  if (self._activityTimer) { clearTimeout(self._activityTimer); }
658
680
  }
659
681
 
682
+ // Returns the delay before the next connection attempt should be made
683
+ //
684
+ // This function guards against attempting to connect more frequently than
685
+ // once every second
686
+ //
687
+ function connectionDelay() {
688
+ var delay = self.connectionWait;
689
+ if (delay === 0) {
690
+ if (self.connectedAt) {
691
+ var t = 1000;
692
+ var connectedFor = new Date().getTime() - self.connectedAt;
693
+ if (connectedFor < t) {
694
+ delay = t - connectedFor;
695
+ }
696
+ }
697
+ }
698
+ return delay;
699
+ }
700
+
660
701
  /*-----------------------------------------------
661
702
  WebSocket Callbacks
662
703
  -----------------------------------------------*/
@@ -666,28 +707,40 @@ Example:
666
707
  self._machine.transition('open');
667
708
  };
668
709
 
710
+ function handleCloseCode(code, message) {
711
+ // first inform the end-developer of this error
712
+ self.emit('error', {type: 'PusherError', data: {code: code, message: message}});
713
+
714
+ if (code === 4000) {
715
+ // SSL only app
716
+ self.compulsorySecure = true;
717
+ self.connectionSecure = true;
718
+ self.options.encrypted = true;
719
+
720
+ self._machine.transition('impermanentlyClosing')
721
+ } else if (code < 4100) {
722
+ // Permentently close connection
723
+ self._machine.transition('permanentlyClosing')
724
+ } else if (code < 4200) {
725
+ // Backoff before reconnecting
726
+ self.connectionWait = 1000;
727
+ self._machine.transition('waiting')
728
+ } else if (code < 4300) {
729
+ // Reconnect immediately
730
+ self._machine.transition('impermanentlyClosing')
731
+ } else {
732
+ // Unknown error
733
+ self._machine.transition('permanentlyClosing')
734
+ }
735
+ }
736
+
669
737
  function ws_onMessageOpen(event) {
670
738
  var params = parseWebSocketEvent(event);
671
739
  if (params !== undefined) {
672
740
  if (params.event === 'pusher:connection_established') {
673
741
  self._machine.transition('connected', params.data.socket_id);
674
742
  } else if (params.event === 'pusher:error') {
675
- // first inform the end-developer of this error
676
- self.emit('error', {type: 'PusherError', data: params.data});
677
-
678
- switch (params.data.code) {
679
- case 4000:
680
- Pusher.warn(params.data.message);
681
-
682
- self.compulsorySecure = true;
683
- self.connectionSecure = true;
684
- self.options.encrypted = true;
685
- break;
686
- case 4001:
687
- // App not found by key - close connection
688
- self._machine.transition('permanentlyClosing');
689
- break;
690
- }
743
+ handleCloseCode(params.data.code, params.data.message)
691
744
  }
692
745
  }
693
746
  }
@@ -753,19 +806,21 @@ Example:
753
806
  self._machine.transition('impermanentlyClosing');
754
807
  }
755
808
 
756
- function triggerStateChange(newState, data) {
757
- // avoid emitting and changing the state
758
- // multiple times when it's the same.
759
- if (self.state === newState) return;
760
-
809
+ // Updates the public state information exposed by connection
810
+ //
811
+ // This is distinct from the internal state information used by _machine
812
+ // to manage the connection
813
+ //
814
+ function updateState(newState, data) {
761
815
  var prevState = self.state;
762
-
763
816
  self.state = newState;
764
817
 
765
- Pusher.debug('State changed', prevState + ' -> ' + newState);
766
-
767
- self.emit('state_change', {previous: prevState, current: newState});
768
- self.emit(newState, data);
818
+ // Only emit when the state changes
819
+ if (prevState !== newState) {
820
+ Pusher.debug('State changed', prevState + ' -> ' + newState);
821
+ self.emit('state_change', {previous: prevState, current: newState});
822
+ self.emit(newState, data);
823
+ }
769
824
  }
770
825
  };
771
826
 
@@ -785,14 +840,24 @@ Example:
785
840
  }
786
841
  // user re-opening connection after closing it
787
842
  else if(this._machine.is("permanentlyClosed")) {
843
+ resetConnectionParameters(this);
788
844
  this._machine.transition('waiting');
789
845
  }
790
846
  };
791
847
 
792
848
  Connection.prototype.send = function(data) {
793
849
  if (this._machine.is('connected')) {
794
- this.socket.send(data);
795
- return true;
850
+ // Bug in iOS (reproduced in 5.0.1) Mobile Safari:
851
+ // 1. Open page/tab, connect WS, get some data.
852
+ // 2. Switch tab or close Mobile Safari and wait for WS connection to get closed (probably by server).
853
+ // 3. Switch back to tab or open Mobile Safari and Mobile Safari crashes.
854
+ // The problem is that WS tries to send data on closed WS connection before it realises it is closed.
855
+ // The timeout means that by the time the send happens, the WS readyState correctly reflects closed state.
856
+ var self = this;
857
+ setTimeout(function() {
858
+ self.socket.send(data);
859
+ }, 0);
860
+ return true; // only a reflection of fact that WS thinks it is open - could get returned before some lower-level failure.
796
861
  } else {
797
862
  return false;
798
863
  }
@@ -823,185 +888,151 @@ Example:
823
888
  this.Pusher.Connection = Connection;
824
889
  }).call(this);
825
890
 
826
- Pusher.Channels = function() {
827
- this.channels = {};
828
- };
891
+ ;(function() {
892
+ Pusher.Channels = function() {
893
+ this.channels = {};
894
+ };
829
895
 
830
- Pusher.Channels.prototype = {
831
- add: function(channel_name, pusher) {
832
- var existing_channel = this.find(channel_name);
833
- if (!existing_channel) {
834
- var channel = Pusher.Channel.factory(channel_name, pusher);
835
- this.channels[channel_name] = channel;
836
- return channel;
837
- } else {
838
- return existing_channel;
839
- }
840
- },
896
+ Pusher.Channels.prototype = {
897
+ add: function(channel_name, pusher) {
898
+ var existing_channel = this.find(channel_name);
899
+ if (!existing_channel) {
900
+ var channel = Pusher.Channel.factory(channel_name, pusher);
901
+ this.channels[channel_name] = channel;
902
+ return channel;
903
+ } else {
904
+ return existing_channel;
905
+ }
906
+ },
841
907
 
842
- find: function(channel_name) {
843
- return this.channels[channel_name];
844
- },
908
+ find: function(channel_name) {
909
+ return this.channels[channel_name];
910
+ },
845
911
 
846
- remove: function(channel_name) {
847
- delete this.channels[channel_name];
848
- },
912
+ remove: function(channel_name) {
913
+ delete this.channels[channel_name];
914
+ },
849
915
 
850
- disconnect: function () {
851
- for(var channel_name in this.channels){
852
- this.channels[channel_name].disconnect()
916
+ disconnect: function () {
917
+ for(var channel_name in this.channels){
918
+ this.channels[channel_name].disconnect()
919
+ }
853
920
  }
854
- }
855
- };
856
-
857
- Pusher.Channel = function(channel_name, pusher) {
858
- var self = this;
859
- Pusher.EventsDispatcher.call(this, function(event_name, event_data) {
860
- Pusher.debug('No callbacks on ' + channel_name + ' for ' + event_name);
861
- });
862
-
863
- this.pusher = pusher;
864
- this.name = channel_name;
865
- this.subscribed = false;
866
-
867
- this.bind('pusher_internal:subscription_succeeded', function(data) {
868
- self.onSubscriptionSucceeded(data);
869
- });
870
- };
871
-
872
- Pusher.Channel.prototype = {
873
- // inheritable constructor
874
- init: function() {},
875
- disconnect: function() {},
876
-
877
- onSubscriptionSucceeded: function(data) {
878
- this.subscribed = true;
879
- this.emit('pusher:subscription_succeeded');
880
- },
881
-
882
- authorize: function(pusher, callback){
883
- callback(false, {}); // normal channels don't require auth
884
- },
885
-
886
- trigger: function(event, data) {
887
- return this.pusher.send_event(event, data, this.name);
888
- }
889
- };
921
+ };
890
922
 
891
- Pusher.Util.extend(Pusher.Channel.prototype, Pusher.EventsDispatcher.prototype);
923
+ Pusher.Channel = function(channel_name, pusher) {
924
+ var self = this;
925
+ Pusher.EventsDispatcher.call(this, function(event_name, event_data) {
926
+ Pusher.debug('No callbacks on ' + channel_name + ' for ' + event_name);
927
+ });
892
928
 
929
+ this.pusher = pusher;
930
+ this.name = channel_name;
931
+ this.subscribed = false;
893
932
 
933
+ this.bind('pusher_internal:subscription_succeeded', function(data) {
934
+ self.onSubscriptionSucceeded(data);
935
+ });
936
+ };
894
937
 
895
- Pusher.auth_callbacks = {};
938
+ Pusher.Channel.prototype = {
939
+ // inheritable constructor
940
+ init: function() {},
941
+ disconnect: function() {
942
+ this.subscribed = false;
943
+ this.emit("pusher_internal:disconnected");
944
+ },
896
945
 
897
- Pusher.authorizers = {
898
- ajax: function(pusher, callback){
899
- var self = this, xhr;
946
+ onSubscriptionSucceeded: function(data) {
947
+ this.subscribed = true;
948
+ this.emit('pusher:subscription_succeeded');
949
+ },
900
950
 
901
- if (Pusher.XHR) {
902
- xhr = new Pusher.XHR();
903
- } else {
904
- xhr = (window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"));
905
- }
951
+ authorize: function(socketId, options, callback){
952
+ return callback(false, {}); // normal channels don't require auth
953
+ },
906
954
 
907
- xhr.open("POST", Pusher.channel_auth_endpoint, true);
908
- xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
909
- xhr.onreadystatechange = function() {
910
- if (xhr.readyState == 4) {
911
- if (xhr.status == 200) {
912
- var data, parsed = false;
955
+ trigger: function(event, data) {
956
+ return this.pusher.send_event(event, data, this.name);
957
+ }
958
+ };
913
959
 
914
- try {
915
- data = JSON.parse(xhr.responseText);
916
- parsed = true;
917
- } catch (e) {
918
- callback(true, 'JSON returned from webapp was invalid, yet status code was 200. Data was: ' + xhr.responseText);
919
- }
960
+ Pusher.Util.extend(Pusher.Channel.prototype, Pusher.EventsDispatcher.prototype);
920
961
 
921
- if (parsed) { // prevents double execution.
922
- callback(false, data);
923
- }
924
- } else {
925
- Pusher.warn("Couldn't get auth info from your webapp", status);
926
- callback(true, xhr.status);
962
+ Pusher.Channel.PrivateChannel = {
963
+ authorize: function(socketId, options, callback){
964
+ var self = this;
965
+ var authorizer = new Pusher.Channel.Authorizer(this, Pusher.channel_auth_transport, options);
966
+ return authorizer.authorize(socketId, function(err, authData) {
967
+ if(!err) {
968
+ self.emit('pusher_internal:authorized', authData);
927
969
  }
928
- }
929
- };
930
- xhr.send('socket_id=' + encodeURIComponent(pusher.connection.socket_id) + '&channel_name=' + encodeURIComponent(self.name));
931
- },
932
- jsonp: function(pusher, callback){
933
- var qstring = 'socket_id=' + encodeURIComponent(pusher.connection.socket_id) + '&channel_name=' + encodeURIComponent(this.name);
934
- var script = document.createElement("script");
935
- // Hacked wrapper.
936
- Pusher.auth_callbacks[this.name] = function(data) {
937
- callback(false, data);
938
- };
939
- var callback_name = "Pusher.auth_callbacks['" + this.name + "']";
940
- script.src = Pusher.channel_auth_endpoint+'?callback='+encodeURIComponent(callback_name)+'&'+qstring;
941
- var head = document.getElementsByTagName("head")[0] || document.documentElement;
942
- head.insertBefore( script, head.firstChild );
943
- }
944
- };
945
970
 
946
- Pusher.Channel.PrivateChannel = {
947
- authorize: function(pusher, callback){
948
- Pusher.authorizers[Pusher.channel_auth_transport].scopedTo(this)(pusher, callback);
949
- }
950
- };
951
-
952
- Pusher.Channel.PresenceChannel = {
953
- init: function(){
954
- this.bind('pusher_internal:member_added', function(data){
955
- var member = this.members.add(data.user_id, data.user_info);
956
- this.emit('pusher:member_added', member);
957
- }.scopedTo(this))
958
-
959
- this.bind('pusher_internal:member_removed', function(data){
960
- var member = this.members.remove(data.user_id);
961
- if (member) {
962
- this.emit('pusher:member_removed', member);
963
- }
964
- }.scopedTo(this))
965
- },
971
+ callback(err, authData);
972
+ });
973
+ }
974
+ };
966
975
 
967
- disconnect: function(){
968
- this.members.clear();
969
- },
976
+ Pusher.Channel.PresenceChannel = {
977
+ init: function(){
978
+ this.members = new Members(this); // leeches off channel events
979
+ },
970
980
 
971
- onSubscriptionSucceeded: function(data) {
972
- this.members._members_map = data.presence.hash;
973
- this.members.count = data.presence.count;
974
- this.subscribed = true;
981
+ onSubscriptionSucceeded: function(data) {
982
+ this.subscribed = true;
983
+ // We override this because we want the Members obj to be responsible for
984
+ // emitting the pusher:subscription_succeeded. It will do this after it has done its work.
985
+ }
986
+ };
975
987
 
976
- this.emit('pusher:subscription_succeeded', this.members);
977
- },
988
+ var Members = function(channel) {
989
+ var self = this;
978
990
 
979
- members: {
980
- _members_map: {},
981
- count: 0,
991
+ var reset = function() {
992
+ this._members_map = {};
993
+ this.count = 0;
994
+ this.me = null;
995
+ };
996
+ reset.call(this);
997
+
998
+ channel.bind('pusher_internal:authorized', function(authorizedData) {
999
+ var channelData = JSON.parse(authorizedData.channel_data);
1000
+ channel.bind("pusher_internal:subscription_succeeded", function(subscriptionData) {
1001
+ self._members_map = subscriptionData.presence.hash;
1002
+ self.count = subscriptionData.presence.count;
1003
+ self.me = self.get(channelData.user_id);
1004
+ channel.emit('pusher:subscription_succeeded', self);
1005
+ });
1006
+ });
982
1007
 
983
- each: function(callback) {
984
- for(var i in this._members_map) {
985
- callback({
986
- id: i,
987
- info: this._members_map[i]
988
- });
1008
+ channel.bind('pusher_internal:member_added', function(data) {
1009
+ if(self.get(data.user_id) === null) { // only incr if user_id does not already exist
1010
+ self.count++;
989
1011
  }
990
- },
991
1012
 
992
- add: function(id, info) {
993
- this._members_map[id] = info;
994
- this.count++;
995
- return this.get(id);
996
- },
1013
+ self._members_map[data.user_id] = data.user_info;
1014
+ channel.emit('pusher:member_added', self.get(data.user_id));
1015
+ });
997
1016
 
998
- remove: function(user_id) {
999
- var member = this.get(user_id);
1000
- if (member) {
1001
- delete this._members_map[user_id];
1002
- this.count--;
1017
+ channel.bind('pusher_internal:member_removed', function(data) {
1018
+ var member = self.get(data.user_id);
1019
+ if(member) {
1020
+ delete self._members_map[data.user_id];
1021
+ self.count--;
1022
+ channel.emit('pusher:member_removed', member);
1023
+ }
1024
+ });
1025
+
1026
+ channel.bind('pusher_internal:disconnected', function() {
1027
+ reset.call(self);
1028
+ });
1029
+ };
1030
+
1031
+ Members.prototype = {
1032
+ each: function(callback) {
1033
+ for(var i in this._members_map) {
1034
+ callback(this.get(i));
1003
1035
  }
1004
- return member;
1005
1036
  },
1006
1037
 
1007
1038
  get: function(user_id) {
@@ -1013,27 +1044,114 @@ Pusher.Channel.PresenceChannel = {
1013
1044
  } else { // have never heard of this user
1014
1045
  return null;
1015
1046
  }
1047
+ }
1048
+ };
1049
+
1050
+ Pusher.Channel.factory = function(channel_name, pusher){
1051
+ var channel = new Pusher.Channel(channel_name, pusher);
1052
+ if (channel_name.indexOf('private-') === 0) {
1053
+ Pusher.Util.extend(channel, Pusher.Channel.PrivateChannel);
1054
+ } else if (channel_name.indexOf('presence-') === 0) {
1055
+ Pusher.Util.extend(channel, Pusher.Channel.PrivateChannel);
1056
+ Pusher.Util.extend(channel, Pusher.Channel.PresenceChannel);
1057
+ };
1058
+ channel.init();
1059
+ return channel;
1060
+ };
1061
+ }).call(this);
1062
+ ;(function() {
1063
+ Pusher.Channel.Authorizer = function(channel, type, options) {
1064
+ this.channel = channel;
1065
+ this.type = type;
1066
+
1067
+ this.authOptions = (options || {}).auth || {};
1068
+ };
1069
+
1070
+ Pusher.Channel.Authorizer.prototype = {
1071
+ composeQuery: function(socketId) {
1072
+ var query = '&socket_id=' + encodeURIComponent(socketId)
1073
+ + '&channel_name=' + encodeURIComponent(this.channel.name);
1074
+
1075
+ for(var i in this.authOptions.params) {
1076
+ query += "&" + encodeURIComponent(i) + "=" + encodeURIComponent(this.authOptions.params[i]);
1077
+ }
1078
+
1079
+ return query;
1016
1080
  },
1017
1081
 
1018
- clear: function() {
1019
- this._members_map = {};
1020
- this.count = 0;
1082
+ authorize: function(socketId, callback) {
1083
+ return Pusher.authorizers[this.type].call(this, socketId, callback);
1021
1084
  }
1022
- }
1023
- };
1024
-
1025
- Pusher.Channel.factory = function(channel_name, pusher){
1026
- var channel = new Pusher.Channel(channel_name, pusher);
1027
- if (channel_name.indexOf('private-') === 0) {
1028
- Pusher.Util.extend(channel, Pusher.Channel.PrivateChannel);
1029
- } else if (channel_name.indexOf('presence-') === 0) {
1030
- Pusher.Util.extend(channel, Pusher.Channel.PrivateChannel);
1031
- Pusher.Util.extend(channel, Pusher.Channel.PresenceChannel);
1032
1085
  };
1033
- channel.init();
1034
- return channel;
1035
- };
1036
1086
 
1087
+
1088
+ Pusher.auth_callbacks = {};
1089
+ Pusher.authorizers = {
1090
+ ajax: function(socketId, callback){
1091
+ var self = this, xhr;
1092
+
1093
+ if (Pusher.XHR) {
1094
+ xhr = new Pusher.XHR();
1095
+ } else {
1096
+ xhr = (window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"));
1097
+ }
1098
+
1099
+ xhr.open("POST", Pusher.channel_auth_endpoint, true);
1100
+
1101
+ // add request headers
1102
+ xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
1103
+ for(var headerName in this.authOptions.headers) {
1104
+ xhr.setRequestHeader(headerName, this.authOptions.headers[headerName]);
1105
+ }
1106
+
1107
+ xhr.onreadystatechange = function() {
1108
+ if (xhr.readyState == 4) {
1109
+ if (xhr.status == 200) {
1110
+ var data, parsed = false;
1111
+
1112
+ try {
1113
+ data = JSON.parse(xhr.responseText);
1114
+ parsed = true;
1115
+ } catch (e) {
1116
+ callback(true, 'JSON returned from webapp was invalid, yet status code was 200. Data was: ' + xhr.responseText);
1117
+ }
1118
+
1119
+ if (parsed) { // prevents double execution.
1120
+ callback(false, data);
1121
+ }
1122
+ } else {
1123
+ Pusher.warn("Couldn't get auth info from your webapp", xhr.status);
1124
+ callback(true, xhr.status);
1125
+ }
1126
+ }
1127
+ };
1128
+
1129
+ xhr.send(this.composeQuery(socketId));
1130
+ return xhr;
1131
+ },
1132
+
1133
+ jsonp: function(socketId, callback){
1134
+ if(this.authOptions.headers !== undefined) {
1135
+ Pusher.warn("Warn", "To send headers with the auth request, you must use AJAX, rather than JSONP.");
1136
+ }
1137
+
1138
+ var script = document.createElement("script");
1139
+ // Hacked wrapper.
1140
+ Pusher.auth_callbacks[this.channel.name] = function(data) {
1141
+ callback(false, data);
1142
+ };
1143
+
1144
+ var callback_name = "Pusher.auth_callbacks['" + this.channel.name + "']";
1145
+ script.src = Pusher.channel_auth_endpoint
1146
+ + '?callback='
1147
+ + encodeURIComponent(callback_name)
1148
+ + this.composeQuery(socketId);
1149
+
1150
+ var head = document.getElementsByTagName("head")[0] || document.documentElement;
1151
+ head.insertBefore( script, head.firstChild );
1152
+ }
1153
+ };
1154
+ }).call(this);
1037
1155
  var _require = (function () {
1038
1156
 
1039
1157
  var handleScriptLoaded;
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pusher_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,19 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-01-25 00:00:00.000000000 Z
12
+ date: 2012-04-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: pusher
16
- requirement: &70209654617200 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: 0.9.1
21
+ version: 0.9.2
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70209654617200
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 0.9.2
25
30
  description: Adds pusher.js/backpusher.js to the asset pipeline and pusher-gem to
26
31
  to your app.
27
32
  email:
@@ -57,7 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
57
62
  version: '0'
58
63
  requirements: []
59
64
  rubyforge_project:
60
- rubygems_version: 1.8.10
65
+ rubygems_version: 1.8.21
61
66
  signing_key:
62
67
  specification_version: 3
63
68
  summary: Pusher integration for Rails 3.1+