actioncable 7.0.8 → 7.1.3.2

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