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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -39
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +3 -546
  5. data/app/assets/javascripts/action_cable.js +574 -0
  6. data/lib/action_cable/channel/base.rb +10 -4
  7. data/lib/action_cable/channel/broadcasting.rb +18 -8
  8. data/lib/action_cable/channel/naming.rb +1 -1
  9. data/lib/action_cable/channel/streams.rb +30 -4
  10. data/lib/action_cable/channel/test_case.rb +310 -0
  11. data/lib/action_cable/channel.rb +1 -0
  12. data/lib/action_cable/connection/authorization.rb +1 -1
  13. data/lib/action_cable/connection/base.rb +13 -7
  14. data/lib/action_cable/connection/message_buffer.rb +1 -4
  15. data/lib/action_cable/connection/stream.rb +4 -2
  16. data/lib/action_cable/connection/subscriptions.rb +2 -5
  17. data/lib/action_cable/connection/test_case.rb +234 -0
  18. data/lib/action_cable/connection/web_socket.rb +1 -3
  19. data/lib/action_cable/connection.rb +1 -0
  20. data/lib/action_cable/engine.rb +1 -1
  21. data/lib/action_cable/gem_version.rb +4 -4
  22. data/lib/action_cable/helpers/action_cable_helper.rb +3 -3
  23. data/lib/action_cable/remote_connections.rb +1 -1
  24. data/lib/action_cable/server/base.rb +9 -4
  25. data/lib/action_cable/server/broadcasting.rb +1 -1
  26. data/lib/action_cable/server/worker.rb +6 -8
  27. data/lib/action_cable/server.rb +0 -1
  28. data/lib/action_cable/subscription_adapter/base.rb +4 -0
  29. data/lib/action_cable/subscription_adapter/postgresql.rb +28 -9
  30. data/lib/action_cable/subscription_adapter/redis.rb +4 -2
  31. data/lib/action_cable/subscription_adapter/test.rb +40 -0
  32. data/lib/action_cable/subscription_adapter.rb +1 -0
  33. data/lib/action_cable/test_case.rb +11 -0
  34. data/lib/action_cable/test_helper.rb +133 -0
  35. data/lib/action_cable.rb +15 -7
  36. data/lib/rails/generators/channel/USAGE +5 -6
  37. data/lib/rails/generators/channel/channel_generator.rb +6 -3
  38. data/lib/rails/generators/channel/templates/{assets → javascript}/channel.js.tt +6 -4
  39. data/lib/rails/generators/channel/templates/javascript/consumer.js.tt +6 -0
  40. data/lib/rails/generators/channel/templates/javascript/index.js.tt +5 -0
  41. data/lib/rails/generators/test_unit/channel_generator.rb +20 -0
  42. data/lib/rails/generators/test_unit/templates/channel_test.rb.tt +8 -0
  43. metadata +41 -16
  44. data/lib/assets/compiled/action_cable.js +0 -601
  45. data/lib/rails/generators/channel/templates/assets/cable.js.tt +0 -13
  46. 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 setup any streams
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}".dup.tap do |signature|
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.info "#{self.class.name} is transmitting the subscription confirmation"
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.info "#{self.class.name} is transmitting the subscription rejection"
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([ channel_name, model ]), message)
15
+ ActionCable.server.broadcast(broadcasting_for(model), message)
16
16
  end
17
17
 
18
- def broadcasting_for(model) #:nodoc:
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 model.is_a?(Array)
21
- model.map { |m| broadcasting_for(m) }.join(":")
22
- when model.respond_to?(:to_gid_param)
23
- model.to_gid_param
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
- model.to_param
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.sub(/Channel$/, "").gsub("::", ":").underscore
17
+ @channel_name ||= name.delete_suffix("Channel").gsub("::", ":").underscore
18
18
  end
19
19
  end
20
20