actioncable 5.2.7.1 → 6.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +53 -47
  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