actioncable 5.2.6.3 → 6.1.5
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +55 -39
- data/MIT-LICENSE +1 -1
- data/README.md +3 -546
- data/app/assets/javascripts/action_cable.js +574 -0
- data/lib/action_cable/channel/base.rb +10 -4
- data/lib/action_cable/channel/broadcasting.rb +18 -8
- data/lib/action_cable/channel/naming.rb +1 -1
- data/lib/action_cable/channel/streams.rb +30 -4
- data/lib/action_cable/channel/test_case.rb +310 -0
- data/lib/action_cable/channel.rb +1 -0
- data/lib/action_cable/connection/authorization.rb +1 -1
- data/lib/action_cable/connection/base.rb +13 -7
- data/lib/action_cable/connection/message_buffer.rb +1 -4
- data/lib/action_cable/connection/stream.rb +4 -2
- data/lib/action_cable/connection/subscriptions.rb +2 -5
- data/lib/action_cable/connection/test_case.rb +234 -0
- data/lib/action_cable/connection/web_socket.rb +1 -3
- data/lib/action_cable/connection.rb +1 -0
- data/lib/action_cable/engine.rb +1 -1
- data/lib/action_cable/gem_version.rb +4 -4
- data/lib/action_cable/helpers/action_cable_helper.rb +3 -3
- data/lib/action_cable/remote_connections.rb +1 -1
- data/lib/action_cable/server/base.rb +9 -4
- data/lib/action_cable/server/broadcasting.rb +1 -1
- data/lib/action_cable/server/worker.rb +6 -8
- data/lib/action_cable/server.rb +0 -1
- data/lib/action_cable/subscription_adapter/base.rb +4 -0
- data/lib/action_cable/subscription_adapter/postgresql.rb +28 -9
- data/lib/action_cable/subscription_adapter/redis.rb +4 -2
- data/lib/action_cable/subscription_adapter/test.rb +40 -0
- data/lib/action_cable/subscription_adapter.rb +1 -0
- data/lib/action_cable/test_case.rb +11 -0
- data/lib/action_cable/test_helper.rb +133 -0
- data/lib/action_cable.rb +15 -7
- data/lib/rails/generators/channel/USAGE +5 -6
- data/lib/rails/generators/channel/channel_generator.rb +6 -3
- data/lib/rails/generators/channel/templates/{assets → javascript}/channel.js.tt +6 -4
- data/lib/rails/generators/channel/templates/javascript/consumer.js.tt +6 -0
- data/lib/rails/generators/channel/templates/javascript/index.js.tt +5 -0
- data/lib/rails/generators/test_unit/channel_generator.rb +20 -0
- data/lib/rails/generators/test_unit/templates/channel_test.rb.tt +8 -0
- metadata +41 -16
- data/lib/assets/compiled/action_cable.js +0 -601
- data/lib/rails/generators/channel/templates/assets/cable.js.tt +0 -13
- data/lib/rails/generators/channel/templates/assets/channel.coffee.tt +0 -14
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
(function(global, factory) {
|
|
2
|
+
typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define([ "exports" ], factory) : factory(global.ActionCable = {});
|
|
3
|
+
})(this, function(exports) {
|
|
4
|
+
"use strict";
|
|
5
|
+
var adapters = {
|
|
6
|
+
logger: self.console,
|
|
7
|
+
WebSocket: self.WebSocket
|
|
8
|
+
};
|
|
9
|
+
var logger = {
|
|
10
|
+
log: function log() {
|
|
11
|
+
if (this.enabled) {
|
|
12
|
+
var _adapters$logger;
|
|
13
|
+
for (var _len = arguments.length, messages = Array(_len), _key = 0; _key < _len; _key++) {
|
|
14
|
+
messages[_key] = arguments[_key];
|
|
15
|
+
}
|
|
16
|
+
messages.push(Date.now());
|
|
17
|
+
(_adapters$logger = adapters.logger).log.apply(_adapters$logger, [ "[ActionCable]" ].concat(messages));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function(obj) {
|
|
22
|
+
return typeof obj;
|
|
23
|
+
} : function(obj) {
|
|
24
|
+
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
|
|
25
|
+
};
|
|
26
|
+
var classCallCheck = function(instance, Constructor) {
|
|
27
|
+
if (!(instance instanceof Constructor)) {
|
|
28
|
+
throw new TypeError("Cannot call a class as a function");
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var createClass = function() {
|
|
32
|
+
function defineProperties(target, props) {
|
|
33
|
+
for (var i = 0; i < props.length; i++) {
|
|
34
|
+
var descriptor = props[i];
|
|
35
|
+
descriptor.enumerable = descriptor.enumerable || false;
|
|
36
|
+
descriptor.configurable = true;
|
|
37
|
+
if ("value" in descriptor) descriptor.writable = true;
|
|
38
|
+
Object.defineProperty(target, descriptor.key, descriptor);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return function(Constructor, protoProps, staticProps) {
|
|
42
|
+
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
|
43
|
+
if (staticProps) defineProperties(Constructor, staticProps);
|
|
44
|
+
return Constructor;
|
|
45
|
+
};
|
|
46
|
+
}();
|
|
47
|
+
var now = function now() {
|
|
48
|
+
return new Date().getTime();
|
|
49
|
+
};
|
|
50
|
+
var secondsSince = function secondsSince(time) {
|
|
51
|
+
return (now() - time) / 1e3;
|
|
52
|
+
};
|
|
53
|
+
var clamp = function clamp(number, min, max) {
|
|
54
|
+
return Math.max(min, Math.min(max, number));
|
|
55
|
+
};
|
|
56
|
+
var ConnectionMonitor = function() {
|
|
57
|
+
function ConnectionMonitor(connection) {
|
|
58
|
+
classCallCheck(this, ConnectionMonitor);
|
|
59
|
+
this.visibilityDidChange = this.visibilityDidChange.bind(this);
|
|
60
|
+
this.connection = connection;
|
|
61
|
+
this.reconnectAttempts = 0;
|
|
62
|
+
}
|
|
63
|
+
ConnectionMonitor.prototype.start = function start() {
|
|
64
|
+
if (!this.isRunning()) {
|
|
65
|
+
this.startedAt = now();
|
|
66
|
+
delete this.stoppedAt;
|
|
67
|
+
this.startPolling();
|
|
68
|
+
addEventListener("visibilitychange", this.visibilityDidChange);
|
|
69
|
+
logger.log("ConnectionMonitor started. pollInterval = " + this.getPollInterval() + " ms");
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
ConnectionMonitor.prototype.stop = function stop() {
|
|
73
|
+
if (this.isRunning()) {
|
|
74
|
+
this.stoppedAt = now();
|
|
75
|
+
this.stopPolling();
|
|
76
|
+
removeEventListener("visibilitychange", this.visibilityDidChange);
|
|
77
|
+
logger.log("ConnectionMonitor stopped");
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
ConnectionMonitor.prototype.isRunning = function isRunning() {
|
|
81
|
+
return this.startedAt && !this.stoppedAt;
|
|
82
|
+
};
|
|
83
|
+
ConnectionMonitor.prototype.recordPing = function recordPing() {
|
|
84
|
+
this.pingedAt = now();
|
|
85
|
+
};
|
|
86
|
+
ConnectionMonitor.prototype.recordConnect = function recordConnect() {
|
|
87
|
+
this.reconnectAttempts = 0;
|
|
88
|
+
this.recordPing();
|
|
89
|
+
delete this.disconnectedAt;
|
|
90
|
+
logger.log("ConnectionMonitor recorded connect");
|
|
91
|
+
};
|
|
92
|
+
ConnectionMonitor.prototype.recordDisconnect = function recordDisconnect() {
|
|
93
|
+
this.disconnectedAt = now();
|
|
94
|
+
logger.log("ConnectionMonitor recorded disconnect");
|
|
95
|
+
};
|
|
96
|
+
ConnectionMonitor.prototype.startPolling = function startPolling() {
|
|
97
|
+
this.stopPolling();
|
|
98
|
+
this.poll();
|
|
99
|
+
};
|
|
100
|
+
ConnectionMonitor.prototype.stopPolling = function stopPolling() {
|
|
101
|
+
clearTimeout(this.pollTimeout);
|
|
102
|
+
};
|
|
103
|
+
ConnectionMonitor.prototype.poll = function poll() {
|
|
104
|
+
var _this = this;
|
|
105
|
+
this.pollTimeout = setTimeout(function() {
|
|
106
|
+
_this.reconnectIfStale();
|
|
107
|
+
_this.poll();
|
|
108
|
+
}, this.getPollInterval());
|
|
109
|
+
};
|
|
110
|
+
ConnectionMonitor.prototype.getPollInterval = function getPollInterval() {
|
|
111
|
+
var _constructor$pollInte = this.constructor.pollInterval, min = _constructor$pollInte.min, max = _constructor$pollInte.max, multiplier = _constructor$pollInte.multiplier;
|
|
112
|
+
var interval = multiplier * Math.log(this.reconnectAttempts + 1);
|
|
113
|
+
return Math.round(clamp(interval, min, max) * 1e3);
|
|
114
|
+
};
|
|
115
|
+
ConnectionMonitor.prototype.reconnectIfStale = function reconnectIfStale() {
|
|
116
|
+
if (this.connectionIsStale()) {
|
|
117
|
+
logger.log("ConnectionMonitor detected stale connection. reconnectAttempts = " + this.reconnectAttempts + ", pollInterval = " + this.getPollInterval() + " ms, time disconnected = " + secondsSince(this.disconnectedAt) + " s, stale threshold = " + this.constructor.staleThreshold + " s");
|
|
118
|
+
this.reconnectAttempts++;
|
|
119
|
+
if (this.disconnectedRecently()) {
|
|
120
|
+
logger.log("ConnectionMonitor skipping reopening recent disconnect");
|
|
121
|
+
} else {
|
|
122
|
+
logger.log("ConnectionMonitor reopening");
|
|
123
|
+
this.connection.reopen();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
ConnectionMonitor.prototype.connectionIsStale = function connectionIsStale() {
|
|
128
|
+
return secondsSince(this.pingedAt ? this.pingedAt : this.startedAt) > this.constructor.staleThreshold;
|
|
129
|
+
};
|
|
130
|
+
ConnectionMonitor.prototype.disconnectedRecently = function disconnectedRecently() {
|
|
131
|
+
return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;
|
|
132
|
+
};
|
|
133
|
+
ConnectionMonitor.prototype.visibilityDidChange = function visibilityDidChange() {
|
|
134
|
+
var _this2 = this;
|
|
135
|
+
if (document.visibilityState === "visible") {
|
|
136
|
+
setTimeout(function() {
|
|
137
|
+
if (_this2.connectionIsStale() || !_this2.connection.isOpen()) {
|
|
138
|
+
logger.log("ConnectionMonitor reopening stale connection on visibilitychange. visibilityState = " + document.visibilityState);
|
|
139
|
+
_this2.connection.reopen();
|
|
140
|
+
}
|
|
141
|
+
}, 200);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
return ConnectionMonitor;
|
|
145
|
+
}();
|
|
146
|
+
ConnectionMonitor.pollInterval = {
|
|
147
|
+
min: 3,
|
|
148
|
+
max: 30,
|
|
149
|
+
multiplier: 5
|
|
150
|
+
};
|
|
151
|
+
ConnectionMonitor.staleThreshold = 6;
|
|
152
|
+
var INTERNAL = {
|
|
153
|
+
message_types: {
|
|
154
|
+
welcome: "welcome",
|
|
155
|
+
disconnect: "disconnect",
|
|
156
|
+
ping: "ping",
|
|
157
|
+
confirmation: "confirm_subscription",
|
|
158
|
+
rejection: "reject_subscription"
|
|
159
|
+
},
|
|
160
|
+
disconnect_reasons: {
|
|
161
|
+
unauthorized: "unauthorized",
|
|
162
|
+
invalid_request: "invalid_request",
|
|
163
|
+
server_restart: "server_restart"
|
|
164
|
+
},
|
|
165
|
+
default_mount_path: "/cable",
|
|
166
|
+
protocols: [ "actioncable-v1-json", "actioncable-unsupported" ]
|
|
167
|
+
};
|
|
168
|
+
var message_types = INTERNAL.message_types, protocols = INTERNAL.protocols;
|
|
169
|
+
var supportedProtocols = protocols.slice(0, protocols.length - 1);
|
|
170
|
+
var indexOf = [].indexOf;
|
|
171
|
+
var Connection = function() {
|
|
172
|
+
function Connection(consumer) {
|
|
173
|
+
classCallCheck(this, Connection);
|
|
174
|
+
this.open = this.open.bind(this);
|
|
175
|
+
this.consumer = consumer;
|
|
176
|
+
this.subscriptions = this.consumer.subscriptions;
|
|
177
|
+
this.monitor = new ConnectionMonitor(this);
|
|
178
|
+
this.disconnected = true;
|
|
179
|
+
}
|
|
180
|
+
Connection.prototype.send = function send(data) {
|
|
181
|
+
if (this.isOpen()) {
|
|
182
|
+
this.webSocket.send(JSON.stringify(data));
|
|
183
|
+
return true;
|
|
184
|
+
} else {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
Connection.prototype.open = function open() {
|
|
189
|
+
if (this.isActive()) {
|
|
190
|
+
logger.log("Attempted to open WebSocket, but existing socket is " + this.getState());
|
|
191
|
+
return false;
|
|
192
|
+
} else {
|
|
193
|
+
logger.log("Opening WebSocket, current state is " + this.getState() + ", subprotocols: " + protocols);
|
|
194
|
+
if (this.webSocket) {
|
|
195
|
+
this.uninstallEventHandlers();
|
|
196
|
+
}
|
|
197
|
+
this.webSocket = new adapters.WebSocket(this.consumer.url, protocols);
|
|
198
|
+
this.installEventHandlers();
|
|
199
|
+
this.monitor.start();
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
Connection.prototype.close = function close() {
|
|
204
|
+
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
|
|
205
|
+
allowReconnect: true
|
|
206
|
+
}, allowReconnect = _ref.allowReconnect;
|
|
207
|
+
if (!allowReconnect) {
|
|
208
|
+
this.monitor.stop();
|
|
209
|
+
}
|
|
210
|
+
if (this.isActive()) {
|
|
211
|
+
return this.webSocket.close();
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
Connection.prototype.reopen = function reopen() {
|
|
215
|
+
logger.log("Reopening WebSocket, current state is " + this.getState());
|
|
216
|
+
if (this.isActive()) {
|
|
217
|
+
try {
|
|
218
|
+
return this.close();
|
|
219
|
+
} catch (error) {
|
|
220
|
+
logger.log("Failed to reopen WebSocket", error);
|
|
221
|
+
} finally {
|
|
222
|
+
logger.log("Reopening WebSocket in " + this.constructor.reopenDelay + "ms");
|
|
223
|
+
setTimeout(this.open, this.constructor.reopenDelay);
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
return this.open();
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
Connection.prototype.getProtocol = function getProtocol() {
|
|
230
|
+
if (this.webSocket) {
|
|
231
|
+
return this.webSocket.protocol;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
Connection.prototype.isOpen = function isOpen() {
|
|
235
|
+
return this.isState("open");
|
|
236
|
+
};
|
|
237
|
+
Connection.prototype.isActive = function isActive() {
|
|
238
|
+
return this.isState("open", "connecting");
|
|
239
|
+
};
|
|
240
|
+
Connection.prototype.isProtocolSupported = function isProtocolSupported() {
|
|
241
|
+
return indexOf.call(supportedProtocols, this.getProtocol()) >= 0;
|
|
242
|
+
};
|
|
243
|
+
Connection.prototype.isState = function isState() {
|
|
244
|
+
for (var _len = arguments.length, states = Array(_len), _key = 0; _key < _len; _key++) {
|
|
245
|
+
states[_key] = arguments[_key];
|
|
246
|
+
}
|
|
247
|
+
return indexOf.call(states, this.getState()) >= 0;
|
|
248
|
+
};
|
|
249
|
+
Connection.prototype.getState = function getState() {
|
|
250
|
+
if (this.webSocket) {
|
|
251
|
+
for (var state in adapters.WebSocket) {
|
|
252
|
+
if (adapters.WebSocket[state] === this.webSocket.readyState) {
|
|
253
|
+
return state.toLowerCase();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return null;
|
|
258
|
+
};
|
|
259
|
+
Connection.prototype.installEventHandlers = function installEventHandlers() {
|
|
260
|
+
for (var eventName in this.events) {
|
|
261
|
+
var handler = this.events[eventName].bind(this);
|
|
262
|
+
this.webSocket["on" + eventName] = handler;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
Connection.prototype.uninstallEventHandlers = function uninstallEventHandlers() {
|
|
266
|
+
for (var eventName in this.events) {
|
|
267
|
+
this.webSocket["on" + eventName] = function() {};
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
return Connection;
|
|
271
|
+
}();
|
|
272
|
+
Connection.reopenDelay = 500;
|
|
273
|
+
Connection.prototype.events = {
|
|
274
|
+
message: function message(event) {
|
|
275
|
+
if (!this.isProtocolSupported()) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
var _JSON$parse = JSON.parse(event.data), identifier = _JSON$parse.identifier, message = _JSON$parse.message, reason = _JSON$parse.reason, reconnect = _JSON$parse.reconnect, type = _JSON$parse.type;
|
|
279
|
+
switch (type) {
|
|
280
|
+
case message_types.welcome:
|
|
281
|
+
this.monitor.recordConnect();
|
|
282
|
+
return this.subscriptions.reload();
|
|
283
|
+
|
|
284
|
+
case message_types.disconnect:
|
|
285
|
+
logger.log("Disconnecting. Reason: " + reason);
|
|
286
|
+
return this.close({
|
|
287
|
+
allowReconnect: reconnect
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
case message_types.ping:
|
|
291
|
+
return this.monitor.recordPing();
|
|
292
|
+
|
|
293
|
+
case message_types.confirmation:
|
|
294
|
+
this.subscriptions.confirmSubscription(identifier);
|
|
295
|
+
return this.subscriptions.notify(identifier, "connected");
|
|
296
|
+
|
|
297
|
+
case message_types.rejection:
|
|
298
|
+
return this.subscriptions.reject(identifier);
|
|
299
|
+
|
|
300
|
+
default:
|
|
301
|
+
return this.subscriptions.notify(identifier, "received", message);
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
open: function open() {
|
|
305
|
+
logger.log("WebSocket onopen event, using '" + this.getProtocol() + "' subprotocol");
|
|
306
|
+
this.disconnected = false;
|
|
307
|
+
if (!this.isProtocolSupported()) {
|
|
308
|
+
logger.log("Protocol is unsupported. Stopping monitor and disconnecting.");
|
|
309
|
+
return this.close({
|
|
310
|
+
allowReconnect: false
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
close: function close(event) {
|
|
315
|
+
logger.log("WebSocket onclose event");
|
|
316
|
+
if (this.disconnected) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
this.disconnected = true;
|
|
320
|
+
this.monitor.recordDisconnect();
|
|
321
|
+
return this.subscriptions.notifyAll("disconnected", {
|
|
322
|
+
willAttemptReconnect: this.monitor.isRunning()
|
|
323
|
+
});
|
|
324
|
+
},
|
|
325
|
+
error: function error() {
|
|
326
|
+
logger.log("WebSocket onerror event");
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
var extend = function extend(object, properties) {
|
|
330
|
+
if (properties != null) {
|
|
331
|
+
for (var key in properties) {
|
|
332
|
+
var value = properties[key];
|
|
333
|
+
object[key] = value;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return object;
|
|
337
|
+
};
|
|
338
|
+
var Subscription = function() {
|
|
339
|
+
function Subscription(consumer) {
|
|
340
|
+
var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
341
|
+
var mixin = arguments[2];
|
|
342
|
+
classCallCheck(this, Subscription);
|
|
343
|
+
this.consumer = consumer;
|
|
344
|
+
this.identifier = JSON.stringify(params);
|
|
345
|
+
extend(this, mixin);
|
|
346
|
+
}
|
|
347
|
+
Subscription.prototype.perform = function perform(action) {
|
|
348
|
+
var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
349
|
+
data.action = action;
|
|
350
|
+
return this.send(data);
|
|
351
|
+
};
|
|
352
|
+
Subscription.prototype.send = function send(data) {
|
|
353
|
+
return this.consumer.send({
|
|
354
|
+
command: "message",
|
|
355
|
+
identifier: this.identifier,
|
|
356
|
+
data: JSON.stringify(data)
|
|
357
|
+
});
|
|
358
|
+
};
|
|
359
|
+
Subscription.prototype.unsubscribe = function unsubscribe() {
|
|
360
|
+
return this.consumer.subscriptions.remove(this);
|
|
361
|
+
};
|
|
362
|
+
return Subscription;
|
|
363
|
+
}();
|
|
364
|
+
var SubscriptionGuarantor = function() {
|
|
365
|
+
function SubscriptionGuarantor(subscriptions) {
|
|
366
|
+
classCallCheck(this, SubscriptionGuarantor);
|
|
367
|
+
this.subscriptions = subscriptions;
|
|
368
|
+
this.pendingSubscriptions = [];
|
|
369
|
+
}
|
|
370
|
+
SubscriptionGuarantor.prototype.guarantee = function guarantee(subscription) {
|
|
371
|
+
if (this.pendingSubscriptions.indexOf(subscription) == -1) {
|
|
372
|
+
logger.log("SubscriptionGuarantor guaranteeing " + subscription.identifier);
|
|
373
|
+
this.pendingSubscriptions.push(subscription);
|
|
374
|
+
} else {
|
|
375
|
+
logger.log("SubscriptionGuarantor already guaranteeing " + subscription.identifier);
|
|
376
|
+
}
|
|
377
|
+
this.startGuaranteeing();
|
|
378
|
+
};
|
|
379
|
+
SubscriptionGuarantor.prototype.forget = function forget(subscription) {
|
|
380
|
+
logger.log("SubscriptionGuarantor forgetting " + subscription.identifier);
|
|
381
|
+
this.pendingSubscriptions = this.pendingSubscriptions.filter(function(s) {
|
|
382
|
+
return s !== subscription;
|
|
383
|
+
});
|
|
384
|
+
};
|
|
385
|
+
SubscriptionGuarantor.prototype.startGuaranteeing = function startGuaranteeing() {
|
|
386
|
+
this.stopGuaranteeing();
|
|
387
|
+
this.retrySubscribing();
|
|
388
|
+
};
|
|
389
|
+
SubscriptionGuarantor.prototype.stopGuaranteeing = function stopGuaranteeing() {
|
|
390
|
+
clearTimeout(this.retryTimeout);
|
|
391
|
+
};
|
|
392
|
+
SubscriptionGuarantor.prototype.retrySubscribing = function retrySubscribing() {
|
|
393
|
+
var _this = this;
|
|
394
|
+
this.retryTimeout = setTimeout(function() {
|
|
395
|
+
if (_this.subscriptions && typeof _this.subscriptions.subscribe === "function") {
|
|
396
|
+
_this.pendingSubscriptions.map(function(subscription) {
|
|
397
|
+
logger.log("SubscriptionGuarantor resubscribing " + subscription.identifier);
|
|
398
|
+
_this.subscriptions.subscribe(subscription);
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}, 500);
|
|
402
|
+
};
|
|
403
|
+
return SubscriptionGuarantor;
|
|
404
|
+
}();
|
|
405
|
+
var Subscriptions = function() {
|
|
406
|
+
function Subscriptions(consumer) {
|
|
407
|
+
classCallCheck(this, Subscriptions);
|
|
408
|
+
this.consumer = consumer;
|
|
409
|
+
this.guarantor = new SubscriptionGuarantor(this);
|
|
410
|
+
this.subscriptions = [];
|
|
411
|
+
}
|
|
412
|
+
Subscriptions.prototype.create = function create(channelName, mixin) {
|
|
413
|
+
var channel = channelName;
|
|
414
|
+
var params = (typeof channel === "undefined" ? "undefined" : _typeof(channel)) === "object" ? channel : {
|
|
415
|
+
channel: channel
|
|
416
|
+
};
|
|
417
|
+
var subscription = new Subscription(this.consumer, params, mixin);
|
|
418
|
+
return this.add(subscription);
|
|
419
|
+
};
|
|
420
|
+
Subscriptions.prototype.add = function add(subscription) {
|
|
421
|
+
this.subscriptions.push(subscription);
|
|
422
|
+
this.consumer.ensureActiveConnection();
|
|
423
|
+
this.notify(subscription, "initialized");
|
|
424
|
+
this.subscribe(subscription);
|
|
425
|
+
return subscription;
|
|
426
|
+
};
|
|
427
|
+
Subscriptions.prototype.remove = function remove(subscription) {
|
|
428
|
+
this.forget(subscription);
|
|
429
|
+
if (!this.findAll(subscription.identifier).length) {
|
|
430
|
+
this.sendCommand(subscription, "unsubscribe");
|
|
431
|
+
}
|
|
432
|
+
return subscription;
|
|
433
|
+
};
|
|
434
|
+
Subscriptions.prototype.reject = function reject(identifier) {
|
|
435
|
+
var _this = this;
|
|
436
|
+
return this.findAll(identifier).map(function(subscription) {
|
|
437
|
+
_this.forget(subscription);
|
|
438
|
+
_this.notify(subscription, "rejected");
|
|
439
|
+
return subscription;
|
|
440
|
+
});
|
|
441
|
+
};
|
|
442
|
+
Subscriptions.prototype.forget = function forget(subscription) {
|
|
443
|
+
this.guarantor.forget(subscription);
|
|
444
|
+
this.subscriptions = this.subscriptions.filter(function(s) {
|
|
445
|
+
return s !== subscription;
|
|
446
|
+
});
|
|
447
|
+
return subscription;
|
|
448
|
+
};
|
|
449
|
+
Subscriptions.prototype.findAll = function findAll(identifier) {
|
|
450
|
+
return this.subscriptions.filter(function(s) {
|
|
451
|
+
return s.identifier === identifier;
|
|
452
|
+
});
|
|
453
|
+
};
|
|
454
|
+
Subscriptions.prototype.reload = function reload() {
|
|
455
|
+
var _this2 = this;
|
|
456
|
+
return this.subscriptions.map(function(subscription) {
|
|
457
|
+
return _this2.subscribe(subscription);
|
|
458
|
+
});
|
|
459
|
+
};
|
|
460
|
+
Subscriptions.prototype.notifyAll = function notifyAll(callbackName) {
|
|
461
|
+
var _this3 = this;
|
|
462
|
+
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
463
|
+
args[_key - 1] = arguments[_key];
|
|
464
|
+
}
|
|
465
|
+
return this.subscriptions.map(function(subscription) {
|
|
466
|
+
return _this3.notify.apply(_this3, [ subscription, callbackName ].concat(args));
|
|
467
|
+
});
|
|
468
|
+
};
|
|
469
|
+
Subscriptions.prototype.notify = function notify(subscription, callbackName) {
|
|
470
|
+
for (var _len2 = arguments.length, args = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {
|
|
471
|
+
args[_key2 - 2] = arguments[_key2];
|
|
472
|
+
}
|
|
473
|
+
var subscriptions = void 0;
|
|
474
|
+
if (typeof subscription === "string") {
|
|
475
|
+
subscriptions = this.findAll(subscription);
|
|
476
|
+
} else {
|
|
477
|
+
subscriptions = [ subscription ];
|
|
478
|
+
}
|
|
479
|
+
return subscriptions.map(function(subscription) {
|
|
480
|
+
return typeof subscription[callbackName] === "function" ? subscription[callbackName].apply(subscription, args) : undefined;
|
|
481
|
+
});
|
|
482
|
+
};
|
|
483
|
+
Subscriptions.prototype.subscribe = function subscribe(subscription) {
|
|
484
|
+
if (this.sendCommand(subscription, "subscribe")) {
|
|
485
|
+
this.guarantor.guarantee(subscription);
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
Subscriptions.prototype.confirmSubscription = function confirmSubscription(identifier) {
|
|
489
|
+
var _this4 = this;
|
|
490
|
+
logger.log("Subscription confirmed " + identifier);
|
|
491
|
+
this.findAll(identifier).map(function(subscription) {
|
|
492
|
+
return _this4.guarantor.forget(subscription);
|
|
493
|
+
});
|
|
494
|
+
};
|
|
495
|
+
Subscriptions.prototype.sendCommand = function sendCommand(subscription, command) {
|
|
496
|
+
var identifier = subscription.identifier;
|
|
497
|
+
return this.consumer.send({
|
|
498
|
+
command: command,
|
|
499
|
+
identifier: identifier
|
|
500
|
+
});
|
|
501
|
+
};
|
|
502
|
+
return Subscriptions;
|
|
503
|
+
}();
|
|
504
|
+
var Consumer = function() {
|
|
505
|
+
function Consumer(url) {
|
|
506
|
+
classCallCheck(this, Consumer);
|
|
507
|
+
this._url = url;
|
|
508
|
+
this.subscriptions = new Subscriptions(this);
|
|
509
|
+
this.connection = new Connection(this);
|
|
510
|
+
}
|
|
511
|
+
Consumer.prototype.send = function send(data) {
|
|
512
|
+
return this.connection.send(data);
|
|
513
|
+
};
|
|
514
|
+
Consumer.prototype.connect = function connect() {
|
|
515
|
+
return this.connection.open();
|
|
516
|
+
};
|
|
517
|
+
Consumer.prototype.disconnect = function disconnect() {
|
|
518
|
+
return this.connection.close({
|
|
519
|
+
allowReconnect: false
|
|
520
|
+
});
|
|
521
|
+
};
|
|
522
|
+
Consumer.prototype.ensureActiveConnection = function ensureActiveConnection() {
|
|
523
|
+
if (!this.connection.isActive()) {
|
|
524
|
+
return this.connection.open();
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
createClass(Consumer, [ {
|
|
528
|
+
key: "url",
|
|
529
|
+
get: function get$$1() {
|
|
530
|
+
return createWebSocketURL(this._url);
|
|
531
|
+
}
|
|
532
|
+
} ]);
|
|
533
|
+
return Consumer;
|
|
534
|
+
}();
|
|
535
|
+
function createWebSocketURL(url) {
|
|
536
|
+
if (typeof url === "function") {
|
|
537
|
+
url = url();
|
|
538
|
+
}
|
|
539
|
+
if (url && !/^wss?:/i.test(url)) {
|
|
540
|
+
var a = document.createElement("a");
|
|
541
|
+
a.href = url;
|
|
542
|
+
a.href = a.href;
|
|
543
|
+
a.protocol = a.protocol.replace("http", "ws");
|
|
544
|
+
return a.href;
|
|
545
|
+
} else {
|
|
546
|
+
return url;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
function createConsumer() {
|
|
550
|
+
var url = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getConfig("url") || INTERNAL.default_mount_path;
|
|
551
|
+
return new Consumer(url);
|
|
552
|
+
}
|
|
553
|
+
function getConfig(name) {
|
|
554
|
+
var element = document.head.querySelector("meta[name='action-cable-" + name + "']");
|
|
555
|
+
if (element) {
|
|
556
|
+
return element.getAttribute("content");
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
exports.Connection = Connection;
|
|
560
|
+
exports.ConnectionMonitor = ConnectionMonitor;
|
|
561
|
+
exports.Consumer = Consumer;
|
|
562
|
+
exports.INTERNAL = INTERNAL;
|
|
563
|
+
exports.Subscription = Subscription;
|
|
564
|
+
exports.Subscriptions = Subscriptions;
|
|
565
|
+
exports.SubscriptionGuarantor = SubscriptionGuarantor;
|
|
566
|
+
exports.adapters = adapters;
|
|
567
|
+
exports.createWebSocketURL = createWebSocketURL;
|
|
568
|
+
exports.logger = logger;
|
|
569
|
+
exports.createConsumer = createConsumer;
|
|
570
|
+
exports.getConfig = getConfig;
|
|
571
|
+
Object.defineProperty(exports, "__esModule", {
|
|
572
|
+
value: true
|
|
573
|
+
});
|
|
574
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "set"
|
|
4
|
+
require "active_support/rescuable"
|
|
4
5
|
|
|
5
6
|
module ActionCable
|
|
6
7
|
module Channel
|
|
@@ -99,6 +100,7 @@ module ActionCable
|
|
|
99
100
|
include Streams
|
|
100
101
|
include Naming
|
|
101
102
|
include Broadcasting
|
|
103
|
+
include ActiveSupport::Rescuable
|
|
102
104
|
|
|
103
105
|
attr_reader :params, :connection, :identifier
|
|
104
106
|
delegate :logger, to: :connection
|
|
@@ -192,7 +194,7 @@ module ActionCable
|
|
|
192
194
|
end
|
|
193
195
|
|
|
194
196
|
private
|
|
195
|
-
# Called once a consumer has become a subscriber of the channel. Usually the place to
|
|
197
|
+
# Called once a consumer has become a subscriber of the channel. Usually the place to set up any streams
|
|
196
198
|
# you want this channel to be sending to the subscriber.
|
|
197
199
|
def subscribed # :doc:
|
|
198
200
|
# Override in subclasses
|
|
@@ -267,10 +269,12 @@ module ActionCable
|
|
|
267
269
|
else
|
|
268
270
|
public_send action
|
|
269
271
|
end
|
|
272
|
+
rescue Exception => exception
|
|
273
|
+
rescue_with_handler(exception) || raise
|
|
270
274
|
end
|
|
271
275
|
|
|
272
276
|
def action_signature(action, data)
|
|
273
|
-
"#{self.class.name}##{action}".
|
|
277
|
+
(+"#{self.class.name}##{action}").tap do |signature|
|
|
274
278
|
if (arguments = data.except("action")).any?
|
|
275
279
|
signature << "(#{arguments.inspect})"
|
|
276
280
|
end
|
|
@@ -279,7 +283,7 @@ module ActionCable
|
|
|
279
283
|
|
|
280
284
|
def transmit_subscription_confirmation
|
|
281
285
|
unless subscription_confirmation_sent?
|
|
282
|
-
logger.
|
|
286
|
+
logger.debug "#{self.class.name} is transmitting the subscription confirmation"
|
|
283
287
|
|
|
284
288
|
ActiveSupport::Notifications.instrument("transmit_subscription_confirmation.action_cable", channel_class: self.class.name) do
|
|
285
289
|
connection.transmit identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:confirmation]
|
|
@@ -294,7 +298,7 @@ module ActionCable
|
|
|
294
298
|
end
|
|
295
299
|
|
|
296
300
|
def transmit_subscription_rejection
|
|
297
|
-
logger.
|
|
301
|
+
logger.debug "#{self.class.name} is transmitting the subscription rejection"
|
|
298
302
|
|
|
299
303
|
ActiveSupport::Notifications.instrument("transmit_subscription_rejection.action_cable", channel_class: self.class.name) do
|
|
300
304
|
connection.transmit identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:rejection]
|
|
@@ -303,3 +307,5 @@ module ActionCable
|
|
|
303
307
|
end
|
|
304
308
|
end
|
|
305
309
|
end
|
|
310
|
+
|
|
311
|
+
ActiveSupport.run_load_hooks(:action_cable_channel, ActionCable::Channel::Base)
|
|
@@ -7,22 +7,32 @@ module ActionCable
|
|
|
7
7
|
module Broadcasting
|
|
8
8
|
extend ActiveSupport::Concern
|
|
9
9
|
|
|
10
|
-
delegate :broadcasting_for, to: :class
|
|
10
|
+
delegate :broadcasting_for, :broadcast_to, to: :class
|
|
11
11
|
|
|
12
12
|
module ClassMethods
|
|
13
13
|
# Broadcast a hash to a unique broadcasting for this <tt>model</tt> in this channel.
|
|
14
14
|
def broadcast_to(model, message)
|
|
15
|
-
ActionCable.server.broadcast(broadcasting_for(
|
|
15
|
+
ActionCable.server.broadcast(broadcasting_for(model), message)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
# Returns a unique broadcasting identifier for this <tt>model</tt> in this channel:
|
|
19
|
+
#
|
|
20
|
+
# CommentsChannel.broadcasting_for("all") # => "comments:all"
|
|
21
|
+
#
|
|
22
|
+
# You can pass any object as a target (e.g. Active Record model), and it
|
|
23
|
+
# would be serialized into a string under the hood.
|
|
24
|
+
def broadcasting_for(model)
|
|
25
|
+
serialize_broadcasting([ channel_name, model ])
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def serialize_broadcasting(object) #:nodoc:
|
|
19
29
|
case
|
|
20
|
-
when
|
|
21
|
-
|
|
22
|
-
when
|
|
23
|
-
|
|
30
|
+
when object.is_a?(Array)
|
|
31
|
+
object.map { |m| serialize_broadcasting(m) }.join(":")
|
|
32
|
+
when object.respond_to?(:to_gid_param)
|
|
33
|
+
object.to_gid_param
|
|
24
34
|
else
|
|
25
|
-
|
|
35
|
+
object.to_param
|
|
26
36
|
end
|
|
27
37
|
end
|
|
28
38
|
end
|
|
@@ -14,7 +14,7 @@ module ActionCable
|
|
|
14
14
|
# Chats::AppearancesChannel.channel_name # => 'chats:appearances'
|
|
15
15
|
# FooChats::BarAppearancesChannel.channel_name # => 'foo_chats:bar_appearances'
|
|
16
16
|
def channel_name
|
|
17
|
-
@channel_name ||= name.
|
|
17
|
+
@channel_name ||= name.delete_suffix("Channel").gsub("::", ":").underscore
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
|