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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +32 -0
- data/Gemfile +2 -1
- data/README.md +29 -34
- data/lib/assets/javascripts/websocket_rails/abstract_connection.js.coffee +45 -0
- data/lib/assets/javascripts/websocket_rails/channel.js.coffee +34 -17
- data/lib/assets/javascripts/websocket_rails/event.js.coffee +13 -11
- data/lib/assets/javascripts/websocket_rails/http_connection.js.coffee +44 -45
- data/lib/assets/javascripts/websocket_rails/main.js +1 -0
- data/lib/assets/javascripts/websocket_rails/websocket_connection.js.coffee +20 -34
- data/lib/assets/javascripts/websocket_rails/websocket_rails.js.coffee +60 -15
- data/lib/generators/websocket_rails/install/templates/websocket_rails.rb +15 -0
- data/lib/rails/config/routes.rb +1 -1
- data/lib/rails/tasks/websocket_rails.tasks +6 -2
- data/lib/websocket_rails/channel.rb +28 -2
- data/lib/websocket_rails/channel_manager.rb +16 -0
- data/lib/websocket_rails/configuration.rb +26 -1
- data/lib/websocket_rails/connection_adapters/http.rb +7 -0
- data/lib/websocket_rails/connection_adapters/web_socket.rb +3 -1
- data/lib/websocket_rails/connection_manager.rb +1 -1
- data/lib/websocket_rails/controller_factory.rb +1 -1
- data/lib/websocket_rails/event.rb +9 -2
- data/lib/websocket_rails/logging.rb +0 -1
- data/lib/websocket_rails/synchronization.rb +11 -7
- data/lib/websocket_rails/version.rb +1 -1
- data/spec/javascripts/generated/assets/abstract_connection.js +71 -0
- data/spec/javascripts/generated/assets/channel.js +58 -34
- data/spec/javascripts/generated/assets/event.js +12 -16
- data/spec/javascripts/generated/assets/http_connection.js +67 -65
- data/spec/javascripts/generated/assets/websocket_connection.js +36 -51
- data/spec/javascripts/generated/assets/websocket_rails.js +68 -21
- data/spec/javascripts/generated/specs/channel_spec.js +102 -19
- data/spec/javascripts/generated/specs/helpers.js +17 -0
- data/spec/javascripts/generated/specs/websocket_connection_spec.js +72 -19
- data/spec/javascripts/generated/specs/websocket_rails_spec.js +146 -47
- data/spec/javascripts/support/jasmine.yml +10 -2
- data/spec/javascripts/support/jasmine_helper.rb +38 -0
- data/spec/javascripts/websocket_rails/channel_spec.coffee +66 -12
- data/spec/javascripts/websocket_rails/event_spec.coffee +7 -7
- data/spec/javascripts/websocket_rails/helpers.coffee +6 -0
- data/spec/javascripts/websocket_rails/websocket_connection_spec.coffee +53 -15
- data/spec/javascripts/websocket_rails/websocket_rails_spec.coffee +108 -25
- data/spec/unit/base_controller_spec.rb +41 -0
- data/spec/unit/channel_manager_spec.rb +21 -0
- data/spec/unit/channel_spec.rb +43 -3
- data/spec/unit/connection_adapters/http_spec.rb +24 -3
- data/spec/unit/connection_adapters_spec.rb +2 -2
- data/spec/unit/connection_manager_spec.rb +1 -1
- data/spec/unit/event_spec.rb +25 -1
- data/spec/unit/logging_spec.rb +1 -1
- metadata +57 -67
- 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
|
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
|
-
|
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 === '
|
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 =
|
28
|
-
|
29
|
-
|
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.
|
33
|
-
|
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.
|
61
|
-
|
62
|
-
|
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.
|
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]
|
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.
|
83
|
-
this._conn.flush_queue(
|
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.
|
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
|
-
|
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
|
-
|
5
|
-
|
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
|
-
|
10
|
+
};
|
11
|
+
|
12
|
+
WebSocketRailsStub.prototype.dispatch = function() {
|
9
13
|
return true;
|
10
|
-
}
|
11
|
-
|
14
|
+
};
|
15
|
+
|
16
|
+
WebSocketRailsStub.prototype.trigger_event = function(event) {
|
12
17
|
return true;
|
13
|
-
}
|
14
|
-
|
15
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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).
|
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);
|