actioncable 7.0.4 → 7.1.5.1

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 +102 -51
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +5 -5
  5. data/app/assets/javascripts/action_cable.js +27 -6
  6. data/app/assets/javascripts/actioncable.esm.js +27 -6
  7. data/app/assets/javascripts/actioncable.js +27 -6
  8. data/lib/action_cable/channel/base.rb +17 -5
  9. data/lib/action_cable/channel/broadcasting.rb +3 -1
  10. data/lib/action_cable/channel/callbacks.rb +37 -0
  11. data/lib/action_cable/channel/naming.rb +4 -2
  12. data/lib/action_cable/channel/streams.rb +2 -0
  13. data/lib/action_cable/channel/test_case.rb +9 -4
  14. data/lib/action_cable/connection/authorization.rb +1 -1
  15. data/lib/action_cable/connection/base.rb +16 -3
  16. data/lib/action_cable/connection/callbacks.rb +55 -0
  17. data/lib/action_cable/connection/internal_channel.rb +3 -1
  18. data/lib/action_cable/connection/stream.rb +1 -3
  19. data/lib/action_cable/connection/stream_event_loop.rb +0 -1
  20. data/lib/action_cable/connection/subscriptions.rb +2 -0
  21. data/lib/action_cable/connection/tagged_logger_proxy.rb +6 -4
  22. data/lib/action_cable/connection/test_case.rb +3 -1
  23. data/lib/action_cable/connection/web_socket.rb +2 -0
  24. data/lib/action_cable/deprecator.rb +7 -0
  25. data/lib/action_cable/engine.rb +13 -5
  26. data/lib/action_cable/gem_version.rb +4 -4
  27. data/lib/action_cable/remote_connections.rb +11 -2
  28. data/lib/action_cable/server/base.rb +3 -0
  29. data/lib/action_cable/server/broadcasting.rb +2 -0
  30. data/lib/action_cable/server/configuration.rb +12 -2
  31. data/lib/action_cable/server/connections.rb +3 -1
  32. data/lib/action_cable/server/worker.rb +0 -1
  33. data/lib/action_cable/subscription_adapter/async.rb +0 -2
  34. data/lib/action_cable/subscription_adapter/postgresql.rb +0 -1
  35. data/lib/action_cable/subscription_adapter/redis.rb +56 -10
  36. data/lib/action_cable/subscription_adapter/test.rb +3 -5
  37. data/lib/action_cable/test_helper.rb +46 -19
  38. data/lib/action_cable/version.rb +1 -1
  39. data/lib/action_cable.rb +24 -12
  40. data/lib/rails/generators/channel/USAGE +14 -8
  41. data/lib/rails/generators/channel/channel_generator.rb +21 -7
  42. metadata +29 -17
  43. data/lib/action_cable/channel.rb +0 -17
  44. data/lib/action_cable/connection.rb +0 -22
  45. data/lib/action_cable/server.rb +0 -16
  46. data/lib/action_cable/subscription_adapter.rb +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 51532672c5293e1ac4ffdf854f4ee41de231ef843e6ddbc51a20fdede0e029e5
4
- data.tar.gz: f5324ae765aa6bcdc65590be2f2ad0c034e9b1a58d318a4f27e9e9f7e6ffd3c1
3
+ metadata.gz: edd9d86e9fbca1d2b24d81d798d1a50eac2fe0f33f5b9a6b623c3a0e8d02ac7d
4
+ data.tar.gz: bd9c7fca8f92dd7e0b6a6dc30e8a2c8ba221baf08da2a1ca9b56ade8e27505f8
5
5
  SHA512:
6
- metadata.gz: 6084037b957593e0e31cdf7c55030f9f2921d03a93b01f0bdb54f2460936909667350edba98b10e287a3563bd39231606abf1f575f45ee47b603d748ef48a072
7
- data.tar.gz: c2457611b9b2694955a9613fc27211e34e4d7b4b0d98b084eca9e1b0052f6ccbbfdacd0133d6b0da2eb1bed895c7a18bfe32ae5dfac167c4725bb8b45dcd6bf9
6
+ metadata.gz: 78049f1d0b8779d8202b3ce9ae6feee445b6200065c5eb78103a662d18a2015f890ff6662bb9df1bf5d8da34a03f7a5bb7af81709f13851b13894ba2432d3a1f
7
+ data.tar.gz: e403b191bafc0f75694adbb8727b7be7f17acb855314ddffe5a860a57c17a471c62ce7ebcb4526af815e3ffe98f24218197f2216533f0341063fe6f8ab664b9a
data/CHANGELOG.md CHANGED
@@ -1,125 +1,176 @@
1
- ## Rails 7.0.4 (September 09, 2022) ##
1
+ ## Rails 7.1.5.1 (December 10, 2024) ##
2
2
 
3
- * The Redis adapter is now compatible with redis-rb 5.0
3
+ * No changes.
4
4
 
5
- Compatibility with redis-rb 3.x was dropped.
6
5
 
7
- *Jean Boussier*
6
+ ## Rails 7.1.5 (October 30, 2024) ##
8
7
 
9
- * The Action Cable server is now mounted with `anchor: true`.
8
+ * No changes.
10
9
 
11
- This means that routes that also start with `/cable` will no longer clash with Action Cable.
12
10
 
13
- *Alex Ghiculescu*
11
+ ## Rails 7.1.4.2 (October 23, 2024) ##
12
+
13
+ * No changes.
14
14
 
15
15
 
16
- ## Rails 7.0.3.1 (July 12, 2022) ##
16
+ ## Rails 7.1.4.1 (October 15, 2024) ##
17
17
 
18
18
  * No changes.
19
19
 
20
20
 
21
- ## Rails 7.0.3 (May 09, 2022) ##
21
+ ## Rails 7.1.4 (August 22, 2024) ##
22
22
 
23
23
  * No changes.
24
24
 
25
25
 
26
- ## Rails 7.0.2.4 (April 26, 2022) ##
26
+ ## Rails 7.1.3.4 (June 04, 2024) ##
27
27
 
28
28
  * No changes.
29
29
 
30
30
 
31
- ## Rails 7.0.2.3 (March 08, 2022) ##
31
+ ## Rails 7.1.3.3 (May 16, 2024) ##
32
32
 
33
33
  * No changes.
34
34
 
35
35
 
36
- ## Rails 7.0.2.2 (February 11, 2022) ##
36
+ ## Rails 7.1.3.2 (February 21, 2024) ##
37
37
 
38
38
  * No changes.
39
39
 
40
40
 
41
- ## Rails 7.0.2.1 (February 11, 2022) ##
41
+ ## Rails 7.1.3.1 (February 21, 2024) ##
42
42
 
43
43
  * No changes.
44
44
 
45
45
 
46
- ## Rails 7.0.2 (February 08, 2022) ##
46
+ ## Rails 7.1.3 (January 16, 2024) ##
47
47
 
48
48
  * No changes.
49
49
 
50
50
 
51
- ## Rails 7.0.1 (January 06, 2022) ##
51
+ ## Rails 7.1.2 (November 10, 2023) ##
52
52
 
53
53
  * No changes.
54
54
 
55
55
 
56
- ## Rails 7.0.0 (December 15, 2021) ##
56
+ ## Rails 7.1.1 (October 11, 2023) ##
57
57
 
58
58
  * No changes.
59
59
 
60
60
 
61
- ## Rails 7.0.0.rc3 (December 14, 2021) ##
61
+ ## Rails 7.1.0 (October 05, 2023) ##
62
62
 
63
63
  * No changes.
64
64
 
65
65
 
66
- ## Rails 7.0.0.rc2 (December 14, 2021) ##
66
+ ## Rails 7.1.0.rc2 (October 01, 2023) ##
67
67
 
68
68
  * No changes.
69
69
 
70
- ## Rails 7.0.0.rc1 (December 06, 2021) ##
71
70
 
72
- * The Action Cable client now ensures successful channel subscriptions:
71
+ ## Rails 7.1.0.rc1 (September 27, 2023) ##
73
72
 
74
- * The client maintains a set of pending subscriptions until either
75
- the server confirms the subscription or the channel is torn down.
76
- * Rectifies the race condition where an unsubscribe is rapidly followed
77
- by a subscribe (on the same channel identifier) and the requests are
78
- handled out of order by the ActionCable server, thereby ignoring the
79
- subscribe command.
73
+ * No changes.
80
74
 
81
- *Daniel Spinosa*
82
75
 
76
+ ## Rails 7.1.0.beta1 (September 13, 2023) ##
83
77
 
84
- ## Rails 7.0.0.alpha2 (September 15, 2021) ##
78
+ * Add a `@server` instance variable referencing the `ActionCable.server`
79
+ singleton to `ActionCable::Channel::ConnectionStub`
85
80
 
86
- * No changes.
81
+ This lets us delegate the `pubsub` and `config` method calls
82
+ to the server. This fixes `NoMethodError` errors when testing
83
+ channel logic that call `pubsub` (e.g. `stop_stream_for`).
84
+
85
+ *Julian Foo*
86
+
87
+ * Added `health_check_path` and `health_check_application` config to
88
+ mount a given health check rack app on a given path.
89
+ Useful when mounting Action Cable standalone.
90
+
91
+ *Joé Dupuis*
92
+
93
+ * Introduce the `capture_broadcasts` test helper.
94
+
95
+ Returns all messages broadcast in a block.
96
+
97
+ ```ruby
98
+ messages = capture_broadcasts("test") do
99
+ ActionCable.server.broadcast "test", { message: "one" }
100
+ ActionCable.server.broadcast "test", { message: "two" }
101
+ end
102
+ assert_equal 2, messages.length
103
+ assert_equal({ "message" => "one" }, messages.first)
104
+ assert_equal({ "message" => "two" }, messages.last)
105
+ ```
87
106
 
107
+ *Alex Ghiculescu*
108
+
109
+ * Display broadcasted messages on error message when using `assert_broadcast_on`
110
+
111
+ *Stéphane Robino*
112
+
113
+ * The Action Cable client now supports subprotocols to allow passing arbitrary data
114
+ to the server.
115
+
116
+ ```js
117
+ const consumer = ActionCable.createConsumer()
118
+
119
+ consumer.addSubProtocol('custom-protocol')
120
+
121
+ consumer.connect()
122
+ ```
123
+
124
+ See also:
125
+
126
+ * https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#subprotocols
88
127
 
89
- ## Rails 7.0.0.alpha1 (September 15, 2021) ##
128
+ *Guillaume Hain*
90
129
 
91
- * Compile ESM package that can be used directly in the browser as actioncable.esm.js.
130
+ * Redis pub/sub adapter now automatically reconnects when Redis connection is lost.
92
131
 
93
- *DHH*
132
+ *Vladimir Dementyev*
94
133
 
95
- * Move action_cable.js to actioncable.js to match naming convention used for other Rails frameworks, and use JS console to communicate the deprecation.
134
+ * The `connected()` callback can now take a `{reconnected}` parameter to differentiate
135
+ connections from reconnections.
96
136
 
97
- *DHH*
137
+ ```js
138
+ import consumer from "./consumer"
98
139
 
99
- * Stop transpiling the UMD package generated as actioncable.js and drop the IE11 testing that relied on that.
140
+ consumer.subscriptions.create("ExampleChannel", {
141
+ connected({reconnected}) {
142
+ if (reconnected) {
143
+ ...
144
+ } else {
145
+ ...
146
+ }
147
+ }
148
+ })
149
+ ```
100
150
 
101
- *DHH*
151
+ *Mansa Keïta*
102
152
 
103
- * Truncate broadcast logging messages.
153
+ * The Redis adapter is now compatible with redis-rb 5.0
104
154
 
105
- *J Smith*
155
+ Compatibility with redis-rb 3.x was dropped.
106
156
 
107
- * OpenSSL constants are now used for Digest computations.
157
+ *Jean Boussier*
158
+
159
+ * The Action Cable server is now mounted with `anchor: true`.
160
+
161
+ This means that routes that also start with `/cable` will no longer clash with Action Cable.
162
+
163
+ *Alex Ghiculescu*
108
164
 
109
- *Dirkjan Bussink*
165
+ * `ActionCable.server.remote_connections.where(...).disconnect` now sends `disconnect` message
166
+ before closing the connection with the reconnection strategy specified (defaults to `true`).
110
167
 
111
- * The Action Cable client now includes safeguards to prevent a "thundering
112
- herd" of client reconnects after server connectivity loss:
168
+ *Vladimir Dementyev*
113
169
 
114
- * The client will wait a random amount between 1x and 3x of the stale
115
- threshold after the server's last ping before making the first
116
- reconnection attempt.
117
- * Subsequent reconnection attempts now use exponential backoff instead of
118
- logarithmic backoff. To allow the delay between reconnection attempts to
119
- increase slowly at first, the default exponentiation base is < 2.
120
- * Random jitter is applied to each delay between reconnection attempts.
170
+ * Added command callbacks to `ActionCable::Connection::Base`.
121
171
 
122
- *Jonathan Hefner*
172
+ Now you can define `before_command`, `after_command`, and `around_command` to be invoked before, after or around any command received by a client respectively.
123
173
 
174
+ *Vladimir Dementyev*
124
175
 
125
- Please check [6-1-stable](https://github.com/rails/rails/blob/6-1-stable/actioncable/CHANGELOG.md) for previous changes.
176
+ Please check [7-0-stable](https://github.com/rails/rails/blob/7-0-stable/actioncable/CHANGELOG.md) for previous changes.
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2015-2022 Basecamp, LLC
1
+ Copyright (c) 37signals LLC
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,13 +1,13 @@
1
- # Action Cable – Integrated WebSockets for Rails
1
+ # Action Cable – Integrated WebSockets for \Rails
2
2
 
3
- Action Cable seamlessly integrates WebSockets with the rest of your Rails application.
3
+ Action Cable seamlessly integrates WebSockets with the rest of your \Rails application.
4
4
  It allows for real-time features to be written in Ruby in the same style
5
- and form as the rest of your Rails application, while still being performant
5
+ and form as the rest of your \Rails application, while still being performant
6
6
  and scalable. It's a full-stack offering that provides both a client-side
7
7
  JavaScript framework and a server-side Ruby framework. You have access to your full
8
8
  domain model written with Active Record or your ORM of choice.
9
9
 
10
- You can read more about Action Cable in the [Action Cable Overview](https://edgeguides.rubyonrails.org/action_cable_overview.html) guide.
10
+ You can read more about Action Cable in the [Action Cable Overview](https://guides.rubyonrails.org/action_cable_overview.html) guide.
11
11
 
12
12
  ## Support
13
13
 
@@ -15,7 +15,7 @@ API documentation is at:
15
15
 
16
16
  * https://api.rubyonrails.org
17
17
 
18
- Bug reports for the Ruby on Rails project can be filed here:
18
+ Bug reports for the Ruby on \Rails project can be filed here:
19
19
 
20
20
  * https://github.com/rails/rails/issues
21
21
 
@@ -4,8 +4,8 @@
4
4
  })(this, (function(exports) {
5
5
  "use strict";
6
6
  var adapters = {
7
- logger: self.console,
8
- WebSocket: self.WebSocket
7
+ logger: typeof console !== "undefined" ? console : undefined,
8
+ WebSocket: typeof WebSocket !== "undefined" ? WebSocket : undefined
9
9
  };
10
10
  var logger = {
11
11
  log(...messages) {
@@ -121,7 +121,8 @@
121
121
  disconnect_reasons: {
122
122
  unauthorized: "unauthorized",
123
123
  invalid_request: "invalid_request",
124
- server_restart: "server_restart"
124
+ server_restart: "server_restart",
125
+ remote: "remote"
125
126
  },
126
127
  default_mount_path: "/cable",
127
128
  protocols: [ "actioncable-v1-json", "actioncable-unsupported" ]
@@ -150,11 +151,12 @@
150
151
  logger.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`);
151
152
  return false;
152
153
  } else {
153
- logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${protocols}`);
154
+ const socketProtocols = [ ...protocols, ...this.consumer.subprotocols || [] ];
155
+ logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${socketProtocols}`);
154
156
  if (this.webSocket) {
155
157
  this.uninstallEventHandlers();
156
158
  }
157
- this.webSocket = new adapters.WebSocket(this.consumer.url, protocols);
159
+ this.webSocket = new adapters.WebSocket(this.consumer.url, socketProtocols);
158
160
  this.installEventHandlers();
159
161
  this.monitor.start();
160
162
  return true;
@@ -196,6 +198,9 @@
196
198
  isActive() {
197
199
  return this.isState("open", "connecting");
198
200
  }
201
+ triedToReconnect() {
202
+ return this.monitor.reconnectAttempts > 0;
203
+ }
199
204
  isProtocolSupported() {
200
205
  return indexOf.call(supportedProtocols, this.getProtocol()) >= 0;
201
206
  }
@@ -233,6 +238,9 @@
233
238
  const {identifier: identifier, message: message, reason: reason, reconnect: reconnect, type: type} = JSON.parse(event.data);
234
239
  switch (type) {
235
240
  case message_types.welcome:
241
+ if (this.triedToReconnect()) {
242
+ this.reconnectAttempted = true;
243
+ }
236
244
  this.monitor.recordConnect();
237
245
  return this.subscriptions.reload();
238
246
 
@@ -247,7 +255,16 @@
247
255
 
248
256
  case message_types.confirmation:
249
257
  this.subscriptions.confirmSubscription(identifier);
250
- return this.subscriptions.notify(identifier, "connected");
258
+ if (this.reconnectAttempted) {
259
+ this.reconnectAttempted = false;
260
+ return this.subscriptions.notify(identifier, "connected", {
261
+ reconnected: true
262
+ });
263
+ } else {
264
+ return this.subscriptions.notify(identifier, "connected", {
265
+ reconnected: false
266
+ });
267
+ }
251
268
 
252
269
  case message_types.rejection:
253
270
  return this.subscriptions.reject(identifier);
@@ -427,6 +444,7 @@
427
444
  this._url = url;
428
445
  this.subscriptions = new Subscriptions(this);
429
446
  this.connection = new Connection(this);
447
+ this.subprotocols = [];
430
448
  }
431
449
  get url() {
432
450
  return createWebSocketURL(this._url);
@@ -447,6 +465,9 @@
447
465
  return this.connection.open();
448
466
  }
449
467
  }
468
+ addSubProtocol(subprotocol) {
469
+ this.subprotocols = [ ...this.subprotocols, subprotocol ];
470
+ }
450
471
  }
451
472
  function createWebSocketURL(url) {
452
473
  if (typeof url === "function") {
@@ -1,6 +1,6 @@
1
1
  var adapters = {
2
- logger: self.console,
3
- WebSocket: self.WebSocket
2
+ logger: typeof console !== "undefined" ? console : undefined,
3
+ WebSocket: typeof WebSocket !== "undefined" ? WebSocket : undefined
4
4
  };
5
5
 
6
6
  var logger = {
@@ -123,7 +123,8 @@ var INTERNAL = {
123
123
  disconnect_reasons: {
124
124
  unauthorized: "unauthorized",
125
125
  invalid_request: "invalid_request",
126
- server_restart: "server_restart"
126
+ server_restart: "server_restart",
127
+ remote: "remote"
127
128
  },
128
129
  default_mount_path: "/cable",
129
130
  protocols: [ "actioncable-v1-json", "actioncable-unsupported" ]
@@ -156,11 +157,12 @@ class Connection {
156
157
  logger.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`);
157
158
  return false;
158
159
  } else {
159
- logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${protocols}`);
160
+ const socketProtocols = [ ...protocols, ...this.consumer.subprotocols || [] ];
161
+ logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${socketProtocols}`);
160
162
  if (this.webSocket) {
161
163
  this.uninstallEventHandlers();
162
164
  }
163
- this.webSocket = new adapters.WebSocket(this.consumer.url, protocols);
165
+ this.webSocket = new adapters.WebSocket(this.consumer.url, socketProtocols);
164
166
  this.installEventHandlers();
165
167
  this.monitor.start();
166
168
  return true;
@@ -202,6 +204,9 @@ class Connection {
202
204
  isActive() {
203
205
  return this.isState("open", "connecting");
204
206
  }
207
+ triedToReconnect() {
208
+ return this.monitor.reconnectAttempts > 0;
209
+ }
205
210
  isProtocolSupported() {
206
211
  return indexOf.call(supportedProtocols, this.getProtocol()) >= 0;
207
212
  }
@@ -241,6 +246,9 @@ Connection.prototype.events = {
241
246
  const {identifier: identifier, message: message, reason: reason, reconnect: reconnect, type: type} = JSON.parse(event.data);
242
247
  switch (type) {
243
248
  case message_types.welcome:
249
+ if (this.triedToReconnect()) {
250
+ this.reconnectAttempted = true;
251
+ }
244
252
  this.monitor.recordConnect();
245
253
  return this.subscriptions.reload();
246
254
 
@@ -255,7 +263,16 @@ Connection.prototype.events = {
255
263
 
256
264
  case message_types.confirmation:
257
265
  this.subscriptions.confirmSubscription(identifier);
258
- return this.subscriptions.notify(identifier, "connected");
266
+ if (this.reconnectAttempted) {
267
+ this.reconnectAttempted = false;
268
+ return this.subscriptions.notify(identifier, "connected", {
269
+ reconnected: true
270
+ });
271
+ } else {
272
+ return this.subscriptions.notify(identifier, "connected", {
273
+ reconnected: false
274
+ });
275
+ }
259
276
 
260
277
  case message_types.rejection:
261
278
  return this.subscriptions.reject(identifier);
@@ -440,6 +457,7 @@ class Consumer {
440
457
  this._url = url;
441
458
  this.subscriptions = new Subscriptions(this);
442
459
  this.connection = new Connection(this);
460
+ this.subprotocols = [];
443
461
  }
444
462
  get url() {
445
463
  return createWebSocketURL(this._url);
@@ -460,6 +478,9 @@ class Consumer {
460
478
  return this.connection.open();
461
479
  }
462
480
  }
481
+ addSubProtocol(subprotocol) {
482
+ this.subprotocols = [ ...this.subprotocols, subprotocol ];
483
+ }
463
484
  }
464
485
 
465
486
  function createWebSocketURL(url) {
@@ -4,8 +4,8 @@
4
4
  })(this, (function(exports) {
5
5
  "use strict";
6
6
  var adapters = {
7
- logger: self.console,
8
- WebSocket: self.WebSocket
7
+ logger: typeof console !== "undefined" ? console : undefined,
8
+ WebSocket: typeof WebSocket !== "undefined" ? WebSocket : undefined
9
9
  };
10
10
  var logger = {
11
11
  log(...messages) {
@@ -121,7 +121,8 @@
121
121
  disconnect_reasons: {
122
122
  unauthorized: "unauthorized",
123
123
  invalid_request: "invalid_request",
124
- server_restart: "server_restart"
124
+ server_restart: "server_restart",
125
+ remote: "remote"
125
126
  },
126
127
  default_mount_path: "/cable",
127
128
  protocols: [ "actioncable-v1-json", "actioncable-unsupported" ]
@@ -150,11 +151,12 @@
150
151
  logger.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`);
151
152
  return false;
152
153
  } else {
153
- logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${protocols}`);
154
+ const socketProtocols = [ ...protocols, ...this.consumer.subprotocols || [] ];
155
+ logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${socketProtocols}`);
154
156
  if (this.webSocket) {
155
157
  this.uninstallEventHandlers();
156
158
  }
157
- this.webSocket = new adapters.WebSocket(this.consumer.url, protocols);
159
+ this.webSocket = new adapters.WebSocket(this.consumer.url, socketProtocols);
158
160
  this.installEventHandlers();
159
161
  this.monitor.start();
160
162
  return true;
@@ -196,6 +198,9 @@
196
198
  isActive() {
197
199
  return this.isState("open", "connecting");
198
200
  }
201
+ triedToReconnect() {
202
+ return this.monitor.reconnectAttempts > 0;
203
+ }
199
204
  isProtocolSupported() {
200
205
  return indexOf.call(supportedProtocols, this.getProtocol()) >= 0;
201
206
  }
@@ -233,6 +238,9 @@
233
238
  const {identifier: identifier, message: message, reason: reason, reconnect: reconnect, type: type} = JSON.parse(event.data);
234
239
  switch (type) {
235
240
  case message_types.welcome:
241
+ if (this.triedToReconnect()) {
242
+ this.reconnectAttempted = true;
243
+ }
236
244
  this.monitor.recordConnect();
237
245
  return this.subscriptions.reload();
238
246
 
@@ -247,7 +255,16 @@
247
255
 
248
256
  case message_types.confirmation:
249
257
  this.subscriptions.confirmSubscription(identifier);
250
- return this.subscriptions.notify(identifier, "connected");
258
+ if (this.reconnectAttempted) {
259
+ this.reconnectAttempted = false;
260
+ return this.subscriptions.notify(identifier, "connected", {
261
+ reconnected: true
262
+ });
263
+ } else {
264
+ return this.subscriptions.notify(identifier, "connected", {
265
+ reconnected: false
266
+ });
267
+ }
251
268
 
252
269
  case message_types.rejection:
253
270
  return this.subscriptions.reject(identifier);
@@ -427,6 +444,7 @@
427
444
  this._url = url;
428
445
  this.subscriptions = new Subscriptions(this);
429
446
  this.connection = new Connection(this);
447
+ this.subprotocols = [];
430
448
  }
431
449
  get url() {
432
450
  return createWebSocketURL(this._url);
@@ -447,6 +465,9 @@
447
465
  return this.connection.open();
448
466
  }
449
467
  }
468
+ addSubProtocol(subprotocol) {
469
+ this.subprotocols = [ ...this.subprotocols, subprotocol ];
470
+ }
450
471
  }
451
472
  function createWebSocketURL(url) {
452
473
  if (typeof url === "function") {
@@ -2,9 +2,12 @@
2
2
 
3
3
  require "set"
4
4
  require "active_support/rescuable"
5
+ require "active_support/parameter_filter"
5
6
 
6
7
  module ActionCable
7
8
  module Channel
9
+ # = Action Cable \Channel \Base
10
+ #
8
11
  # The channel provides the basic structure of grouping behavior into logical units when communicating over the WebSocket connection.
9
12
  # You can think of a channel like a form of controller, but one that's capable of pushing content to the subscriber in addition to simply
10
13
  # responding to the subscriber's direct requests.
@@ -209,9 +212,11 @@ module ActionCable
209
212
  # Transmit a hash of data to the subscriber. The hash will automatically be wrapped in a JSON envelope with
210
213
  # the proper channel identifier marked as the recipient.
211
214
  def transmit(data, via: nil) # :doc:
212
- status = "#{self.class.name} transmitting #{data.inspect.truncate(300)}"
213
- status += " (via #{via})" if via
214
- logger.debug(status)
215
+ logger.debug do
216
+ status = "#{self.class.name} transmitting #{data.inspect.truncate(300)}"
217
+ status += " (via #{via})" if via
218
+ status
219
+ end
215
220
 
216
221
  payload = { channel_class: self.class.name, data: data, via: via }
217
222
  ActiveSupport::Notifications.instrument("transmit.action_cable", payload) do
@@ -262,7 +267,7 @@ module ActionCable
262
267
  end
263
268
 
264
269
  def dispatch_action(action, data)
265
- logger.info action_signature(action, data)
270
+ logger.debug action_signature(action, data)
266
271
 
267
272
  if method(action).arity == 1
268
273
  public_send action, data
@@ -275,12 +280,19 @@ module ActionCable
275
280
 
276
281
  def action_signature(action, data)
277
282
  (+"#{self.class.name}##{action}").tap do |signature|
278
- if (arguments = data.except("action")).any?
283
+ arguments = data.except("action")
284
+
285
+ if arguments.any?
286
+ arguments = parameter_filter.filter(arguments)
279
287
  signature << "(#{arguments.inspect})"
280
288
  end
281
289
  end
282
290
  end
283
291
 
292
+ def parameter_filter
293
+ @parameter_filter ||= ActiveSupport::ParameterFilter.new(connection.config.filter_parameters)
294
+ end
295
+
284
296
  def transmit_subscription_confirmation
285
297
  unless subscription_confirmation_sent?
286
298
  logger.debug "#{self.class.name} is transmitting the subscription confirmation"
@@ -7,7 +7,9 @@ module ActionCable
7
7
  module Broadcasting
8
8
  extend ActiveSupport::Concern
9
9
 
10
- delegate :broadcasting_for, :broadcast_to, to: :class
10
+ included do
11
+ delegate :broadcasting_for, :broadcast_to, to: :class
12
+ end
11
13
 
12
14
  module ClassMethods
13
15
  # Broadcast a hash to a unique broadcasting for this <tt>model</tt> in this channel.
@@ -4,6 +4,34 @@ require "active_support/callbacks"
4
4
 
5
5
  module ActionCable
6
6
  module Channel
7
+ # = Action Cable \Channel \Callbacks
8
+ #
9
+ # Action Cable Channel provides callback hooks that are invoked during the
10
+ # life cycle of a channel:
11
+ #
12
+ # * {before_subscribe}[rdoc-ref:ClassMethods#before_subscribe]
13
+ # * {after_subscribe}[rdoc-ref:ClassMethods#after_subscribe] (aliased as
14
+ # {on_subscribe}[rdoc-ref:ClassMethods#on_subscribe])
15
+ # * {before_unsubscribe}[rdoc-ref:ClassMethods#before_unsubscribe]
16
+ # * {after_unsubscribe}[rdoc-ref:ClassMethods#after_unsubscribe] (aliased as
17
+ # {on_unsubscribe}[rdoc-ref:ClassMethods#on_unsubscribe])
18
+ #
19
+ # ==== Example
20
+ #
21
+ # class ChatChannel < ApplicationCable::Channel
22
+ # after_subscribe :send_welcome_message, unless: :subscription_rejected?
23
+ # after_subscribe :track_subscription
24
+ #
25
+ # private
26
+ # def send_welcome_message
27
+ # broadcast_to(...)
28
+ # end
29
+ #
30
+ # def track_subscription
31
+ # # ...
32
+ # end
33
+ # end
34
+ #
7
35
  module Callbacks
8
36
  extend ActiveSupport::Concern
9
37
  include ActiveSupport::Callbacks
@@ -18,6 +46,15 @@ module ActionCable
18
46
  set_callback(:subscribe, :before, *methods, &block)
19
47
  end
20
48
 
49
+ # This callback will be triggered after the Base#subscribed method is
50
+ # called, even if the subscription was rejected with the Base#reject
51
+ # method.
52
+ #
53
+ # To trigger the callback only on successful subscriptions, use the
54
+ # Base#subscription_rejected? method:
55
+ #
56
+ # after_subscribe :my_method, unless: :subscription_rejected?
57
+ #
21
58
  def after_subscribe(*methods, &block)
22
59
  set_callback(:subscribe, :after, *methods, &block)
23
60
  end
@@ -18,8 +18,10 @@ module ActionCable
18
18
  end
19
19
  end
20
20
 
21
- # Delegates to the class's ::channel_name.
22
- delegate :channel_name, to: :class
21
+ included do
22
+ # Delegates to the class's ::channel_name.
23
+ delegate :channel_name, to: :class
24
+ end
23
25
  end
24
26
  end
25
27
  end