@rails/actioncable 7.0.0-alpha2 → 7.0.0-rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.