pusher_rails 0.1.0

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