@rails/actioncable 7.0.0-alpha2 → 7.0.0-rc1

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.
@@ -246,6 +246,7 @@
246
246
  return this.monitor.recordPing();
247
247
 
248
248
  case message_types.confirmation:
249
+ this.subscriptions.confirmSubscription(identifier);
249
250
  return this.subscriptions.notify(identifier, "connected");
250
251
 
251
252
  case message_types.rejection:
@@ -310,9 +311,46 @@
310
311
  return this.consumer.subscriptions.remove(this);
311
312
  }
312
313
  }
314
+ class SubscriptionGuarantor {
315
+ constructor(subscriptions) {
316
+ this.subscriptions = subscriptions;
317
+ this.pendingSubscriptions = [];
318
+ }
319
+ guarantee(subscription) {
320
+ if (this.pendingSubscriptions.indexOf(subscription) == -1) {
321
+ logger.log(`SubscriptionGuarantor guaranteeing ${subscription.identifier}`);
322
+ this.pendingSubscriptions.push(subscription);
323
+ } else {
324
+ logger.log(`SubscriptionGuarantor already guaranteeing ${subscription.identifier}`);
325
+ }
326
+ this.startGuaranteeing();
327
+ }
328
+ forget(subscription) {
329
+ logger.log(`SubscriptionGuarantor forgetting ${subscription.identifier}`);
330
+ this.pendingSubscriptions = this.pendingSubscriptions.filter((s => s !== subscription));
331
+ }
332
+ startGuaranteeing() {
333
+ this.stopGuaranteeing();
334
+ this.retrySubscribing();
335
+ }
336
+ stopGuaranteeing() {
337
+ clearTimeout(this.retryTimeout);
338
+ }
339
+ retrySubscribing() {
340
+ this.retryTimeout = setTimeout((() => {
341
+ if (this.subscriptions && typeof this.subscriptions.subscribe === "function") {
342
+ this.pendingSubscriptions.map((subscription => {
343
+ logger.log(`SubscriptionGuarantor resubscribing ${subscription.identifier}`);
344
+ this.subscriptions.subscribe(subscription);
345
+ }));
346
+ }
347
+ }), 500);
348
+ }
349
+ }
313
350
  class Subscriptions {
314
351
  constructor(consumer) {
315
352
  this.consumer = consumer;
353
+ this.guarantor = new SubscriptionGuarantor(this);
316
354
  this.subscriptions = [];
317
355
  }
318
356
  create(channelName, mixin) {
@@ -327,7 +365,7 @@
327
365
  this.subscriptions.push(subscription);
328
366
  this.consumer.ensureActiveConnection();
329
367
  this.notify(subscription, "initialized");
330
- this.sendCommand(subscription, "subscribe");
368
+ this.subscribe(subscription);
331
369
  return subscription;
332
370
  }
333
371
  remove(subscription) {
@@ -345,6 +383,7 @@
345
383
  }));
346
384
  }
347
385
  forget(subscription) {
386
+ this.guarantor.forget(subscription);
348
387
  this.subscriptions = this.subscriptions.filter((s => s !== subscription));
349
388
  return subscription;
350
389
  }
@@ -352,7 +391,7 @@
352
391
  return this.subscriptions.filter((s => s.identifier === identifier));
353
392
  }
354
393
  reload() {
355
- return this.subscriptions.map((subscription => this.sendCommand(subscription, "subscribe")));
394
+ return this.subscriptions.map((subscription => this.subscribe(subscription)));
356
395
  }
357
396
  notifyAll(callbackName, ...args) {
358
397
  return this.subscriptions.map((subscription => this.notify(subscription, callbackName, ...args)));
@@ -366,6 +405,15 @@
366
405
  }
367
406
  return subscriptions.map((subscription => typeof subscription[callbackName] === "function" ? subscription[callbackName](...args) : undefined));
368
407
  }
408
+ subscribe(subscription) {
409
+ if (this.sendCommand(subscription, "subscribe")) {
410
+ this.guarantor.guarantee(subscription);
411
+ }
412
+ }
413
+ confirmSubscription(identifier) {
414
+ logger.log(`Subscription confirmed ${identifier}`);
415
+ this.findAll(identifier).map((subscription => this.guarantor.forget(subscription)));
416
+ }
369
417
  sendCommand(subscription, command) {
370
418
  const {identifier: identifier} = subscription;
371
419
  return this.consumer.send({
@@ -429,6 +477,7 @@
429
477
  exports.Consumer = Consumer;
430
478
  exports.INTERNAL = INTERNAL;
431
479
  exports.Subscription = Subscription;
480
+ exports.SubscriptionGuarantor = SubscriptionGuarantor;
432
481
  exports.Subscriptions = Subscriptions;
433
482
  exports.adapters = adapters;
434
483
  exports.createConsumer = createConsumer;
@@ -254,6 +254,7 @@ Connection.prototype.events = {
254
254
  return this.monitor.recordPing();
255
255
 
256
256
  case message_types.confirmation:
257
+ this.subscriptions.confirmSubscription(identifier);
257
258
  return this.subscriptions.notify(identifier, "connected");
258
259
 
259
260
  case message_types.rejection:
@@ -321,9 +322,47 @@ class Subscription {
321
322
  }
322
323
  }
323
324
 
325
+ class SubscriptionGuarantor {
326
+ constructor(subscriptions) {
327
+ this.subscriptions = subscriptions;
328
+ this.pendingSubscriptions = [];
329
+ }
330
+ guarantee(subscription) {
331
+ if (this.pendingSubscriptions.indexOf(subscription) == -1) {
332
+ logger.log(`SubscriptionGuarantor guaranteeing ${subscription.identifier}`);
333
+ this.pendingSubscriptions.push(subscription);
334
+ } else {
335
+ logger.log(`SubscriptionGuarantor already guaranteeing ${subscription.identifier}`);
336
+ }
337
+ this.startGuaranteeing();
338
+ }
339
+ forget(subscription) {
340
+ logger.log(`SubscriptionGuarantor forgetting ${subscription.identifier}`);
341
+ this.pendingSubscriptions = this.pendingSubscriptions.filter((s => s !== subscription));
342
+ }
343
+ startGuaranteeing() {
344
+ this.stopGuaranteeing();
345
+ this.retrySubscribing();
346
+ }
347
+ stopGuaranteeing() {
348
+ clearTimeout(this.retryTimeout);
349
+ }
350
+ retrySubscribing() {
351
+ this.retryTimeout = setTimeout((() => {
352
+ if (this.subscriptions && typeof this.subscriptions.subscribe === "function") {
353
+ this.pendingSubscriptions.map((subscription => {
354
+ logger.log(`SubscriptionGuarantor resubscribing ${subscription.identifier}`);
355
+ this.subscriptions.subscribe(subscription);
356
+ }));
357
+ }
358
+ }), 500);
359
+ }
360
+ }
361
+
324
362
  class Subscriptions {
325
363
  constructor(consumer) {
326
364
  this.consumer = consumer;
365
+ this.guarantor = new SubscriptionGuarantor(this);
327
366
  this.subscriptions = [];
328
367
  }
329
368
  create(channelName, mixin) {
@@ -338,7 +377,7 @@ class Subscriptions {
338
377
  this.subscriptions.push(subscription);
339
378
  this.consumer.ensureActiveConnection();
340
379
  this.notify(subscription, "initialized");
341
- this.sendCommand(subscription, "subscribe");
380
+ this.subscribe(subscription);
342
381
  return subscription;
343
382
  }
344
383
  remove(subscription) {
@@ -356,6 +395,7 @@ class Subscriptions {
356
395
  }));
357
396
  }
358
397
  forget(subscription) {
398
+ this.guarantor.forget(subscription);
359
399
  this.subscriptions = this.subscriptions.filter((s => s !== subscription));
360
400
  return subscription;
361
401
  }
@@ -363,7 +403,7 @@ class Subscriptions {
363
403
  return this.subscriptions.filter((s => s.identifier === identifier));
364
404
  }
365
405
  reload() {
366
- return this.subscriptions.map((subscription => this.sendCommand(subscription, "subscribe")));
406
+ return this.subscriptions.map((subscription => this.subscribe(subscription)));
367
407
  }
368
408
  notifyAll(callbackName, ...args) {
369
409
  return this.subscriptions.map((subscription => this.notify(subscription, callbackName, ...args)));
@@ -377,6 +417,15 @@ class Subscriptions {
377
417
  }
378
418
  return subscriptions.map((subscription => typeof subscription[callbackName] === "function" ? subscription[callbackName](...args) : undefined));
379
419
  }
420
+ subscribe(subscription) {
421
+ if (this.sendCommand(subscription, "subscribe")) {
422
+ this.guarantor.guarantee(subscription);
423
+ }
424
+ }
425
+ confirmSubscription(identifier) {
426
+ logger.log(`Subscription confirmed ${identifier}`);
427
+ this.findAll(identifier).map((subscription => this.guarantor.forget(subscription)));
428
+ }
380
429
  sendCommand(subscription, command) {
381
430
  const {identifier: identifier} = subscription;
382
431
  return this.consumer.send({
@@ -439,4 +488,4 @@ function getConfig(name) {
439
488
  }
440
489
  }
441
490
 
442
- export { Connection, ConnectionMonitor, Consumer, INTERNAL, Subscription, Subscriptions, adapters, createConsumer, createWebSocketURL, getConfig, logger };
491
+ export { Connection, ConnectionMonitor, Consumer, INTERNAL, Subscription, SubscriptionGuarantor, Subscriptions, adapters, createConsumer, createWebSocketURL, getConfig, logger };
@@ -246,6 +246,7 @@
246
246
  return this.monitor.recordPing();
247
247
 
248
248
  case message_types.confirmation:
249
+ this.subscriptions.confirmSubscription(identifier);
249
250
  return this.subscriptions.notify(identifier, "connected");
250
251
 
251
252
  case message_types.rejection:
@@ -310,9 +311,46 @@
310
311
  return this.consumer.subscriptions.remove(this);
311
312
  }
312
313
  }
314
+ class SubscriptionGuarantor {
315
+ constructor(subscriptions) {
316
+ this.subscriptions = subscriptions;
317
+ this.pendingSubscriptions = [];
318
+ }
319
+ guarantee(subscription) {
320
+ if (this.pendingSubscriptions.indexOf(subscription) == -1) {
321
+ logger.log(`SubscriptionGuarantor guaranteeing ${subscription.identifier}`);
322
+ this.pendingSubscriptions.push(subscription);
323
+ } else {
324
+ logger.log(`SubscriptionGuarantor already guaranteeing ${subscription.identifier}`);
325
+ }
326
+ this.startGuaranteeing();
327
+ }
328
+ forget(subscription) {
329
+ logger.log(`SubscriptionGuarantor forgetting ${subscription.identifier}`);
330
+ this.pendingSubscriptions = this.pendingSubscriptions.filter((s => s !== subscription));
331
+ }
332
+ startGuaranteeing() {
333
+ this.stopGuaranteeing();
334
+ this.retrySubscribing();
335
+ }
336
+ stopGuaranteeing() {
337
+ clearTimeout(this.retryTimeout);
338
+ }
339
+ retrySubscribing() {
340
+ this.retryTimeout = setTimeout((() => {
341
+ if (this.subscriptions && typeof this.subscriptions.subscribe === "function") {
342
+ this.pendingSubscriptions.map((subscription => {
343
+ logger.log(`SubscriptionGuarantor resubscribing ${subscription.identifier}`);
344
+ this.subscriptions.subscribe(subscription);
345
+ }));
346
+ }
347
+ }), 500);
348
+ }
349
+ }
313
350
  class Subscriptions {
314
351
  constructor(consumer) {
315
352
  this.consumer = consumer;
353
+ this.guarantor = new SubscriptionGuarantor(this);
316
354
  this.subscriptions = [];
317
355
  }
318
356
  create(channelName, mixin) {
@@ -327,7 +365,7 @@
327
365
  this.subscriptions.push(subscription);
328
366
  this.consumer.ensureActiveConnection();
329
367
  this.notify(subscription, "initialized");
330
- this.sendCommand(subscription, "subscribe");
368
+ this.subscribe(subscription);
331
369
  return subscription;
332
370
  }
333
371
  remove(subscription) {
@@ -345,6 +383,7 @@
345
383
  }));
346
384
  }
347
385
  forget(subscription) {
386
+ this.guarantor.forget(subscription);
348
387
  this.subscriptions = this.subscriptions.filter((s => s !== subscription));
349
388
  return subscription;
350
389
  }
@@ -352,7 +391,7 @@
352
391
  return this.subscriptions.filter((s => s.identifier === identifier));
353
392
  }
354
393
  reload() {
355
- return this.subscriptions.map((subscription => this.sendCommand(subscription, "subscribe")));
394
+ return this.subscriptions.map((subscription => this.subscribe(subscription)));
356
395
  }
357
396
  notifyAll(callbackName, ...args) {
358
397
  return this.subscriptions.map((subscription => this.notify(subscription, callbackName, ...args)));
@@ -366,6 +405,15 @@
366
405
  }
367
406
  return subscriptions.map((subscription => typeof subscription[callbackName] === "function" ? subscription[callbackName](...args) : undefined));
368
407
  }
408
+ subscribe(subscription) {
409
+ if (this.sendCommand(subscription, "subscribe")) {
410
+ this.guarantor.guarantee(subscription);
411
+ }
412
+ }
413
+ confirmSubscription(identifier) {
414
+ logger.log(`Subscription confirmed ${identifier}`);
415
+ this.findAll(identifier).map((subscription => this.guarantor.forget(subscription)));
416
+ }
369
417
  sendCommand(subscription, command) {
370
418
  const {identifier: identifier} = subscription;
371
419
  return this.consumer.send({
@@ -428,6 +476,7 @@
428
476
  exports.Consumer = Consumer;
429
477
  exports.INTERNAL = INTERNAL;
430
478
  exports.Subscription = Subscription;
479
+ exports.SubscriptionGuarantor = SubscriptionGuarantor;
431
480
  exports.Subscriptions = Subscriptions;
432
481
  exports.adapters = adapters;
433
482
  exports.createConsumer = createConsumer;
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@rails/actioncable",
3
- "version": "7.0.0-alpha2",
3
+ "version": "7.0.0-rc1",
4
4
  "description": "WebSocket framework for Ruby on Rails.",
5
- "module": "app/javascript/action_cable/index.js",
6
- "main": "app/assets/javascripts/action_cable.js",
5
+ "module": "app/assets/javascripts/actioncable.esm.js",
6
+ "main": "app/assets/javascripts/actioncable.js",
7
7
  "files": [
8
8
  "app/assets/javascripts/*.js",
9
9
  "src/*.js"
package/src/connection.js CHANGED
@@ -132,6 +132,7 @@ Connection.prototype.events = {
132
132
  case message_types.ping:
133
133
  return this.monitor.recordPing()
134
134
  case message_types.confirmation:
135
+ this.subscriptions.confirmSubscription(identifier)
135
136
  return this.subscriptions.notify(identifier, "connected")
136
137
  case message_types.rejection:
137
138
  return this.subscriptions.reject(identifier)
package/src/index.js CHANGED
@@ -4,6 +4,7 @@ import Consumer, { createWebSocketURL } from "./consumer"
4
4
  import INTERNAL from "./internal"
5
5
  import Subscription from "./subscription"
6
6
  import Subscriptions from "./subscriptions"
7
+ import SubscriptionGuarantor from "./subscription_guarantor"
7
8
  import adapters from "./adapters"
8
9
  import logger from "./logger"
9
10
 
@@ -14,6 +15,7 @@ export {
14
15
  INTERNAL,
15
16
  Subscription,
16
17
  Subscriptions,
18
+ SubscriptionGuarantor,
17
19
  adapters,
18
20
  createWebSocketURL,
19
21
  logger,
@@ -0,0 +1,50 @@
1
+ import logger from "./logger"
2
+
3
+ // Responsible for ensuring channel subscribe command is confirmed, retrying until confirmation is received.
4
+ // Internal class, not intended for direct user manipulation.
5
+
6
+ class SubscriptionGuarantor {
7
+ constructor(subscriptions) {
8
+ this.subscriptions = subscriptions
9
+ this.pendingSubscriptions = []
10
+ }
11
+
12
+ guarantee(subscription) {
13
+ if(this.pendingSubscriptions.indexOf(subscription) == -1){
14
+ logger.log(`SubscriptionGuarantor guaranteeing ${subscription.identifier}`)
15
+ this.pendingSubscriptions.push(subscription)
16
+ }
17
+ else {
18
+ logger.log(`SubscriptionGuarantor already guaranteeing ${subscription.identifier}`)
19
+ }
20
+ this.startGuaranteeing()
21
+ }
22
+
23
+ forget(subscription) {
24
+ logger.log(`SubscriptionGuarantor forgetting ${subscription.identifier}`)
25
+ this.pendingSubscriptions = (this.pendingSubscriptions.filter((s) => s !== subscription))
26
+ }
27
+
28
+ startGuaranteeing() {
29
+ this.stopGuaranteeing()
30
+ this.retrySubscribing()
31
+ }
32
+
33
+ stopGuaranteeing() {
34
+ clearTimeout(this.retryTimeout)
35
+ }
36
+
37
+ retrySubscribing() {
38
+ this.retryTimeout = setTimeout(() => {
39
+ if (this.subscriptions && typeof(this.subscriptions.subscribe) === "function") {
40
+ this.pendingSubscriptions.map((subscription) => {
41
+ logger.log(`SubscriptionGuarantor resubscribing ${subscription.identifier}`)
42
+ this.subscriptions.subscribe(subscription)
43
+ })
44
+ }
45
+ }
46
+ , 500)
47
+ }
48
+ }
49
+
50
+ export default SubscriptionGuarantor
@@ -1,4 +1,6 @@
1
1
  import Subscription from "./subscription"
2
+ import SubscriptionGuarantor from "./subscription_guarantor"
3
+ import logger from "./logger"
2
4
 
3
5
  // Collection class for creating (and internally managing) channel subscriptions.
4
6
  // The only method intended to be triggered by the user is ActionCable.Subscriptions#create,
@@ -13,6 +15,7 @@ import Subscription from "./subscription"
13
15
  export default class Subscriptions {
14
16
  constructor(consumer) {
15
17
  this.consumer = consumer
18
+ this.guarantor = new SubscriptionGuarantor(this)
16
19
  this.subscriptions = []
17
20
  }
18
21
 
@@ -29,7 +32,7 @@ export default class Subscriptions {
29
32
  this.subscriptions.push(subscription)
30
33
  this.consumer.ensureActiveConnection()
31
34
  this.notify(subscription, "initialized")
32
- this.sendCommand(subscription, "subscribe")
35
+ this.subscribe(subscription)
33
36
  return subscription
34
37
  }
35
38
 
@@ -50,6 +53,7 @@ export default class Subscriptions {
50
53
  }
51
54
 
52
55
  forget(subscription) {
56
+ this.guarantor.forget(subscription)
53
57
  this.subscriptions = (this.subscriptions.filter((s) => s !== subscription))
54
58
  return subscription
55
59
  }
@@ -60,7 +64,7 @@ export default class Subscriptions {
60
64
 
61
65
  reload() {
62
66
  return this.subscriptions.map((subscription) =>
63
- this.sendCommand(subscription, "subscribe"))
67
+ this.subscribe(subscription))
64
68
  }
65
69
 
66
70
  notifyAll(callbackName, ...args) {
@@ -80,6 +84,18 @@ export default class Subscriptions {
80
84
  (typeof subscription[callbackName] === "function" ? subscription[callbackName](...args) : undefined))
81
85
  }
82
86
 
87
+ subscribe(subscription) {
88
+ if (this.sendCommand(subscription, "subscribe")) {
89
+ this.guarantor.guarantee(subscription)
90
+ }
91
+ }
92
+
93
+ confirmSubscription(identifier) {
94
+ logger.log(`Subscription confirmed ${identifier}`)
95
+ this.findAll(identifier).map((subscription) =>
96
+ this.guarantor.forget(subscription))
97
+ }
98
+
83
99
  sendCommand(subscription, command) {
84
100
  const {identifier} = subscription
85
101
  return this.consumer.send({command, identifier})
package/CHANGELOG.md DELETED
@@ -1,42 +0,0 @@
1
- ## Rails 7.0.0.alpha2 (September 15, 2021) ##
2
-
3
- * No changes.
4
-
5
-
6
- ## Rails 7.0.0.alpha1 (September 15, 2021) ##
7
-
8
- * Compile ESM package that can be used directly in the browser as actioncable.esm.js.
9
-
10
- *DHH*
11
-
12
- * Move action_cable.js to actioncable.js to match naming convention used for other Rails frameworks, and use JS console to communicate the deprecation.
13
-
14
- *DHH*
15
-
16
- * Stop transpiling the UMD package generated as actioncable.js and drop the IE11 testing that relied on that.
17
-
18
- *DHH*
19
-
20
- * Truncate broadcast logging messages.
21
-
22
- *J Smith*
23
-
24
- * OpenSSL constants are now used for Digest computations.
25
-
26
- *Dirkjan Bussink*
27
-
28
- * The Action Cable client now includes safeguards to prevent a "thundering
29
- herd" of client reconnects after server connectivity loss:
30
-
31
- * The client will wait a random amount between 1x and 3x of the stale
32
- threshold after the server's last ping before making the first
33
- reconnection attempt.
34
- * Subsequent reconnection attempts now use exponential backoff instead of
35
- logarithmic backoff. To allow the delay between reconnection attempts to
36
- increase slowly at first, the default exponentiation base is < 2.
37
- * Random jitter is applied to each delay between reconnection attempts.
38
-
39
- *Jonathan Hefner*
40
-
41
-
42
- Please check [6-1-stable](https://github.com/rails/rails/blob/6-1-stable/actioncable/CHANGELOG.md) for previous changes.