websocket-rails 0.6.2 → 0.7.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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +32 -0
  3. data/Gemfile +2 -1
  4. data/README.md +29 -34
  5. data/lib/assets/javascripts/websocket_rails/abstract_connection.js.coffee +45 -0
  6. data/lib/assets/javascripts/websocket_rails/channel.js.coffee +34 -17
  7. data/lib/assets/javascripts/websocket_rails/event.js.coffee +13 -11
  8. data/lib/assets/javascripts/websocket_rails/http_connection.js.coffee +44 -45
  9. data/lib/assets/javascripts/websocket_rails/main.js +1 -0
  10. data/lib/assets/javascripts/websocket_rails/websocket_connection.js.coffee +20 -34
  11. data/lib/assets/javascripts/websocket_rails/websocket_rails.js.coffee +60 -15
  12. data/lib/generators/websocket_rails/install/templates/websocket_rails.rb +15 -0
  13. data/lib/rails/config/routes.rb +1 -1
  14. data/lib/rails/tasks/websocket_rails.tasks +6 -2
  15. data/lib/websocket_rails/channel.rb +28 -2
  16. data/lib/websocket_rails/channel_manager.rb +16 -0
  17. data/lib/websocket_rails/configuration.rb +26 -1
  18. data/lib/websocket_rails/connection_adapters/http.rb +7 -0
  19. data/lib/websocket_rails/connection_adapters/web_socket.rb +3 -1
  20. data/lib/websocket_rails/connection_manager.rb +1 -1
  21. data/lib/websocket_rails/controller_factory.rb +1 -1
  22. data/lib/websocket_rails/event.rb +9 -2
  23. data/lib/websocket_rails/logging.rb +0 -1
  24. data/lib/websocket_rails/synchronization.rb +11 -7
  25. data/lib/websocket_rails/version.rb +1 -1
  26. data/spec/javascripts/generated/assets/abstract_connection.js +71 -0
  27. data/spec/javascripts/generated/assets/channel.js +58 -34
  28. data/spec/javascripts/generated/assets/event.js +12 -16
  29. data/spec/javascripts/generated/assets/http_connection.js +67 -65
  30. data/spec/javascripts/generated/assets/websocket_connection.js +36 -51
  31. data/spec/javascripts/generated/assets/websocket_rails.js +68 -21
  32. data/spec/javascripts/generated/specs/channel_spec.js +102 -19
  33. data/spec/javascripts/generated/specs/helpers.js +17 -0
  34. data/spec/javascripts/generated/specs/websocket_connection_spec.js +72 -19
  35. data/spec/javascripts/generated/specs/websocket_rails_spec.js +146 -47
  36. data/spec/javascripts/support/jasmine.yml +10 -2
  37. data/spec/javascripts/support/jasmine_helper.rb +38 -0
  38. data/spec/javascripts/websocket_rails/channel_spec.coffee +66 -12
  39. data/spec/javascripts/websocket_rails/event_spec.coffee +7 -7
  40. data/spec/javascripts/websocket_rails/helpers.coffee +6 -0
  41. data/spec/javascripts/websocket_rails/websocket_connection_spec.coffee +53 -15
  42. data/spec/javascripts/websocket_rails/websocket_rails_spec.coffee +108 -25
  43. data/spec/unit/base_controller_spec.rb +41 -0
  44. data/spec/unit/channel_manager_spec.rb +21 -0
  45. data/spec/unit/channel_spec.rb +43 -3
  46. data/spec/unit/connection_adapters/http_spec.rb +24 -3
  47. data/spec/unit/connection_adapters_spec.rb +2 -2
  48. data/spec/unit/connection_manager_spec.rb +1 -1
  49. data/spec/unit/event_spec.rb +25 -1
  50. data/spec/unit/logging_spec.rb +1 -1
  51. metadata +57 -67
  52. data/spec/javascripts/support/jasmine_config.rb +0 -63
@@ -1,74 +1,59 @@
1
+
1
2
  /*
2
3
  WebSocket Interface for the WebSocketRails client.
3
- */
4
-
4
+ */
5
5
 
6
6
  (function() {
7
- var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
7
+ var __hasProp = {}.hasOwnProperty,
8
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
9
+
10
+ WebSocketRails.WebSocketConnection = (function(_super) {
11
+ __extends(WebSocketConnection, _super);
12
+
13
+ WebSocketConnection.prototype.connection_type = 'websocket';
8
14
 
9
- WebSocketRails.WebSocketConnection = (function() {
10
15
  function WebSocketConnection(url, dispatcher) {
11
16
  this.url = url;
12
17
  this.dispatcher = dispatcher;
13
- this.flush_queue = __bind(this.flush_queue, this);
14
- this.on_error = __bind(this.on_error, this);
15
- this.on_close = __bind(this.on_close, this);
16
- this.on_message = __bind(this.on_message, this);
17
- this.trigger = __bind(this.trigger, this);
18
+ WebSocketConnection.__super__.constructor.apply(this, arguments);
18
19
  if (this.url.match(/^wss?:\/\//)) {
19
20
  console.log("WARNING: Using connection urls with protocol specified is depricated");
20
- } else if (window.location.protocol === 'http:') {
21
- this.url = "ws://" + this.url;
22
- } else {
21
+ } else if (window.location.protocol === 'https:') {
23
22
  this.url = "wss://" + this.url;
23
+ } else {
24
+ this.url = "ws://" + this.url;
24
25
  }
25
- this.message_queue = [];
26
26
  this._conn = new WebSocket(this.url);
27
- this._conn.onmessage = this.on_message;
28
- this._conn.onclose = this.on_close;
29
- this._conn.onerror = this.on_error;
27
+ this._conn.onmessage = (function(_this) {
28
+ return function(event) {
29
+ var event_data;
30
+ event_data = JSON.parse(event.data);
31
+ return _this.on_message(event_data);
32
+ };
33
+ })(this);
34
+ this._conn.onclose = (function(_this) {
35
+ return function(event) {
36
+ return _this.on_close(event);
37
+ };
38
+ })(this);
39
+ this._conn.onerror = (function(_this) {
40
+ return function(event) {
41
+ return _this.on_error(event);
42
+ };
43
+ })(this);
30
44
  }
31
45
 
32
- WebSocketConnection.prototype.trigger = function(event) {
33
- if (this.dispatcher.state !== 'connected') {
34
- return this.message_queue.push(event);
35
- } else {
36
- return this._conn.send(event.serialize());
37
- }
38
- };
39
-
40
- WebSocketConnection.prototype.on_message = function(event) {
41
- var data;
42
- data = JSON.parse(event.data);
43
- return this.dispatcher.new_message(data);
44
- };
45
-
46
- WebSocketConnection.prototype.on_close = function(event) {
47
- var close_event;
48
- close_event = new WebSocketRails.Event(['connection_closed', event]);
49
- this.dispatcher.state = 'disconnected';
50
- return this.dispatcher.dispatch(close_event);
51
- };
52
-
53
- WebSocketConnection.prototype.on_error = function(event) {
54
- var error_event;
55
- error_event = new WebSocketRails.Event(['connection_error', event]);
56
- this.dispatcher.state = 'disconnected';
57
- return this.dispatcher.dispatch(error_event);
46
+ WebSocketConnection.prototype.close = function() {
47
+ return this._conn.close();
58
48
  };
59
49
 
60
- WebSocketConnection.prototype.flush_queue = function() {
61
- var event, _i, _len, _ref;
62
- _ref = this.message_queue;
63
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
64
- event = _ref[_i];
65
- this._conn.send(event.serialize());
66
- }
67
- return this.message_queue = [];
50
+ WebSocketConnection.prototype.send_event = function(event) {
51
+ WebSocketConnection.__super__.send_event.apply(this, arguments);
52
+ return this._conn.send(event.serialize());
68
53
  };
69
54
 
70
55
  return WebSocketConnection;
71
56
 
72
- })();
57
+ })(WebSocketRails.AbstractConnection);
73
58
 
74
59
  }).call(this);
@@ -1,3 +1,4 @@
1
+
1
2
  /*
2
3
  WebsocketRails JavaScript Client
3
4
 
@@ -15,8 +16,7 @@ Listening for new events from the server
15
16
  dispatcher.bind('event_name', function(data) {
16
17
  console.log(data.user_name);
17
18
  });
18
- */
19
-
19
+ */
20
20
 
21
21
  (function() {
22
22
  var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
@@ -38,17 +38,46 @@ Listening for new events from the server
38
38
  this.bind = __bind(this.bind, this);
39
39
  this.connection_established = __bind(this.connection_established, this);
40
40
  this.new_message = __bind(this.new_message, this);
41
- this.state = 'connecting';
41
+ this.reconnect = __bind(this.reconnect, this);
42
42
  this.callbacks = {};
43
43
  this.channels = {};
44
44
  this.queue = {};
45
+ this.connect();
46
+ }
47
+
48
+ WebSocketRails.prototype.connect = function() {
49
+ this.state = 'connecting';
45
50
  if (!(this.supports_websockets() && this.use_websockets)) {
46
- this._conn = new WebSocketRails.HttpConnection(url, this);
51
+ this._conn = new WebSocketRails.HttpConnection(this.url, this);
47
52
  } else {
48
- this._conn = new WebSocketRails.WebSocketConnection(url, this);
53
+ this._conn = new WebSocketRails.WebSocketConnection(this.url, this);
49
54
  }
50
- this._conn.new_message = this.new_message;
51
- }
55
+ return this._conn.new_message = this.new_message;
56
+ };
57
+
58
+ WebSocketRails.prototype.disconnect = function() {
59
+ if (this._conn) {
60
+ this._conn.close();
61
+ delete this._conn._conn;
62
+ delete this._conn;
63
+ }
64
+ return this.state = 'disconnected';
65
+ };
66
+
67
+ WebSocketRails.prototype.reconnect = function() {
68
+ var event, id, old_connection_id, _ref, _ref1;
69
+ old_connection_id = (_ref = this._conn) != null ? _ref.connection_id : void 0;
70
+ this.disconnect();
71
+ this.connect();
72
+ _ref1 = this.queue;
73
+ for (id in _ref1) {
74
+ event = _ref1[id];
75
+ if (event.connection_id === old_connection_id && !event.is_result()) {
76
+ this.trigger_event(event);
77
+ }
78
+ }
79
+ return this.reconnect_channels();
80
+ };
52
81
 
53
82
  WebSocketRails.prototype.new_message = function(data) {
54
83
  var event, socket_message, _i, _len, _ref, _results;
@@ -60,7 +89,7 @@ Listening for new events from the server
60
89
  if ((_ref = this.queue[event.id]) != null) {
61
90
  _ref.run_callbacks(event.success, event.data);
62
91
  }
63
- this.queue[event.id] = null;
92
+ delete this.queue[event.id];
64
93
  } else if (event.is_channel()) {
65
94
  this.dispatch_channel(event);
66
95
  } else if (event.is_ping()) {
@@ -79,8 +108,8 @@ Listening for new events from the server
79
108
 
80
109
  WebSocketRails.prototype.connection_established = function(data) {
81
110
  this.state = 'connected';
82
- this.connection_id = data.connection_id;
83
- this._conn.flush_queue(data.connection_id);
111
+ this._conn.setConnectionId(data.connection_id);
112
+ this._conn.flush_queue();
84
113
  if (this.on_open != null) {
85
114
  return this.on_open(data);
86
115
  }
@@ -95,10 +124,9 @@ Listening for new events from the server
95
124
  };
96
125
 
97
126
  WebSocketRails.prototype.trigger = function(event_name, data, success_callback, failure_callback) {
98
- var event;
99
- event = new WebSocketRails.Event([event_name, data, this.connection_id], success_callback, failure_callback);
100
- this.queue[event.id] = event;
101
- return this._conn.trigger(event);
127
+ var event, _ref;
128
+ event = new WebSocketRails.Event([event_name, data, (_ref = this._conn) != null ? _ref.connection_id : void 0], success_callback, failure_callback);
129
+ return this.trigger_event(event);
102
130
  };
103
131
 
104
132
  WebSocketRails.prototype.trigger_event = function(event) {
@@ -106,7 +134,10 @@ Listening for new events from the server
106
134
  if ((_base = this.queue)[_name = event.id] == null) {
107
135
  _base[_name] = event;
108
136
  }
109
- return this._conn.trigger(event);
137
+ if (this._conn) {
138
+ this._conn.trigger(event);
139
+ }
140
+ return event;
110
141
  };
111
142
 
112
143
  WebSocketRails.prototype.dispatch = function(event) {
@@ -123,10 +154,10 @@ Listening for new events from the server
123
154
  return _results;
124
155
  };
125
156
 
126
- WebSocketRails.prototype.subscribe = function(channel_name) {
157
+ WebSocketRails.prototype.subscribe = function(channel_name, success_callback, failure_callback) {
127
158
  var channel;
128
159
  if (this.channels[channel_name] == null) {
129
- channel = new WebSocketRails.Channel(channel_name, this);
160
+ channel = new WebSocketRails.Channel(channel_name, this, false, success_callback, failure_callback);
130
161
  this.channels[channel_name] = channel;
131
162
  return channel;
132
163
  } else {
@@ -134,10 +165,10 @@ Listening for new events from the server
134
165
  }
135
166
  };
136
167
 
137
- WebSocketRails.prototype.subscribe_private = function(channel_name) {
168
+ WebSocketRails.prototype.subscribe_private = function(channel_name, success_callback, failure_callback) {
138
169
  var channel;
139
170
  if (this.channels[channel_name] == null) {
140
- channel = new WebSocketRails.Channel(channel_name, this, true);
171
+ channel = new WebSocketRails.Channel(channel_name, this, true, success_callback, failure_callback);
141
172
  this.channels[channel_name] = channel;
142
173
  return channel;
143
174
  } else {
@@ -165,8 +196,8 @@ Listening for new events from the server
165
196
  };
166
197
 
167
198
  WebSocketRails.prototype.pong = function() {
168
- var pong;
169
- pong = new WebSocketRails.Event(['websocket_rails.pong', {}, this.connection_id]);
199
+ var pong, _ref;
200
+ pong = new WebSocketRails.Event(['websocket_rails.pong', {}, (_ref = this._conn) != null ? _ref.connection_id : void 0]);
170
201
  return this._conn.trigger(pong);
171
202
  };
172
203
 
@@ -174,6 +205,22 @@ Listening for new events from the server
174
205
  return this.state !== 'connected';
175
206
  };
176
207
 
208
+ WebSocketRails.prototype.reconnect_channels = function() {
209
+ var callbacks, channel, name, _ref, _results;
210
+ _ref = this.channels;
211
+ _results = [];
212
+ for (name in _ref) {
213
+ channel = _ref[name];
214
+ callbacks = channel._callbacks;
215
+ channel.destroy();
216
+ delete this.channels[name];
217
+ channel = channel.is_private ? this.subscribe_private(name) : this.subscribe(name);
218
+ channel._callbacks = callbacks;
219
+ _results.push(channel);
220
+ }
221
+ return _results;
222
+ };
223
+
177
224
  return WebSocketRails;
178
225
 
179
226
  })();
@@ -1,25 +1,95 @@
1
1
  (function() {
2
2
  describe('WebSocketRails.Channel:', function() {
3
3
  beforeEach(function() {
4
- this.dispatcher = {
5
- new_message: function() {
4
+ var WebSocketRailsStub;
5
+ this.dispatcher = new (WebSocketRailsStub = (function() {
6
+ function WebSocketRailsStub() {}
7
+
8
+ WebSocketRailsStub.prototype.new_message = function() {
6
9
  return true;
7
- },
8
- dispatch: function() {
10
+ };
11
+
12
+ WebSocketRailsStub.prototype.dispatch = function() {
9
13
  return true;
10
- },
11
- trigger_event: function(event) {
14
+ };
15
+
16
+ WebSocketRailsStub.prototype.trigger_event = function(event) {
12
17
  return true;
13
- },
14
- state: 'connected',
15
- connection_id: 12345
16
- };
18
+ };
19
+
20
+ WebSocketRailsStub.prototype.state = 'connected';
21
+
22
+ WebSocketRailsStub.prototype._conn = {
23
+ connection_id: 12345
24
+ };
25
+
26
+ return WebSocketRailsStub;
27
+
28
+ })());
17
29
  this.channel = new WebSocketRails.Channel('public', this.dispatcher);
18
30
  return sinon.spy(this.dispatcher, 'trigger_event');
19
31
  });
20
32
  afterEach(function() {
21
33
  return this.dispatcher.trigger_event.restore();
22
34
  });
35
+ describe('.bind', function() {
36
+ return it('should add a function to the callbacks collection', function() {
37
+ var test_func;
38
+ test_func = function() {};
39
+ this.channel.bind('event_name', test_func);
40
+ expect(this.channel._callbacks['event_name'].length).toBe(1);
41
+ return expect(this.channel._callbacks['event_name']).toContain(test_func);
42
+ });
43
+ });
44
+ describe('.trigger', function() {
45
+ describe('before the channel token is set', function() {
46
+ return it('queues the events', function() {
47
+ var queue;
48
+ this.channel.trigger('someEvent', 'someData');
49
+ queue = this.channel._queue;
50
+ expect(queue[0].name).toEqual('someEvent');
51
+ return expect(queue[0].data).toEqual('someData');
52
+ });
53
+ });
54
+ return describe('when channel token is set', function() {
55
+ return it('adds token to event metadata and dispatches event', function() {
56
+ this.channel._token = 'valid token';
57
+ this.channel.trigger('someEvent', 'someData');
58
+ return expect(this.dispatcher.trigger_event.calledWith([
59
+ 'someEvent', {
60
+ token: 'valid token',
61
+ data: 'someData'
62
+ }
63
+ ]));
64
+ });
65
+ });
66
+ });
67
+ describe('.destroy', function() {
68
+ it('should destroy all callbacks', function() {
69
+ var event_callback;
70
+ event_callback = function() {
71
+ return true;
72
+ };
73
+ this.channel.bind('new_message', this.event_callback);
74
+ this.channel.destroy();
75
+ return expect(this.channel._callbacks).toEqual({});
76
+ });
77
+ describe('when this channel\'s connection is still active', function() {
78
+ return it('should send unsubscribe event', function() {
79
+ this.channel.destroy();
80
+ return expect(this.dispatcher.trigger_event.args[0][0].name).toEqual('websocket_rails.unsubscribe');
81
+ });
82
+ });
83
+ return describe('when this channel\'s connection is no more active', function() {
84
+ beforeEach(function() {
85
+ return this.dispatcher._conn.connection_id++;
86
+ });
87
+ return it('should not send unsubscribe event', function() {
88
+ this.channel.destroy();
89
+ return expect(this.dispatcher.trigger_event.notCalled).toEqual(true);
90
+ });
91
+ });
92
+ });
23
93
  describe('public channels', function() {
24
94
  beforeEach(function() {
25
95
  this.channel = new WebSocketRails.Channel('forchan', this.dispatcher, false);
@@ -35,17 +105,30 @@
35
105
  expect(this.channel._callbacks).toBeDefined();
36
106
  return expect(this.channel._callbacks).toEqual({});
37
107
  });
38
- it('should be public', function() {
108
+ return it('should be public', function() {
39
109
  return expect(this.channel.is_private).toBeFalsy;
40
110
  });
41
- return describe('.bind', function() {
42
- return it('should add a function to the callbacks collection', function() {
43
- var test_func;
44
- test_func = function() {};
45
- this.channel.bind('event_name', test_func);
46
- expect(this.channel._callbacks['event_name'].length).toBe(1);
47
- return expect(this.channel._callbacks['event_name']).toContain(test_func);
111
+ });
112
+ describe('channel tokens', function() {
113
+ it('should set token when event_name is websocket_rails.channel_token', function() {
114
+ this.channel.dispatch('websocket_rails.channel_token', {
115
+ token: 'abc123'
116
+ });
117
+ return expect(this.channel._token).toEqual('abc123');
118
+ });
119
+ it("should refresh channel's connection_id after channel_token has been received", function() {
120
+ this.channel.connection_id = null;
121
+ this.channel.dispatch('websocket_rails.channel_token', {
122
+ token: 'abc123'
123
+ });
124
+ return expect(this.channel.connection_id).toEqual(this.dispatcher._conn.connection_id);
125
+ });
126
+ return it('should flush the event queue after setting token', function() {
127
+ this.channel.trigger('someEvent', 'someData');
128
+ this.channel.dispatch('websocket_rails.channel_token', {
129
+ token: 'abc123'
48
130
  });
131
+ return expect(this.channel._queue.length).toEqual(0);
49
132
  });
50
133
  });
51
134
  return describe('private channels', function() {
@@ -57,7 +140,7 @@
57
140
  return expect(this.event.name).toEqual('websocket_rails.subscribe_private');
58
141
  });
59
142
  return it('should be private', function() {
60
- return expect(this.channel.is_private).toBe(true);
143
+ return expect(this.channel.is_private).toBeTruthy;
61
144
  });
62
145
  });
63
146
  });
@@ -0,0 +1,17 @@
1
+ (function() {
2
+ window.helpers = {
3
+ startConnection: function(dispatcher, connection_id) {
4
+ var message;
5
+ if (connection_id == null) {
6
+ connection_id = 1;
7
+ }
8
+ message = {
9
+ data: {
10
+ connection_id: connection_id
11
+ }
12
+ };
13
+ return dispatcher.new_message([['client_connected', message]]);
14
+ }
15
+ };
16
+
17
+ }).call(this);