websocket-rails 0.6.2 → 0.7.0

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