actioncable 7.0.8.6 → 7.1.0.beta1

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 +58 -166
  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 +16 -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 +18 -5
  16. data/lib/action_cable/connection/callbacks.rb +51 -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 +31 -19
  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: a13038733b51f6687ee76e037bdbe9563c04facd3ab8d245b7770a4d3fe593b6
4
- data.tar.gz: a83a614bf1d839ab6206a0fc639956b1e9f124e0274f5e10e90b1d588f923f90
3
+ metadata.gz: ccb9a2f59fc62c2ab7db13d614444a13b987954a4ccf840c0dc63a232a3af0c2
4
+ data.tar.gz: c5d380823470f2b257050785a1e2d65a734bce3d89589e2c0d47f761755e28e4
5
5
  SHA512:
6
- metadata.gz: a5a7dfc3e3d01560f8c0fe8c9b068cf82465521edb8bb8058ec2c64e7249665f02893850bb73fc769a8d23892bc90050fad64d612e055a6329254edb5803d1a3
7
- data.tar.gz: 81940860ad410356f4cbc930ec0c25454c2b21ba471b2a5c4df5294627a72dbb4d365fc3e63a5ce53b13b4712c3e68e9fbbc4164b9a694f13a9e5267c19da579
6
+ metadata.gz: ed5b02acbf05dcc57a03b420019afd733d0f8ce867ec55be7b6f7fafdd97083bfe903808f34b5193f451b67d4bffee1d96e586768c1be3c9a85a4763f9176efe
7
+ data.tar.gz: 8a5eff431fae0f83c0962e67aaaeabdee3dc7235484a005c94dd39ddaf523c5c94139e248085910cfff239c9c46d5c8b1c197b636545458b5d119c991512b7eb
data/CHANGELOG.md CHANGED
@@ -1,88 +1,79 @@
1
- ## Rails 7.0.8.6 (October 23, 2024) ##
1
+ ## Rails 7.1.0.beta1 (September 13, 2023) ##
2
2
 
3
- * No changes.
3
+ * Add a `@server` instance variable referencing the `ActionCable.server`
4
+ singleton to `ActionCable::Channel::ConnectionStub`
4
5
 
6
+ This lets us delegate the `pubsub` and `config` method calls
7
+ to the server. This fixes `NoMethodError` errors when testing
8
+ channel logic that call `pubsub` (e.g. `stop_stream_for`).
5
9
 
6
- ## Rails 7.0.8.5 (October 15, 2024) ##
10
+ *Julian Foo*
7
11
 
8
- * No changes.
12
+ * Added `health_check_path` and `health_check_application` config to
13
+ mount a given health check rack app on a given path.
14
+ Useful when mounting Action Cable standalone.
9
15
 
16
+ *Joé Dupuis*
10
17
 
11
- ## Rails 7.0.8.4 (June 04, 2024) ##
18
+ * Introduce the `capture_broadcasts` test helper.
12
19
 
13
- * No changes.
20
+ Returns all messages broadcast in a block.
14
21
 
22
+ ```ruby
23
+ messages = capture_broadcasts("test") do
24
+ ActionCable.server.broadcast "test", { message: "one" }
25
+ ActionCable.server.broadcast "test", { message: "two" }
26
+ end
27
+ assert_equal 2, messages.length
28
+ assert_equal({ "message" => "one" }, messages.first)
29
+ assert_equal({ "message" => "two" }, messages.last)
30
+ ```
15
31
 
16
- ## Rails 7.0.8.3 (May 17, 2024) ##
17
-
18
- * No changes.
19
-
20
-
21
- ## Rails 7.0.8.2 (May 16, 2024) ##
22
-
23
- * No changes.
24
-
25
-
26
- ## Rails 7.0.8.1 (February 21, 2024) ##
27
-
28
- * No changes.
29
-
30
-
31
- ## Rails 7.0.8 (September 09, 2023) ##
32
-
33
- * No changes.
34
-
35
-
36
- ## Rails 7.0.7.2 (August 22, 2023) ##
37
-
38
- * No changes.
39
-
40
-
41
- ## Rails 7.0.7.1 (August 22, 2023) ##
42
-
43
- * No changes.
44
-
45
-
46
- ## Rails 7.0.7 (August 09, 2023) ##
47
-
48
- * No changes.
32
+ *Alex Ghiculescu*
49
33
 
34
+ * Display broadcasted messages on error message when using `assert_broadcast_on`
50
35
 
51
- ## Rails 7.0.6 (June 29, 2023) ##
36
+ *Stéphane Robino*
52
37
 
53
- * Fix Action Cable Redis configuration with sentinels.
38
+ * The Action Cable client now supports subprotocols to allow passing arbitrary data
39
+ to the server.
54
40
 
55
- *Dmitriy Ivliev*
41
+ ```js
42
+ const consumer = ActionCable.createConsumer()
56
43
 
44
+ consumer.addSubProtocol('custom-protocol')
57
45
 
58
- ## Rails 7.0.5.1 (June 26, 2023) ##
46
+ consumer.connect()
47
+ ```
59
48
 
60
- * No changes.
49
+ See also:
61
50
 
51
+ * https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#subprotocols
62
52
 
63
- ## Rails 7.0.5 (May 24, 2023) ##
53
+ *Guillaume Hain*
64
54
 
65
- * Restore Action Cable Redis pub/sub listener on connection failure.
55
+ * Redis pub/sub adapter now automatically reconnects when Redis connection is lost.
66
56
 
67
57
  *Vladimir Dementyev*
68
58
 
59
+ * The `connected()` callback can now take a `{reconnected}` parameter to differentiate
60
+ connections from reconnections.
69
61
 
70
- ## Rails 7.0.4.3 (March 13, 2023) ##
62
+ ```js
63
+ import consumer from "./consumer"
71
64
 
72
- * No changes.
65
+ consumer.subscriptions.create("ExampleChannel", {
66
+ connected({reconnected}) {
67
+ if (reconnected) {
68
+ ...
69
+ } else {
70
+ ...
71
+ }
72
+ }
73
+ })
74
+ ```
73
75
 
74
-
75
- ## Rails 7.0.4.2 (January 24, 2023) ##
76
-
77
- * No changes.
78
-
79
-
80
- ## Rails 7.0.4.1 (January 17, 2023) ##
81
-
82
- * No changes.
83
-
84
-
85
- ## Rails 7.0.4 (September 09, 2022) ##
76
+ *Mansa Keïta*
86
77
 
87
78
  * The Redis adapter is now compatible with redis-rb 5.0
88
79
 
@@ -96,114 +87,15 @@
96
87
 
97
88
  *Alex Ghiculescu*
98
89
 
90
+ * `ActionCable.server.remote_connections.where(...).disconnect` now sends `disconnect` message
91
+ before closing the connection with the reconnection strategy specified (defaults to `true`).
99
92
 
100
- ## Rails 7.0.3.1 (July 12, 2022) ##
101
-
102
- * No changes.
103
-
104
-
105
- ## Rails 7.0.3 (May 09, 2022) ##
106
-
107
- * No changes.
108
-
109
-
110
- ## Rails 7.0.2.4 (April 26, 2022) ##
111
-
112
- * No changes.
113
-
114
-
115
- ## Rails 7.0.2.3 (March 08, 2022) ##
116
-
117
- * No changes.
118
-
119
-
120
- ## Rails 7.0.2.2 (February 11, 2022) ##
121
-
122
- * No changes.
123
-
124
-
125
- ## Rails 7.0.2.1 (February 11, 2022) ##
126
-
127
- * No changes.
128
-
129
-
130
- ## Rails 7.0.2 (February 08, 2022) ##
131
-
132
- * No changes.
133
-
134
-
135
- ## Rails 7.0.1 (January 06, 2022) ##
136
-
137
- * No changes.
138
-
139
-
140
- ## Rails 7.0.0 (December 15, 2021) ##
141
-
142
- * No changes.
143
-
144
-
145
- ## Rails 7.0.0.rc3 (December 14, 2021) ##
146
-
147
- * No changes.
148
-
149
-
150
- ## Rails 7.0.0.rc2 (December 14, 2021) ##
151
-
152
- * No changes.
153
-
154
- ## Rails 7.0.0.rc1 (December 06, 2021) ##
155
-
156
- * The Action Cable client now ensures successful channel subscriptions:
157
-
158
- * The client maintains a set of pending subscriptions until either
159
- the server confirms the subscription or the channel is torn down.
160
- * Rectifies the race condition where an unsubscribe is rapidly followed
161
- by a subscribe (on the same channel identifier) and the requests are
162
- handled out of order by the ActionCable server, thereby ignoring the
163
- subscribe command.
164
-
165
- *Daniel Spinosa*
166
-
167
-
168
- ## Rails 7.0.0.alpha2 (September 15, 2021) ##
169
-
170
- * No changes.
171
-
172
-
173
- ## Rails 7.0.0.alpha1 (September 15, 2021) ##
174
-
175
- * Compile ESM package that can be used directly in the browser as actioncable.esm.js.
176
-
177
- *DHH*
178
-
179
- * Move action_cable.js to actioncable.js to match naming convention used for other Rails frameworks, and use JS console to communicate the deprecation.
180
-
181
- *DHH*
182
-
183
- * Stop transpiling the UMD package generated as actioncable.js and drop the IE11 testing that relied on that.
184
-
185
- *DHH*
186
-
187
- * Truncate broadcast logging messages.
188
-
189
- *J Smith*
190
-
191
- * OpenSSL constants are now used for Digest computations.
192
-
193
- *Dirkjan Bussink*
194
-
195
- * The Action Cable client now includes safeguards to prevent a "thundering
196
- herd" of client reconnects after server connectivity loss:
93
+ *Vladimir Dementyev*
197
94
 
198
- * The client will wait a random amount between 1x and 3x of the stale
199
- threshold after the server's last ping before making the first
200
- reconnection attempt.
201
- * Subsequent reconnection attempts now use exponential backoff instead of
202
- logarithmic backoff. To allow the delay between reconnection attempts to
203
- increase slowly at first, the default exponentiation base is < 2.
204
- * Random jitter is applied to each delay between reconnection attempts.
95
+ * Added command callbacks to `ActionCable::Connection::Base`.
205
96
 
206
- *Jonathan Hefner*
97
+ 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.
207
98
 
99
+ *Vladimir Dementyev*
208
100
 
209
- Please check [6-1-stable](https://github.com/rails/rails/blob/6-1-stable/actioncable/CHANGELOG.md) for previous changes.
101
+ 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,22 @@ 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 hooks during the life cycle of a channel subscription.
10
+ # Callbacks allow triggering logic during this cycle. Available callbacks are:
11
+ #
12
+ # * <tt>before_subscribe</tt>
13
+ # * <tt>after_subscribe</tt> (also aliased as: <tt>on_subscribe</tt>)
14
+ # * <tt>before_unsubscribe</tt>
15
+ # * <tt>after_unsubscribe</tt> (also aliased as: <tt>on_unsubscribe</tt>)
16
+ #
17
+ # NOTE: the <tt>after_subscribe</tt> callback is triggered whenever
18
+ # the <tt>subscribed</tt> method is called, even if subscription was rejected
19
+ # with the <tt>reject</tt> method.
20
+ # To trigger <tt>after_subscribe</tt> only on successful subscriptions,
21
+ # use <tt>after_subscribe :my_method_name, unless: :subscription_rejected?</tt>
22
+ #
7
23
  module Callbacks
8
24
  extend ActiveSupport::Concern
9
25
  include ActiveSupport::Callbacks
@@ -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.
@@ -15,6 +15,8 @@ module ActionCable
15
15
  end
16
16
  end
17
17
 
18
+ # = Action Cable \Channel Stub
19
+ #
18
20
  # Stub +stream_from+ to track streams for the channel.
19
21
  # Add public aliases for +subscription_confirmation_sent?+ and
20
22
  # +subscription_rejected?+.
@@ -45,9 +47,12 @@ module ActionCable
45
47
  end
46
48
 
47
49
  class ConnectionStub
48
- attr_reader :transmissions, :identifiers, :subscriptions, :logger
50
+ attr_reader :server, :transmissions, :identifiers, :subscriptions, :logger
51
+
52
+ delegate :pubsub, :config, to: :server
49
53
 
50
54
  def initialize(identifiers = {})
55
+ @server = ActionCable.server
51
56
  @transmissions = []
52
57
 
53
58
  identifiers.each do |identifier, val|