pusher_rails 0.1.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.
data/README.md ADDED
@@ -0,0 +1,9 @@
1
+ Pusher for Rails 3.1+
2
+ =====================
3
+
4
+ Adds the javascript needed to use [Pusher](https://pusher.com) in your rails 3.1 app.
5
+
6
+ Add this to your app/assets/javascripts/application.js:
7
+
8
+ //=require pusher
9
+
File without changes
@@ -0,0 +1,11 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'pusher_rails'
3
+ s.version = '0.1.0'
4
+ s.author = 'David Grandinetti'
5
+ s.email = 'dave@wegoto12.com'
6
+ s.summary = 'Pusher integration for Rails 3.1+'
7
+ s.description = 'Adds pusher.js to the asset pipeline'
8
+ s.homepage = 'https://github.com/dbgrandi/pusher_rails'
9
+
10
+ s.files = Dir["#{File.dirname(__FILE__)}/**/*"]
11
+ end
@@ -0,0 +1,1037 @@
1
+ /*!
2
+ * Pusher JavaScript Library v1.9.1
3
+ * http://pusherapp.com/
4
+ *
5
+ * Copyright 2011, Pusher
6
+ * Released under the MIT licence.
7
+ */
8
+
9
+ if (typeof 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)));
15
+ };
16
+ };
17
+ }
18
+
19
+ var Pusher = function(app_key, options) {
20
+ this.options = options || {};
21
+ this.path = '/app/' + app_key + '?client=js&version=' + Pusher.VERSION;
22
+ this.key = app_key;
23
+ this.channels = new Pusher.Channels();
24
+ this.global_channel = new Pusher.Channel('pusher_global_channel');
25
+ this.global_channel.global = true;
26
+
27
+ var self = this;
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
+ self.send_local_event(params.event, params.data, params.channel);
38
+ })
39
+ .bind('disconnected', function() {
40
+ self.channels.disconnect();
41
+ })
42
+ .bind('error', function(err) {
43
+ Pusher.debug('Error', err);
44
+ });
45
+
46
+ Pusher.instances.push(this);
47
+
48
+ if (Pusher.isReady) self.connect();
49
+ };
50
+ Pusher.instances = [];
51
+ Pusher.prototype = {
52
+ channel: function(name) {
53
+ return this.channels.find(name);
54
+ },
55
+
56
+ connect: function() {
57
+ this.connection.connect();
58
+ },
59
+
60
+ disconnect: function() {
61
+ Pusher.debug('Disconnecting');
62
+ this.connection.disconnect();
63
+ },
64
+
65
+ bind: function(event_name, callback) {
66
+ this.global_channel.bind(event_name, callback);
67
+ return this;
68
+ },
69
+
70
+ bind_all: function(callback) {
71
+ this.global_channel.bind_all(callback);
72
+ return this;
73
+ },
74
+
75
+ subscribeAll: function() {
76
+ var channel;
77
+ for (channel in this.channels.channels) {
78
+ if (this.channels.channels.hasOwnProperty(channel)) {
79
+ this.subscribe(channel);
80
+ }
81
+ }
82
+ },
83
+
84
+ subscribe: function(channel_name) {
85
+ var self = this;
86
+ var channel = this.channels.add(channel_name, this);
87
+ if (this.connection.state === 'connected') {
88
+ channel.authorize(this, function(data) {
89
+ self.send_event('pusher:subscribe', {
90
+ channel: channel_name,
91
+ auth: data.auth,
92
+ channel_data: data.channel_data
93
+ });
94
+ });
95
+ }
96
+ return channel;
97
+ },
98
+
99
+ unsubscribe: function(channel_name) {
100
+ this.channels.remove(channel_name);
101
+ if (this.connection.state === 'connected') {
102
+ this.send_event('pusher:unsubscribe', {
103
+ channel: channel_name
104
+ });
105
+ }
106
+ },
107
+
108
+ send_event: function(event_name, data, channel) {
109
+ Pusher.debug("Event sent (channel,event,data)", channel, event_name, data);
110
+
111
+ var payload = {
112
+ event: event_name,
113
+ data: data
114
+ };
115
+ if (channel) payload['channel'] = channel;
116
+
117
+ this.connection.send(JSON.stringify(payload));
118
+ return this;
119
+ },
120
+
121
+ send_local_event: function(event_name, event_data, channel_name) {
122
+ event_data = Pusher.data_decorator(event_name, event_data);
123
+ if (channel_name) {
124
+ var channel = this.channel(channel_name);
125
+ if (channel) {
126
+ channel.dispatch_with_all(event_name, event_data);
127
+ }
128
+ } else {
129
+ // Bit hacky but these events won't get logged otherwise
130
+ Pusher.debug("Event recd (event,data)", event_name, event_data);
131
+ }
132
+
133
+ this.global_channel.dispatch_with_all(event_name, event_data);
134
+ }
135
+ };
136
+
137
+ Pusher.Util = {
138
+ extend: function extend(target, extensions) {
139
+ for (var property in extensions) {
140
+ if (extensions[property] && extensions[property].constructor &&
141
+ extensions[property].constructor === Object) {
142
+ target[property] = extend(target[property] || {}, extensions[property]);
143
+ } else {
144
+ target[property] = extensions[property];
145
+ }
146
+ }
147
+ return target;
148
+ }
149
+ };
150
+
151
+ // To receive log output provide a Pusher.log function, for example
152
+ // Pusher.log = function(m){console.log(m)}
153
+ Pusher.debug = function() {
154
+ if (!Pusher.log) { return }
155
+ var m = ["Pusher"]
156
+ for (var i = 0; i < arguments.length; i++){
157
+ if (typeof arguments[i] === "string") {
158
+ m.push(arguments[i])
159
+ } else {
160
+ if (window['JSON'] == undefined) {
161
+ m.push(arguments[i].toString());
162
+ } else {
163
+ m.push(JSON.stringify(arguments[i]))
164
+ }
165
+ }
166
+ };
167
+ Pusher.log(m.join(" : "))
168
+ }
169
+
170
+ // Pusher defaults
171
+ Pusher.VERSION = '1.9.1';
172
+
173
+ Pusher.host = 'ws.pusherapp.com';
174
+ Pusher.ws_port = 80;
175
+ Pusher.wss_port = 443;
176
+ Pusher.channel_auth_endpoint = '/pusher/auth';
177
+ Pusher.connection_timeout = 5000;
178
+ Pusher.cdn_http = 'http://js.pusherapp.com/'
179
+ Pusher.cdn_https = 'https://d3ds63zw57jt09.cloudfront.net/'
180
+ Pusher.data_decorator = function(event_name, event_data){ return event_data }; // wrap event_data before dispatching
181
+ Pusher.allow_reconnect = true;
182
+ Pusher.channel_auth_transport = 'ajax';
183
+
184
+ Pusher.isReady = false;
185
+ Pusher.ready = function() {
186
+ Pusher.isReady = true;
187
+ for (var i = 0, l = Pusher.instances.length; i < l; i++) {
188
+ Pusher.instances[i].connect();
189
+ }
190
+ };
191
+
192
+ ;(function() {
193
+ /* Abstract event binding
194
+ Example:
195
+
196
+ var MyEventEmitter = function(){};
197
+ MyEventEmitter.prototype = new Pusher.EventsDispatcher;
198
+
199
+ var emitter = new MyEventEmitter();
200
+
201
+ // Bind to single event
202
+ emitter.bind('foo_event', function(data){ alert(data)} );
203
+
204
+ // Bind to all
205
+ emitter.bind_all(function(event_name, data){ alert(data) });
206
+
207
+ --------------------------------------------------------*/
208
+ function EventsDispatcher() {
209
+ this.callbacks = {};
210
+ this.global_callbacks = [];
211
+ }
212
+
213
+ EventsDispatcher.prototype.bind = function(event_name, callback) {
214
+ this.callbacks[event_name] = this.callbacks[event_name] || [];
215
+ this.callbacks[event_name].push(callback);
216
+ return this;// chainable
217
+ };
218
+
219
+ EventsDispatcher.prototype.emit = function(event_name, data) {
220
+ this.dispatch_global_callbacks(event_name, data);
221
+ this.dispatch(event_name, data);
222
+ return this;
223
+ };
224
+
225
+ EventsDispatcher.prototype.bind_all = function(callback) {
226
+ this.global_callbacks.push(callback);
227
+ return this;
228
+ };
229
+
230
+ EventsDispatcher.prototype.dispatch = function(event_name, event_data) {
231
+ var callbacks = this.callbacks[event_name];
232
+
233
+ if (callbacks) {
234
+ for (var i = 0; i < callbacks.length; i++) {
235
+ callbacks[i](event_data);
236
+ }
237
+ } else {
238
+ // Log is un-necessary in case of global channel or connection object
239
+ if (!(this.global || this instanceof Pusher.Connection || this instanceof Pusher.Machine)) {
240
+ Pusher.debug('No callbacks for ' + event_name, event_data);
241
+ }
242
+ }
243
+ };
244
+
245
+ EventsDispatcher.prototype.dispatch_global_callbacks = function(event_name, data) {
246
+ for (var i = 0; i < this.global_callbacks.length; i++) {
247
+ this.global_callbacks[i](event_name, data);
248
+ }
249
+ };
250
+
251
+ EventsDispatcher.prototype.dispatch_with_all = function(event_name, data) {
252
+ this.dispatch(event_name, data);
253
+ this.dispatch_global_callbacks(event_name, data);
254
+ };
255
+
256
+ this.Pusher.EventsDispatcher = EventsDispatcher;
257
+ }).call(this);
258
+
259
+ ;(function() {
260
+ var Pusher = this.Pusher;
261
+
262
+ /*-----------------------------------------------
263
+ Helpers:
264
+ -----------------------------------------------*/
265
+
266
+ // MSIE doesn't have array.indexOf
267
+ var nativeIndexOf = Array.prototype.indexOf;
268
+ function indexOf(array, item) {
269
+ if (array == null) return -1;
270
+ if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
271
+ for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
272
+ return -1;
273
+ }
274
+
275
+
276
+ function capitalize(str) {
277
+ return str.substr(0, 1).toUpperCase() + str.substr(1);
278
+ }
279
+
280
+
281
+ function safeCall(method, obj, data) {
282
+ if (obj[method] !== undefined) {
283
+ obj[method](data);
284
+ }
285
+ }
286
+
287
+ /*-----------------------------------------------
288
+ The State Machine
289
+ -----------------------------------------------*/
290
+ function Machine(actor, initialState, transitions, stateActions) {
291
+ Pusher.EventsDispatcher.call(this);
292
+
293
+ this.actor = actor;
294
+ this.state = undefined;
295
+ this.errors = [];
296
+
297
+ // functions for each state
298
+ this.stateActions = stateActions;
299
+
300
+ // set up the transitions
301
+ this.transitions = transitions;
302
+
303
+ this.transition(initialState);
304
+ };
305
+
306
+ Machine.prototype.transition = function(nextState, data) {
307
+ var prevState = this.state;
308
+ var stateCallbacks = this.stateActions;
309
+
310
+ if (prevState && (indexOf(this.transitions[prevState], nextState) == -1)) {
311
+ throw new Error(this.actor.key + ': Invalid transition [' + prevState + ' to ' + nextState + ']');
312
+ }
313
+
314
+ // exit
315
+ safeCall(prevState + 'Exit', stateCallbacks, data);
316
+
317
+ // tween
318
+ safeCall(prevState + 'To' + capitalize(nextState), stateCallbacks, data);
319
+
320
+ // pre
321
+ safeCall(nextState + 'Pre', stateCallbacks, data);
322
+
323
+ // change state:
324
+ this.state = nextState;
325
+
326
+ // handy to bind to
327
+ this.emit('state_change', {
328
+ oldState: prevState,
329
+ newState: nextState
330
+ });
331
+
332
+ // Post:
333
+ safeCall(nextState + 'Post', stateCallbacks, data);
334
+ };
335
+
336
+ Machine.prototype.is = function(state) {
337
+ return this.state === state;
338
+ };
339
+
340
+ Machine.prototype.isNot = function(state) {
341
+ return this.state !== state;
342
+ };
343
+
344
+ Pusher.Util.extend(Machine.prototype, Pusher.EventsDispatcher.prototype);
345
+
346
+ this.Pusher.Machine = Machine;
347
+ }).call(this);
348
+
349
+ ;(function() {
350
+ var Pusher = this.Pusher;
351
+
352
+ var machineTransitions = {
353
+ 'initialized': ['waiting', 'failed'],
354
+ 'waiting': ['connecting', 'permanentlyClosed'],
355
+ 'connecting': ['open', 'permanentlyClosing', 'impermanentlyClosing', 'waiting'],
356
+ 'open': ['connected', 'permanentlyClosing', 'impermanentlyClosing', 'waiting'],
357
+ 'connected': ['permanentlyClosing', 'impermanentlyClosing', 'waiting'],
358
+ 'impermanentlyClosing': ['waiting', 'permanentlyClosing'],
359
+ 'permanentlyClosing': ['permanentlyClosed'],
360
+ 'permanentlyClosed': ['waiting']
361
+ };
362
+
363
+
364
+ // Amount to add to time between connection attemtpts per failed attempt.
365
+ var UNSUCCESSFUL_CONNECTION_ATTEMPT_ADDITIONAL_WAIT = 2000;
366
+ var UNSUCCESSFUL_OPEN_ATTEMPT_ADDITIONAL_TIMEOUT = 2000;
367
+ var UNSUCCESSFUL_CONNECTED_ATTEMPT_ADDITIONAL_TIMEOUT = 2000;
368
+
369
+ var MAX_CONNECTION_ATTEMPT_WAIT = 5 * UNSUCCESSFUL_CONNECTION_ATTEMPT_ADDITIONAL_WAIT;
370
+ var MAX_OPEN_ATTEMPT_TIMEOUT = 5 * UNSUCCESSFUL_OPEN_ATTEMPT_ADDITIONAL_TIMEOUT;
371
+ var MAX_CONNECTED_ATTEMPT_TIMEOUT = 5 * UNSUCCESSFUL_CONNECTED_ATTEMPT_ADDITIONAL_TIMEOUT;
372
+
373
+ function resetConnectionParameters(connection) {
374
+ connection.connectionWait = 0;
375
+
376
+ if (Pusher.TransportType === 'flash') {
377
+ // Flash needs a bit more time
378
+ connection.openTimeout = 5000;
379
+ } else {
380
+ connection.openTimeout = 2000;
381
+ }
382
+ connection.connectedTimeout = 2000;
383
+ connection.connectionSecure = connection.compulsorySecure;
384
+ connection.connectionAttempts = 0;
385
+ }
386
+
387
+ function Connection(key, options) {
388
+ var self = this;
389
+
390
+ Pusher.EventsDispatcher.call(this);
391
+
392
+ this.options = Pusher.Util.extend({encrypted: false}, options || {});
393
+
394
+ // define the state machine that runs the connection
395
+ this._machine = new Pusher.Machine(self, 'initialized', machineTransitions, {
396
+
397
+ // TODO: Use the constructor for this.
398
+ initializedPre: function() {
399
+ self.compulsorySecure = self.options.encrypted;
400
+
401
+ self.key = key;
402
+ self.socket = null;
403
+ self.socket_id = null;
404
+
405
+ self.state = 'initialized';
406
+ },
407
+
408
+ waitingPre: function() {
409
+ self._waitingTimer = setTimeout(function() {
410
+ self._machine.transition('connecting');
411
+ }, self.connectionWait);
412
+
413
+ if (self.connectionWait > 0) {
414
+ informUser('connecting_in', self.connectionWait);
415
+ }
416
+
417
+ if (self.connectionAttempts > 4) {
418
+ triggerStateChange('unavailable');
419
+ } else {
420
+ triggerStateChange('connecting');
421
+ }
422
+ },
423
+
424
+ waitingExit: function() {
425
+ clearTimeout(self._waitingTimer);
426
+ },
427
+
428
+ connectingPre: function() {
429
+ // removed: if not closed, something is wrong that we should fix
430
+ // if(self.socket !== undefined) self.socket.close();
431
+ var url = formatURL(self.key, self.connectionSecure);
432
+ Pusher.debug('Connecting', url);
433
+ self.socket = new Pusher.Transport(url);
434
+ // now that the socket connection attempt has been started,
435
+ // set up the callbacks fired by the socket for different outcomes
436
+ self.socket.onopen = ws_onopen;
437
+ self.socket.onclose = transitionToWaiting;
438
+ self.socket.onerror = ws_onError;
439
+
440
+ // allow time to get ws_onOpen, otherwise close socket and try again
441
+ self._connectingTimer = setTimeout(TransitionToImpermanentClosing, self.openTimeout);
442
+ },
443
+
444
+ connectingExit: function() {
445
+ clearTimeout(self._connectingTimer);
446
+ },
447
+
448
+ connectingToWaiting: function() {
449
+ updateConnectionParameters();
450
+
451
+ // FUTURE: update only ssl
452
+ },
453
+
454
+ connectingToImpermanentlyClosing: function() {
455
+ updateConnectionParameters();
456
+
457
+ // FUTURE: update only timeout
458
+ },
459
+
460
+ openPre: function() {
461
+ self.socket.onmessage = ws_onMessage;
462
+ self.socket.onerror = ws_onError;
463
+ self.socket.onclose = function() {
464
+ self._machine.transition('waiting');
465
+ };
466
+
467
+ // allow time to get connected-to-Pusher message, otherwise close socket, try again
468
+ self._openTimer = setTimeout(TransitionToImpermanentClosing, self.connectedTimeout);
469
+ },
470
+
471
+ openExit: function() {
472
+ clearTimeout(self._openTimer);
473
+ },
474
+
475
+ openToWaiting: function() {
476
+ updateConnectionParameters();
477
+ },
478
+
479
+ openToImpermanentlyClosing: function() {
480
+ updateConnectionParameters();
481
+ },
482
+
483
+ connectedPre: function(socket_id) {
484
+ self.socket_id = socket_id;
485
+
486
+ self.socket.onmessage = ws_onMessage;
487
+ self.socket.onerror = ws_onError;
488
+ self.socket.onclose = function() {
489
+ self._machine.transition('waiting');
490
+ };
491
+
492
+ resetConnectionParameters(self);
493
+ },
494
+
495
+ connectedPost: function() {
496
+ triggerStateChange('connected');
497
+ },
498
+
499
+ connectedExit: function() {
500
+ triggerStateChange('disconnected');
501
+ },
502
+
503
+ impermanentlyClosingPost: function() {
504
+ self.socket.onclose = transitionToWaiting;
505
+ self.socket.close();
506
+ },
507
+
508
+ permanentlyClosingPost: function() {
509
+ self.socket.onclose = function() {
510
+ resetConnectionParameters(self);
511
+ self._machine.transition('permanentlyClosed');
512
+ };
513
+
514
+ self.socket.close();
515
+ },
516
+
517
+ failedPre: function() {
518
+ triggerStateChange('failed');
519
+ Pusher.debug('WebSockets are not available in this browser.');
520
+ }
521
+ });
522
+
523
+ /*-----------------------------------------------
524
+ -----------------------------------------------*/
525
+
526
+ function updateConnectionParameters() {
527
+ if (self.connectionWait < MAX_CONNECTION_ATTEMPT_WAIT) {
528
+ self.connectionWait += UNSUCCESSFUL_CONNECTION_ATTEMPT_ADDITIONAL_WAIT;
529
+ }
530
+
531
+ if (self.openTimeout < MAX_OPEN_ATTEMPT_TIMEOUT) {
532
+ self.openTimeout += UNSUCCESSFUL_OPEN_ATTEMPT_ADDITIONAL_TIMEOUT;
533
+ }
534
+
535
+ if (self.connectedTimeout < MAX_CONNECTED_ATTEMPT_TIMEOUT) {
536
+ self.connectedTimeout += UNSUCCESSFUL_CONNECTED_ATTEMPT_ADDITIONAL_TIMEOUT;
537
+ }
538
+
539
+ if (self.compulsorySecure !== true) {
540
+ self.connectionSecure = !self.connectionSecure;
541
+ }
542
+
543
+ self.connectionAttempts++;
544
+ }
545
+
546
+ function formatURL(key, isSecure) {
547
+ var port = Pusher.ws_port;
548
+ var protocol = 'ws://';
549
+
550
+ if (isSecure) {
551
+ port = Pusher.wss_port;
552
+ protocol = 'wss://';
553
+ }
554
+
555
+ return protocol + Pusher.host + ':' + port + '/app/' + key + '?client=js&version=' + Pusher.VERSION;
556
+ }
557
+
558
+ // callback for close and retry. Used on timeouts.
559
+ function TransitionToImpermanentClosing() {
560
+ self._machine.transition('impermanentlyClosing');
561
+ }
562
+
563
+ /*-----------------------------------------------
564
+ WebSocket Callbacks
565
+ -----------------------------------------------*/
566
+
567
+ // no-op, as we only care when we get pusher:connection_established
568
+ function ws_onopen() {
569
+ self._machine.transition('open');
570
+ };
571
+
572
+ function ws_onMessage(event) {
573
+ var params = parseWebSocketEvent(event);
574
+
575
+ // case of invalid JSON payload sent
576
+ // we have to handle the error in the parseWebSocketEvent
577
+ // method as JavaScript error objects are kinda icky.
578
+ if (typeof params === 'undefined') return;
579
+
580
+ Pusher.debug('Event recd (event,data)', params.event, params.data);
581
+
582
+ // Continue to work with valid payloads:
583
+ if (params.event === 'pusher:connection_established') {
584
+ self._machine.transition('connected', params.data.socket_id);
585
+ } else if (params.event === 'pusher:error') {
586
+ // first inform the end-developer of this error
587
+ informUser('error', {type: 'PusherError', data: params.data});
588
+
589
+ // App not found by key - close connection
590
+ if (params.data.code === 4001) {
591
+ self._machine.transition('permanentlyClosing');
592
+ }
593
+ } else if (params.event === 'pusher:heartbeat') {
594
+ } else if (self._machine.is('connected')) {
595
+ informUser('message', params);
596
+ }
597
+ }
598
+
599
+
600
+ /**
601
+ * Parses an event from the WebSocket to get
602
+ * the JSON payload that we require
603
+ *
604
+ * @param {MessageEvent} event The event from the WebSocket.onmessage handler.
605
+ **/
606
+ function parseWebSocketEvent(event) {
607
+ try {
608
+ var params = JSON.parse(event.data);
609
+
610
+ if (typeof params.data === 'string') {
611
+ try {
612
+ params.data = JSON.parse(params.data);
613
+ } catch (e) {
614
+ if (!(e instanceof SyntaxError)) {
615
+ throw e;
616
+ }
617
+ }
618
+ }
619
+
620
+ return params;
621
+ } catch (e) {
622
+ informUser('error', {type: 'MessageParseError', error: e, data: event.data});
623
+ }
624
+ }
625
+
626
+ function transitionToWaiting() {
627
+ self._machine.transition('waiting');
628
+ }
629
+
630
+ function ws_onError() {
631
+ informUser('error', {
632
+ type: 'WebSocketError'
633
+ });
634
+
635
+ // note: required? is the socket auto closed in the case of error?
636
+ self.socket.close();
637
+ self._machine.transition('impermanentlyClosing');
638
+ }
639
+
640
+ function informUser(eventName, data) {
641
+ self.emit(eventName, data);
642
+ }
643
+
644
+ function triggerStateChange(newState, data) {
645
+ // avoid emitting and changing the state
646
+ // multiple times when it's the same.
647
+ if (self.state === newState) return;
648
+
649
+ var prevState = self.state;
650
+
651
+ self.state = newState;
652
+
653
+ Pusher.debug('State changed', prevState + ' -> ' + newState);
654
+
655
+ self.emit('state_change', {previous: prevState, current: newState});
656
+ self.emit(newState, data);
657
+ }
658
+ };
659
+
660
+ Connection.prototype.connect = function() {
661
+ // no WebSockets
662
+ if (Pusher.Transport === null) {
663
+ this._machine.transition('failed');
664
+ }
665
+ // initial open of connection
666
+ else if(this._machine.is('initialized')) {
667
+ resetConnectionParameters(this);
668
+ this._machine.transition('waiting');
669
+ }
670
+ // user skipping connection wait
671
+ else if (this._machine.is('waiting')) {
672
+ this._machine.transition('connecting');
673
+ }
674
+ // user re-opening connection after closing it
675
+ else if(this._machine.is("permanentlyClosed")) {
676
+ this._machine.transition('waiting');
677
+ }
678
+ };
679
+
680
+ Connection.prototype.send = function(data) {
681
+ if (this._machine.is('connected')) {
682
+ this.socket.send(data);
683
+ return true;
684
+ } else {
685
+ return false;
686
+ }
687
+ };
688
+
689
+ Connection.prototype.disconnect = function() {
690
+ if (this._machine.is('waiting')) {
691
+ this._machine.transition('permanentlyClosed');
692
+ } else {
693
+ this._machine.transition('permanentlyClosing');
694
+ }
695
+ };
696
+
697
+ Pusher.Util.extend(Connection.prototype, Pusher.EventsDispatcher.prototype);
698
+
699
+ this.Pusher.Connection = Connection;
700
+ }).call(this);
701
+
702
+ Pusher.Channels = function() {
703
+ this.channels = {};
704
+ };
705
+
706
+ Pusher.Channels.prototype = {
707
+ add: function(channel_name, pusher) {
708
+ var existing_channel = this.find(channel_name);
709
+ if (!existing_channel) {
710
+ var channel = Pusher.Channel.factory(channel_name, pusher);
711
+ this.channels[channel_name] = channel;
712
+ return channel;
713
+ } else {
714
+ return existing_channel;
715
+ }
716
+ },
717
+
718
+ find: function(channel_name) {
719
+ return this.channels[channel_name];
720
+ },
721
+
722
+ remove: function(channel_name) {
723
+ delete this.channels[channel_name];
724
+ },
725
+
726
+ disconnect: function () {
727
+ for(var channel_name in this.channels){
728
+ this.channels[channel_name].disconnect()
729
+ }
730
+ }
731
+ };
732
+
733
+ Pusher.Channel = function(channel_name, pusher) {
734
+ Pusher.EventsDispatcher.call(this);
735
+
736
+ this.pusher = pusher;
737
+ this.name = channel_name;
738
+ this.subscribed = false;
739
+ };
740
+
741
+ Pusher.Channel.prototype = {
742
+ // inheritable constructor
743
+ init: function(){
744
+
745
+ },
746
+
747
+ disconnect: function(){
748
+
749
+ },
750
+
751
+ // Activate after successful subscription. Called on top-level pusher:subscription_succeeded
752
+ acknowledge_subscription: function(data){
753
+ this.subscribed = true;
754
+ },
755
+
756
+ is_private: function(){
757
+ return false;
758
+ },
759
+
760
+ is_presence: function(){
761
+ return false;
762
+ },
763
+
764
+ authorize: function(pusher, callback){
765
+ callback({}); // normal channels don't require auth
766
+ },
767
+
768
+ trigger: function(event, data) {
769
+ this.pusher.send_event(event, data, this.name);
770
+ return this;
771
+ }
772
+ };
773
+
774
+ Pusher.Util.extend(Pusher.Channel.prototype, Pusher.EventsDispatcher.prototype);
775
+
776
+
777
+
778
+ Pusher.auth_callbacks = {};
779
+
780
+ Pusher.authorizers = {
781
+ ajax: function(pusher, callback){
782
+ var self = this;
783
+ var xhr = window.XMLHttpRequest ?
784
+ new XMLHttpRequest() :
785
+ new ActiveXObject("Microsoft.XMLHTTP");
786
+ xhr.open("POST", Pusher.channel_auth_endpoint, true);
787
+ xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
788
+ xhr.onreadystatechange = function() {
789
+ if (xhr.readyState == 4) {
790
+ if (xhr.status == 200) {
791
+ var data = JSON.parse(xhr.responseText);
792
+ callback(data);
793
+ } else {
794
+ Pusher.debug("Couldn't get auth info from your webapp", status);
795
+ }
796
+ }
797
+ };
798
+ xhr.send('socket_id=' + encodeURIComponent(pusher.connection.socket_id) + '&channel_name=' + encodeURIComponent(self.name));
799
+ },
800
+ jsonp: function(pusher, callback){
801
+ var qstring = 'socket_id=' + encodeURIComponent(pusher.connection.socket_id) + '&channel_name=' + encodeURIComponent(this.name);
802
+ var script = document.createElement("script");
803
+ Pusher.auth_callbacks[this.name] = callback;
804
+ var callback_name = "Pusher.auth_callbacks['" + this.name + "']";
805
+ script.src = Pusher.channel_auth_endpoint+'?callback='+encodeURIComponent(callback_name)+'&'+qstring;
806
+ var head = document.getElementsByTagName("head")[0] || document.documentElement;
807
+ head.insertBefore( script, head.firstChild );
808
+ }
809
+ };
810
+
811
+ Pusher.Channel.PrivateChannel = {
812
+ is_private: function(){
813
+ return true;
814
+ },
815
+
816
+ authorize: function(pusher, callback){
817
+ Pusher.authorizers[Pusher.channel_auth_transport].scopedTo(this)(pusher, callback);
818
+ }
819
+ };
820
+
821
+ Pusher.Channel.PresenceChannel = {
822
+
823
+ init: function(){
824
+ this.bind('pusher_internal:subscription_succeeded', function(sub_data){
825
+ this.acknowledge_subscription(sub_data);
826
+ this.dispatch_with_all('pusher:subscription_succeeded', this.members);
827
+ }.scopedTo(this));
828
+
829
+ this.bind('pusher_internal:member_added', function(data){
830
+ var member = this.members.add(data.user_id, data.user_info);
831
+ this.dispatch_with_all('pusher:member_added', member);
832
+ }.scopedTo(this))
833
+
834
+ this.bind('pusher_internal:member_removed', function(data){
835
+ var member = this.members.remove(data.user_id);
836
+ if (member) {
837
+ this.dispatch_with_all('pusher:member_removed', member);
838
+ }
839
+ }.scopedTo(this))
840
+ },
841
+
842
+ disconnect: function(){
843
+ this.members.clear();
844
+ },
845
+
846
+ acknowledge_subscription: function(sub_data){
847
+ this.members._members_map = sub_data.presence.hash;
848
+ this.members.count = sub_data.presence.count;
849
+ this.subscribed = true;
850
+ },
851
+
852
+ is_presence: function(){
853
+ return true;
854
+ },
855
+
856
+ members: {
857
+ _members_map: {},
858
+ count: 0,
859
+
860
+ each: function(callback) {
861
+ for(var i in this._members_map) {
862
+ callback({
863
+ id: i,
864
+ info: this._members_map[i]
865
+ });
866
+ }
867
+ },
868
+
869
+ add: function(id, info) {
870
+ this._members_map[id] = info;
871
+ this.count++;
872
+ return this.get(id);
873
+ },
874
+
875
+ remove: function(user_id) {
876
+ var member = this.get(user_id);
877
+ if (member) {
878
+ delete this._members_map[user_id];
879
+ this.count--;
880
+ }
881
+ return member;
882
+ },
883
+
884
+ get: function(user_id) {
885
+ var user_info = this._members_map[user_id];
886
+ if (user_info) {
887
+ return {
888
+ id: user_id,
889
+ info: user_info
890
+ }
891
+ } else {
892
+ return null;
893
+ }
894
+ },
895
+
896
+ clear: function() {
897
+ this._members_map = {};
898
+ this.count = 0;
899
+ }
900
+ }
901
+ };
902
+
903
+ Pusher.Channel.factory = function(channel_name, pusher){
904
+ var channel = new Pusher.Channel(channel_name, pusher);
905
+ if(channel_name.indexOf(Pusher.Channel.private_prefix) === 0) {
906
+ Pusher.Util.extend(channel, Pusher.Channel.PrivateChannel);
907
+ } else if(channel_name.indexOf(Pusher.Channel.presence_prefix) === 0) {
908
+ Pusher.Util.extend(channel, Pusher.Channel.PrivateChannel);
909
+ Pusher.Util.extend(channel, Pusher.Channel.PresenceChannel);
910
+ };
911
+ channel.init();// inheritable constructor
912
+ return channel;
913
+ };
914
+
915
+ Pusher.Channel.private_prefix = "private-";
916
+ Pusher.Channel.presence_prefix = "presence-";
917
+
918
+ var _require = (function () {
919
+
920
+ var handleScriptLoaded;
921
+ if (document.addEventListener) {
922
+ handleScriptLoaded = function (elem, callback) {
923
+ elem.addEventListener('load', callback, false)
924
+ }
925
+ } else {
926
+ handleScriptLoaded = function(elem, callback) {
927
+ elem.attachEvent('onreadystatechange', function () {
928
+ if(elem.readyState == 'loaded' || elem.readyState == 'complete') callback()
929
+ })
930
+ }
931
+ }
932
+
933
+ return function (deps, callback) {
934
+ var dep_count = 0,
935
+ dep_length = deps.length;
936
+
937
+ function checkReady (callback) {
938
+ dep_count++;
939
+ if ( dep_length == dep_count ) {
940
+ // Opera needs the timeout for page initialization weirdness
941
+ setTimeout(callback, 0);
942
+ }
943
+ }
944
+
945
+ function addScript (src, callback) {
946
+ callback = callback || function(){}
947
+ var head = document.getElementsByTagName('head')[0];
948
+ var script = document.createElement('script');
949
+ script.setAttribute('src', src);
950
+ script.setAttribute("type","text/javascript");
951
+ script.setAttribute('async', true);
952
+
953
+ handleScriptLoaded(script, function () {
954
+ checkReady(callback);
955
+ });
956
+
957
+ head.appendChild(script);
958
+ }
959
+
960
+ for(var i = 0; i < dep_length; i++) {
961
+ addScript(deps[i], callback);
962
+ }
963
+ }
964
+ })();
965
+
966
+ ;(function() {
967
+ var cdn = (document.location.protocol == 'http:') ? Pusher.cdn_http : Pusher.cdn_https;
968
+ var root = cdn + Pusher.VERSION;
969
+
970
+ var deps = [];
971
+ if (typeof window['JSON'] === undefined) {
972
+ deps.push(root + '/json2.js');
973
+ }
974
+ if (typeof window['WebSocket'] === 'undefined') {
975
+ // We manually initialize web-socket-js to iron out cross browser issues
976
+ window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION = true;
977
+ deps.push(root + '/flashfallback.js');
978
+ }
979
+
980
+ var initialize = function() {
981
+ if (typeof window['WebSocket'] === 'undefined' && typeof window['MozWebSocket'] === 'undefined') {
982
+ return function() {
983
+ // This runs after flashfallback.js has loaded
984
+ if (typeof window['WebSocket'] !== 'undefined') {
985
+ // window['WebSocket'] is a flash emulation of WebSocket
986
+ Pusher.Transport = window['WebSocket'];
987
+ Pusher.TransportType = 'flash';
988
+
989
+ window.WEB_SOCKET_SWF_LOCATION = root + "/WebSocketMain.swf";
990
+ WebSocket.__addTask(function() {
991
+ Pusher.ready();
992
+ })
993
+ WebSocket.__initialize();
994
+ } else {
995
+ // Flash must not be installed
996
+ Pusher.Transport = null;
997
+ Pusher.TransportType = 'none';
998
+ Pusher.ready();
999
+ }
1000
+ }
1001
+ } else {
1002
+ return function() {
1003
+ // This is because Mozilla have decided to
1004
+ // prefix the WebSocket constructor with "Moz".
1005
+ if (typeof window['MozWebSocket'] !== 'undefined') {
1006
+ Pusher.Transport = window['MozWebSocket'];
1007
+ } else {
1008
+ Pusher.Transport = window['WebSocket'];
1009
+ }
1010
+ // We have some form of a native websocket,
1011
+ // even if the constructor is prefixed:
1012
+ Pusher.TransportType = 'native';
1013
+
1014
+ // Initialise Pusher.
1015
+ Pusher.ready();
1016
+ }
1017
+ }
1018
+ }();
1019
+
1020
+ var ondocumentbody = function(callback) {
1021
+ var load_body = function() {
1022
+ document.body ? callback() : setTimeout(load_body, 0);
1023
+ }
1024
+ load_body();
1025
+ };
1026
+
1027
+ var initializeOnDocumentBody = function() {
1028
+ ondocumentbody(initialize);
1029
+ }
1030
+
1031
+ if (deps.length > 0) {
1032
+ _require(deps, initializeOnDocumentBody);
1033
+ } else {
1034
+ initializeOnDocumentBody();
1035
+ }
1036
+ })();
1037
+
metadata ADDED
@@ -0,0 +1,48 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pusher_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - David Grandinetti
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-07-27 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: Adds pusher.js to the asset pipeline
15
+ email: dave@wegoto12.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ./pusher_rails-0.1.0.gem
21
+ - ./pusher_rails.gemspec
22
+ - ./README.md
23
+ - ./vendor/assets/javascripts/pusher.js
24
+ homepage: https://github.com/dbgrandi/pusher_rails
25
+ licenses: []
26
+ post_install_message:
27
+ rdoc_options: []
28
+ require_paths:
29
+ - lib
30
+ required_ruby_version: !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ! '>='
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ! '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ requirements: []
43
+ rubyforge_project:
44
+ rubygems_version: 1.8.5
45
+ signing_key:
46
+ specification_version: 3
47
+ summary: Pusher integration for Rails 3.1+
48
+ test_files: []