actioncable 7.0.7.2 → 7.1.1

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 +68 -121
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +4 -4
  5. data/app/assets/javascripts/action_cable.js +25 -4
  6. data/app/assets/javascripts/actioncable.esm.js +25 -4
  7. data/app/assets/javascripts/actioncable.js +25 -4
  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 +6 -1
  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 +3 -6
  36. data/lib/action_cable/subscription_adapter/test.rb +3 -5
  37. data/lib/action_cable/test_helper.rb +53 -22
  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 +26 -14
  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: f0b157e84c123c5c09c4d01ae257a2be69f755aa995a4fc124be0ff78d51c3f2
4
- data.tar.gz: fad558caa312b76636d2bc9fe58dfe59a6cd233b83a5e59884c3a146b2a67627
3
+ metadata.gz: aa223855ae2af9c70e27271ec03c83d04e21c180c062f471d7bc388636e7e90a
4
+ data.tar.gz: f0188f07302139302eafc4cb12f2e7dd79d9391cde0079f6192dbed309278f07
5
5
  SHA512:
6
- metadata.gz: f331494a48497d24d09b299bae351fc4d43b433c8a5a7453ac52be0ea26440172173b2a9c96189558058f5181d8b4093deac7b87f873cb64c7f1eb19d32cb8c2
7
- data.tar.gz: 98c7611eac2cec92bf2e6e2dd1b03794d5a136c5b45d74472abe3b0b93132e93c519ada3dd8d7e0de3d740dd8e100500367abf769c27ab08859cc2be936f5ca9
6
+ metadata.gz: 1c2d900a7b3a882b196b3b4ae2b490d4ab2335add9f250b869201bcff68adebd0dbc5bd8e63d437be9db7844dd0b866ce803774ff2592b87282018dd9faedfe2
7
+ data.tar.gz: be12b84cff9a8e0b089e75a97071a8e030ca10cd00fbe7d5f77e22824a583109f3ccf9ec77474cbd81fe33b5ae2c91541185657ac810e041590eaef3e40cc5cc
data/CHANGELOG.md CHANGED
@@ -1,174 +1,121 @@
1
- ## Rails 7.0.7.2 (August 22, 2023) ##
1
+ ## Rails 7.1.1 (October 11, 2023) ##
2
2
 
3
3
  * No changes.
4
4
 
5
5
 
6
- ## Rails 7.0.7.1 (August 22, 2023) ##
6
+ ## Rails 7.1.0 (October 05, 2023) ##
7
7
 
8
8
  * No changes.
9
9
 
10
10
 
11
- ## Rails 7.0.7 (August 09, 2023) ##
11
+ ## Rails 7.1.0.rc2 (October 01, 2023) ##
12
12
 
13
13
  * No changes.
14
14
 
15
15
 
16
- ## Rails 7.0.6 (June 29, 2023) ##
17
-
18
- * Fix Action Cable Redis configuration with sentinels.
19
-
20
- *Dmitriy Ivliev*
21
-
22
-
23
- ## Rails 7.0.5.1 (June 26, 2023) ##
16
+ ## Rails 7.1.0.rc1 (September 27, 2023) ##
24
17
 
25
18
  * No changes.
26
19
 
27
20
 
28
- ## Rails 7.0.5 (May 24, 2023) ##
29
-
30
- * Restore Action Cable Redis pub/sub listener on connection failure.
31
-
32
- *Vladimir Dementyev*
21
+ ## Rails 7.1.0.beta1 (September 13, 2023) ##
33
22
 
23
+ * Add a `@server` instance variable referencing the `ActionCable.server`
24
+ singleton to `ActionCable::Channel::ConnectionStub`
34
25
 
35
- ## Rails 7.0.4.3 (March 13, 2023) ##
26
+ This lets us delegate the `pubsub` and `config` method calls
27
+ to the server. This fixes `NoMethodError` errors when testing
28
+ channel logic that call `pubsub` (e.g. `stop_stream_for`).
36
29
 
37
- * No changes.
30
+ *Julian Foo*
38
31
 
32
+ * Added `health_check_path` and `health_check_application` config to
33
+ mount a given health check rack app on a given path.
34
+ Useful when mounting Action Cable standalone.
39
35
 
40
- ## Rails 7.0.4.2 (January 24, 2023) ##
41
-
42
- * No changes.
36
+ *Joé Dupuis*
43
37
 
38
+ * Introduce the `capture_broadcasts` test helper.
44
39
 
45
- ## Rails 7.0.4.1 (January 17, 2023) ##
40
+ Returns all messages broadcast in a block.
46
41
 
47
- * No changes.
48
-
49
-
50
- ## Rails 7.0.4 (September 09, 2022) ##
51
-
52
- * The Redis adapter is now compatible with redis-rb 5.0
53
-
54
- Compatibility with redis-rb 3.x was dropped.
55
-
56
- *Jean Boussier*
57
-
58
- * The Action Cable server is now mounted with `anchor: true`.
59
-
60
- This means that routes that also start with `/cable` will no longer clash with Action Cable.
42
+ ```ruby
43
+ messages = capture_broadcasts("test") do
44
+ ActionCable.server.broadcast "test", { message: "one" }
45
+ ActionCable.server.broadcast "test", { message: "two" }
46
+ end
47
+ assert_equal 2, messages.length
48
+ assert_equal({ "message" => "one" }, messages.first)
49
+ assert_equal({ "message" => "two" }, messages.last)
50
+ ```
61
51
 
62
52
  *Alex Ghiculescu*
63
53
 
54
+ * Display broadcasted messages on error message when using `assert_broadcast_on`
64
55
 
65
- ## Rails 7.0.3.1 (July 12, 2022) ##
66
-
67
- * No changes.
68
-
69
-
70
- ## Rails 7.0.3 (May 09, 2022) ##
71
-
72
- * No changes.
73
-
74
-
75
- ## Rails 7.0.2.4 (April 26, 2022) ##
76
-
77
- * No changes.
78
-
79
-
80
- ## Rails 7.0.2.3 (March 08, 2022) ##
81
-
82
- * No changes.
83
-
84
-
85
- ## Rails 7.0.2.2 (February 11, 2022) ##
86
-
87
- * No changes.
88
-
56
+ *Stéphane Robino*
89
57
 
90
- ## Rails 7.0.2.1 (February 11, 2022) ##
91
-
92
- * No changes.
58
+ * The Action Cable client now supports subprotocols to allow passing arbitrary data
59
+ to the server.
93
60
 
61
+ ```js
62
+ const consumer = ActionCable.createConsumer()
94
63
 
95
- ## Rails 7.0.2 (February 08, 2022) ##
64
+ consumer.addSubProtocol('custom-protocol')
96
65
 
97
- * No changes.
66
+ consumer.connect()
67
+ ```
98
68
 
69
+ See also:
99
70
 
100
- ## Rails 7.0.1 (January 06, 2022) ##
101
-
102
- * No changes.
71
+ * https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#subprotocols
103
72
 
73
+ *Guillaume Hain*
104
74
 
105
- ## Rails 7.0.0 (December 15, 2021) ##
106
-
107
- * No changes.
108
-
109
-
110
- ## Rails 7.0.0.rc3 (December 14, 2021) ##
111
-
112
- * No changes.
113
-
114
-
115
- ## Rails 7.0.0.rc2 (December 14, 2021) ##
116
-
117
- * No changes.
118
-
119
- ## Rails 7.0.0.rc1 (December 06, 2021) ##
120
-
121
- * The Action Cable client now ensures successful channel subscriptions:
122
-
123
- * The client maintains a set of pending subscriptions until either
124
- the server confirms the subscription or the channel is torn down.
125
- * Rectifies the race condition where an unsubscribe is rapidly followed
126
- by a subscribe (on the same channel identifier) and the requests are
127
- handled out of order by the ActionCable server, thereby ignoring the
128
- subscribe command.
129
-
130
- *Daniel Spinosa*
131
-
132
-
133
- ## Rails 7.0.0.alpha2 (September 15, 2021) ##
134
-
135
- * No changes.
75
+ * Redis pub/sub adapter now automatically reconnects when Redis connection is lost.
136
76
 
77
+ *Vladimir Dementyev*
137
78
 
138
- ## Rails 7.0.0.alpha1 (September 15, 2021) ##
79
+ * The `connected()` callback can now take a `{reconnected}` parameter to differentiate
80
+ connections from reconnections.
139
81
 
140
- * Compile ESM package that can be used directly in the browser as actioncable.esm.js.
82
+ ```js
83
+ import consumer from "./consumer"
141
84
 
142
- *DHH*
85
+ consumer.subscriptions.create("ExampleChannel", {
86
+ connected({reconnected}) {
87
+ if (reconnected) {
88
+ ...
89
+ } else {
90
+ ...
91
+ }
92
+ }
93
+ })
94
+ ```
143
95
 
144
- * Move action_cable.js to actioncable.js to match naming convention used for other Rails frameworks, and use JS console to communicate the deprecation.
96
+ *Mansa Keïta*
145
97
 
146
- *DHH*
98
+ * The Redis adapter is now compatible with redis-rb 5.0
147
99
 
148
- * Stop transpiling the UMD package generated as actioncable.js and drop the IE11 testing that relied on that.
100
+ Compatibility with redis-rb 3.x was dropped.
149
101
 
150
- *DHH*
102
+ *Jean Boussier*
151
103
 
152
- * Truncate broadcast logging messages.
104
+ * The Action Cable server is now mounted with `anchor: true`.
153
105
 
154
- *J Smith*
106
+ This means that routes that also start with `/cable` will no longer clash with Action Cable.
155
107
 
156
- * OpenSSL constants are now used for Digest computations.
108
+ *Alex Ghiculescu*
157
109
 
158
- *Dirkjan Bussink*
110
+ * `ActionCable.server.remote_connections.where(...).disconnect` now sends `disconnect` message
111
+ before closing the connection with the reconnection strategy specified (defaults to `true`).
159
112
 
160
- * The Action Cable client now includes safeguards to prevent a "thundering
161
- herd" of client reconnects after server connectivity loss:
113
+ *Vladimir Dementyev*
162
114
 
163
- * The client will wait a random amount between 1x and 3x of the stale
164
- threshold after the server's last ping before making the first
165
- reconnection attempt.
166
- * Subsequent reconnection attempts now use exponential backoff instead of
167
- logarithmic backoff. To allow the delay between reconnection attempts to
168
- increase slowly at first, the default exponentiation base is < 2.
169
- * Random jitter is applied to each delay between reconnection attempts.
115
+ * Added command callbacks to `ActionCable::Connection::Base`.
170
116
 
171
- *Jonathan Hefner*
117
+ 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.
172
118
 
119
+ *Vladimir Dementyev*
173
120
 
174
- Please check [6-1-stable](https://github.com/rails/rails/blob/6-1-stable/actioncable/CHANGELOG.md) for previous changes.
121
+ 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
 
@@ -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") {
@@ -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) {
@@ -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
@@ -2,6 +2,8 @@
2
2
 
3
3
  module ActionCable
4
4
  module Channel
5
+ # = Action Cable \Channel \Streams
6
+ #
5
7
  # Streams allow channels to route broadcastings to the subscriber. A broadcasting is, as discussed elsewhere, a pubsub queue where any data
6
8
  # placed into it is automatically sent to the clients that are connected at that time. It's purely an online queue, though. If you're not
7
9
  # streaming a broadcasting at the very moment it sends out an update, you will not get that update, even if you connect after it has been sent.