@stomp/stompjs 7.0.1 → 7.1.1

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.
package/src/client.ts CHANGED
@@ -11,7 +11,9 @@ import {
11
11
  IPublishParams,
12
12
  IStompSocket,
13
13
  messageCallbackType,
14
+ ReconnectionTimeMode,
14
15
  StompSocketState,
16
+ TickerStrategy,
15
17
  wsErrorCallbackType,
16
18
  } from './types.js';
17
19
  import { Versions } from './versions.js';
@@ -90,6 +92,33 @@ export class Client {
90
92
  */
91
93
  public reconnectDelay: number = 5000;
92
94
 
95
+ /**
96
+ * tracking the time to the next reconnection. Initialized to [Client#reconnectDelay]{@link Client#reconnectDelay}'s value and it may
97
+ * change depending on the [Client#reconnectTimeMode]{@link Client#reconnectTimeMode} setting
98
+ */
99
+ private _nextReconnectDelay: number = 0;
100
+
101
+ /**
102
+ * Maximum time to wait between reconnects, in milliseconds. Defaults to 15 minutes.
103
+ * Only relevant when [Client#reconnectTimeMode]{@link Client#reconnectTimeMode} not LINEAR (e.g., EXPONENTIAL).
104
+ * Set to 0 for no limit on wait time.
105
+ */
106
+ public maxReconnectDelay: number = 15 * 60 * 1000; // 15 minutes in ms
107
+
108
+ /**
109
+ * Reconnection wait time mode, either linear (default) or exponential.
110
+ * Note: See [Client#maxReconnectDelay]{@link Client#maxReconnectDelay} for setting the maximum delay when exponential
111
+ *
112
+ * ```javascript
113
+ * client.configure({
114
+ * reconnectTimeMode: ReconnectionTimeMode.EXPONENTIAL,
115
+ * reconnectDelay: 200, // It will wait 200, 400, 800 ms...
116
+ * maxReconnectDelay: 10000, // Optional, when provided, it will not wait more that these ms
117
+ * })
118
+ * ```
119
+ */
120
+ public reconnectTimeMode: ReconnectionTimeMode = ReconnectionTimeMode.LINEAR;
121
+
93
122
  /**
94
123
  * Incoming heartbeat interval in milliseconds. Set to 0 to disable.
95
124
  */
@@ -100,6 +129,23 @@ export class Client {
100
129
  */
101
130
  public heartbeatOutgoing: number = 10000;
102
131
 
132
+ /**
133
+ * Outgoing heartbeat strategy.
134
+ * See https://github.com/stomp-js/stompjs/pull/579
135
+ *
136
+ * Can be worker or interval strategy, but will always use `interval`
137
+ * if web workers are unavailable, for example, in a non-browser environment.
138
+ *
139
+ * Using Web Workers may work better on long-running pages
140
+ * and mobile apps, as the browser may suspend Timers in the main page.
141
+ * Try the `Worker` mode if you discover disconnects when the browser tab is in the background.
142
+ *
143
+ * When used in a JS environment, use 'worker' or 'interval' as valid values.
144
+ *
145
+ * Defaults to `interval` strategy.
146
+ */
147
+ public heartbeatStrategy: TickerStrategy = TickerStrategy.Interval;
148
+
103
149
  /**
104
150
  * This switches on a non-standard behavior while sending WebSocket packets.
105
151
  * It splits larger (text) packets into chunks of [maxWebSocketChunkSize]{@link Client#maxWebSocketChunkSize}.
@@ -221,7 +267,7 @@ export class Client {
221
267
  * This can be used to reliably fetch credentials, access token etc. from some other service
222
268
  * in an asynchronous way.
223
269
  */
224
- public beforeConnect: () => void | Promise<void>;
270
+ public beforeConnect: (client: Client) => void | Promise<void>;
225
271
 
226
272
  /**
227
273
  * Callback, invoked on every successful connection to the STOMP broker.
@@ -375,12 +421,24 @@ export class Client {
375
421
  public configure(conf: StompConfig): void {
376
422
  // bulk assign all properties to this
377
423
  (Object as any).assign(this, conf);
424
+
425
+ // Warn on incorrect maxReconnectDelay settings
426
+ if (
427
+ this.maxReconnectDelay > 0 &&
428
+ this.maxReconnectDelay < this.reconnectDelay
429
+ ) {
430
+ this.debug(
431
+ `Warning: maxReconnectDelay (${this.maxReconnectDelay}ms) is less than reconnectDelay (${this.reconnectDelay}ms). Using reconnectDelay as the maxReconnectDelay delay.`,
432
+ );
433
+ this.maxReconnectDelay = this.reconnectDelay;
434
+ }
378
435
  }
379
436
 
380
437
  /**
381
438
  * Initiate the connection with the broker.
382
439
  * If the connection breaks, as per [Client#reconnectDelay]{@link Client#reconnectDelay},
383
- * it will keep trying to reconnect.
440
+ * it will keep trying to reconnect. If the [Client#reconnectTimeMode]{@link Client#reconnectTimeMode}
441
+ * is set to EXPONENTIAL it will increase the wait time exponentially
384
442
  *
385
443
  * Call [Client#deactivate]{@link Client#deactivate} to disconnect and stop reconnection attempts.
386
444
  */
@@ -393,6 +451,7 @@ export class Client {
393
451
 
394
452
  this._changeState(ActivationState.ACTIVE);
395
453
 
454
+ this._nextReconnectDelay = this.reconnectDelay;
396
455
  this._connect();
397
456
  };
398
457
 
@@ -408,16 +467,18 @@ export class Client {
408
467
  }
409
468
 
410
469
  private async _connect(): Promise<void> {
411
- await this.beforeConnect();
470
+ await this.beforeConnect(this);
412
471
 
413
472
  if (this._stompHandler) {
414
- this.debug('There is already a stompHandler, skipping the call to connect');
473
+ this.debug(
474
+ 'There is already a stompHandler, skipping the call to connect',
475
+ );
415
476
  return;
416
477
  }
417
478
 
418
479
  if (!this.active) {
419
480
  this.debug(
420
- 'Client has been marked inactive, will not attempt to connect'
481
+ 'Client has been marked inactive, will not attempt to connect',
421
482
  );
422
483
  return;
423
484
  }
@@ -435,7 +496,7 @@ export class Client {
435
496
  // Connection not established, close the underlying socket
436
497
  // a reconnection will be attempted
437
498
  this.debug(
438
- `Connection not established in ${this.connectionTimeout}ms, closing socket`
499
+ `Connection not established in ${this.connectionTimeout}ms, closing socket`,
439
500
  );
440
501
  this.forceDisconnect();
441
502
  }, this.connectionTimeout);
@@ -453,6 +514,7 @@ export class Client {
453
514
  disconnectHeaders: this._disconnectHeaders,
454
515
  heartbeatIncoming: this.heartbeatIncoming,
455
516
  heartbeatOutgoing: this.heartbeatOutgoing,
517
+ heartbeatStrategy: this.heartbeatStrategy,
456
518
  splitLargeFrames: this.splitLargeFrames,
457
519
  maxWebSocketChunkSize: this.maxWebSocketChunkSize,
458
520
  forceBinaryWSFrames: this.forceBinaryWSFrames,
@@ -469,7 +531,7 @@ export class Client {
469
531
 
470
532
  if (!this.active) {
471
533
  this.debug(
472
- 'STOMP got connected while deactivate was issued, will disconnect now'
534
+ 'STOMP got connected while deactivate was issued, will disconnect now',
473
535
  );
474
536
  this._disposeStompHandler();
475
537
  return;
@@ -523,7 +585,7 @@ export class Client {
523
585
  } else if (this.brokerURL) {
524
586
  webSocket = new WebSocket(
525
587
  this.brokerURL,
526
- this.stompVersions.protocolVersions()
588
+ this.stompVersions.protocolVersions(),
527
589
  );
528
590
  } else {
529
591
  throw new Error('Either brokerURL or webSocketFactory must be provided');
@@ -533,12 +595,26 @@ export class Client {
533
595
  }
534
596
 
535
597
  private _schedule_reconnect(): void {
536
- if (this.reconnectDelay > 0) {
537
- this.debug(`STOMP: scheduling reconnection in ${this.reconnectDelay}ms`);
598
+ if (this._nextReconnectDelay > 0) {
599
+ this.debug(
600
+ `STOMP: scheduling reconnection in ${this._nextReconnectDelay}ms`,
601
+ );
538
602
 
539
603
  this._reconnector = setTimeout(() => {
604
+ if (this.reconnectTimeMode === ReconnectionTimeMode.EXPONENTIAL) {
605
+ this._nextReconnectDelay = this._nextReconnectDelay * 2;
606
+
607
+ // Truncated exponential backoff with a set limit unless disabled
608
+ if (this.maxReconnectDelay !== 0) {
609
+ this._nextReconnectDelay = Math.min(
610
+ this._nextReconnectDelay,
611
+ this.maxReconnectDelay,
612
+ );
613
+ }
614
+ }
615
+
540
616
  this._connect();
541
- }, this.reconnectDelay);
617
+ }, this._nextReconnectDelay);
542
618
  }
543
619
  }
544
620
 
@@ -577,6 +653,9 @@ export class Client {
577
653
 
578
654
  this._changeState(ActivationState.DEACTIVATING);
579
655
 
656
+ // Reset reconnection timer just to be safe
657
+ this._nextReconnectDelay = 0;
658
+
580
659
  // Clear if a reconnection was scheduled
581
660
  if (this._reconnector) {
582
661
  clearTimeout(this._reconnector);
@@ -747,7 +826,7 @@ export class Client {
747
826
  public subscribe(
748
827
  destination: string,
749
828
  callback: messageCallbackType,
750
- headers: StompHeaders = {}
829
+ headers: StompHeaders = {},
751
830
  ): StompSubscription {
752
831
  this._checkConnection();
753
832
  // @ts-ignore - we already checked that there is a _stompHandler, and it is connected
@@ -835,7 +914,7 @@ export class Client {
835
914
  public ack(
836
915
  messageId: string,
837
916
  subscriptionId: string,
838
- headers: StompHeaders = {}
917
+ headers: StompHeaders = {},
839
918
  ): void {
840
919
  this._checkConnection();
841
920
  // @ts-ignore - we already checked that there is a _stompHandler, and it is connected
@@ -858,7 +937,7 @@ export class Client {
858
937
  public nack(
859
938
  messageId: string,
860
939
  subscriptionId: string,
861
- headers: StompHeaders = {}
940
+ headers: StompHeaders = {},
862
941
  ): void {
863
942
  this._checkConnection();
864
943
  // @ts-ignore - we already checked that there is a _stompHandler, and it is connected
@@ -2,8 +2,6 @@
2
2
  * A Transaction is created by calling [Client#begin]{@link Client#begin}
3
3
  *
4
4
  * Part of `@stomp/stompjs`.
5
- *
6
- * TODO: Example and caveat
7
5
  */
8
6
  export interface ITransaction {
9
7
  /**
@@ -1,13 +1,16 @@
1
1
  import { StompHeaders } from './stomp-headers.js';
2
2
  import {
3
3
  ActivationState,
4
+ TickerStrategy,
4
5
  closeEventCallbackType,
5
6
  debugFnType,
6
7
  frameCallbackType,
7
8
  messageCallbackType,
9
+ ReconnectionTimeMode,
8
10
  wsErrorCallbackType,
9
11
  } from './types.js';
10
12
  import { Versions } from './versions.js';
13
+ import { Client } from './client.js';
11
14
 
12
15
  /**
13
16
  * Configuration options for STOMP Client, each key corresponds to
@@ -42,6 +45,16 @@ export class StompConfig {
42
45
  */
43
46
  public reconnectDelay?: number;
44
47
 
48
+ /**
49
+ * See [Client#maxReconnectDelay]{@link Client#maxReconnectDelay}
50
+ */
51
+ public maxReconnectDelay?: number;
52
+
53
+ /**
54
+ * See [Client#reconnectTimeMode]{@link Client#reconnectTimeMode}
55
+ */
56
+ public reconnectTimeMode?: ReconnectionTimeMode;
57
+
45
58
  /**
46
59
  * See [Client#heartbeatIncoming]{@link Client#heartbeatIncoming}.
47
60
  */
@@ -52,6 +65,11 @@ export class StompConfig {
52
65
  */
53
66
  public heartbeatOutgoing?: number;
54
67
 
68
+ /**
69
+ * See [Client#heartbeatStrategy]{@link Client#heartbeatStrategy}.
70
+ */
71
+ public heartbeatStrategy?: TickerStrategy;
72
+
55
73
  /**
56
74
  * See [Client#splitLargeFrames]{@link Client#splitLargeFrames}.
57
75
  */
@@ -100,7 +118,7 @@ export class StompConfig {
100
118
  /**
101
119
  * See [Client#beforeConnect]{@link Client#beforeConnect}.
102
120
  */
103
- public beforeConnect?: () => void | Promise<void>;
121
+ public beforeConnect?: (client: Client) => void | Promise<void>;
104
122
 
105
123
  /**
106
124
  * See [Client#onConnect]{@link Client#onConnect}.
@@ -1,3 +1,4 @@
1
+ import { augmentWebsocket } from './augment-websocket.js';
1
2
  import { BYTE } from './byte.js';
2
3
  import { Client } from './client.js';
3
4
  import { FrameImpl } from './frame-impl.js';
@@ -6,6 +7,7 @@ import { ITransaction } from './i-transaction.js';
6
7
  import { Parser } from './parser.js';
7
8
  import { StompHeaders } from './stomp-headers.js';
8
9
  import { StompSubscription } from './stomp-subscription.js';
10
+ import { Ticker } from './ticker.js';
9
11
  import {
10
12
  closeEventCallbackType,
11
13
  debugFnType,
@@ -19,7 +21,6 @@ import {
19
21
  wsErrorCallbackType,
20
22
  } from './types.js';
21
23
  import { Versions } from './versions.js';
22
- import { augmentWebsocket } from './augment-websocket.js';
23
24
 
24
25
  /**
25
26
  * The STOMP protocol handler
@@ -85,7 +86,7 @@ export class StompHandler {
85
86
  private _partialData: string;
86
87
  private _escapeHeaderValues: boolean;
87
88
  private _counter: number;
88
- private _pinger: any;
89
+ private _pinger?: Ticker;
89
90
  private _ponger: any;
90
91
  private _lastServerActivityTS: number;
91
92
 
@@ -289,12 +290,14 @@ export class StompHandler {
289
290
  if (this.heartbeatOutgoing !== 0 && serverIncoming !== 0) {
290
291
  const ttl: number = Math.max(this.heartbeatOutgoing, serverIncoming);
291
292
  this.debug(`send PING every ${ttl}ms`);
292
- this._pinger = setInterval(() => {
293
+
294
+ this._pinger = new Ticker(ttl, this._client.heartbeatStrategy, this.debug);
295
+ this._pinger.start(() => {
293
296
  if (this._webSocket.readyState === StompSocketState.OPEN) {
294
297
  this._webSocket.send(BYTE.LF);
295
298
  this.debug('>>> PING');
296
299
  }
297
- }, ttl);
300
+ });
298
301
  }
299
302
 
300
303
  if (this.heartbeatIncoming !== 0 && serverOutgoing !== 0) {
@@ -426,7 +429,7 @@ export class StompHandler {
426
429
  this._connected = false;
427
430
 
428
431
  if (this._pinger) {
429
- clearInterval(this._pinger);
432
+ this._pinger.stop();
430
433
  this._pinger = undefined;
431
434
  }
432
435
  if (this._ponger) {
package/src/ticker.ts ADDED
@@ -0,0 +1,76 @@
1
+ import { debugFnType, TickerStrategy } from './types.js';
2
+
3
+ export class Ticker {
4
+ private readonly _workerScript = `
5
+ var startTime = Date.now();
6
+ setInterval(function() {
7
+ self.postMessage(Date.now() - startTime);
8
+ }, ${this._interval});
9
+ `;
10
+
11
+ private _worker?: Worker;
12
+ private _timer?: any;
13
+
14
+ constructor(
15
+ private readonly _interval: number,
16
+ private readonly _strategy = TickerStrategy.Interval,
17
+ private readonly _debug: debugFnType) {
18
+ }
19
+
20
+ public start(tick: (elapsedTime: number) => void): void {
21
+ this.stop();
22
+
23
+ if (this.shouldUseWorker()) {
24
+ this.runWorker(tick);
25
+ } else {
26
+ this.runInterval(tick);
27
+ }
28
+ }
29
+
30
+ public stop(): void {
31
+ this.disposeWorker();
32
+ this.disposeInterval();
33
+ }
34
+
35
+ private shouldUseWorker(): boolean {
36
+ return typeof(Worker) !== 'undefined' && this._strategy === TickerStrategy.Worker
37
+ }
38
+
39
+ private runWorker(tick: (elapsedTime: number) => void): void {
40
+ this._debug('Using runWorker for outgoing pings');
41
+ if (!this._worker) {
42
+ this._worker = new Worker(
43
+ URL.createObjectURL(
44
+ new Blob([this._workerScript], { type: 'text/javascript' })
45
+ )
46
+ );
47
+ this._worker.onmessage = (message) => tick(message.data);
48
+ }
49
+ }
50
+
51
+ private runInterval(tick: (elapsedTime: number) => void): void {
52
+ this._debug('Using runInterval for outgoing pings');
53
+ if (!this._timer) {
54
+ const startTime = Date.now();
55
+ this._timer = setInterval(() => {
56
+ tick(Date.now() - startTime);
57
+ }, this._interval);
58
+ }
59
+ }
60
+
61
+ private disposeWorker(): void {
62
+ if (this._worker) {
63
+ this._worker.terminate();
64
+ delete this._worker;
65
+ this._debug('Outgoing ping disposeWorker');
66
+ }
67
+ }
68
+
69
+ private disposeInterval(): void {
70
+ if (this._timer) {
71
+ clearInterval(this._timer);
72
+ delete this._timer;
73
+ this._debug('Outgoing ping disposeInterval');
74
+ }
75
+ }
76
+ }
package/src/types.ts CHANGED
@@ -157,6 +157,22 @@ export enum ActivationState {
157
157
  INACTIVE,
158
158
  }
159
159
 
160
+ /**
161
+ * Possible reconnection wait time modes
162
+ */
163
+ export enum ReconnectionTimeMode {
164
+ LINEAR,
165
+ EXPONENTIAL
166
+ }
167
+
168
+ /**
169
+ * Possible ticker strategies for outgoing heartbeat ping
170
+ */
171
+ export enum TickerStrategy {
172
+ Interval = 'interval',
173
+ Worker = 'worker'
174
+ }
175
+
160
176
  /**
161
177
  * @internal
162
178
  */
@@ -167,6 +183,7 @@ export interface IStomptHandlerConfig {
167
183
  disconnectHeaders: StompHeaders;
168
184
  heartbeatIncoming: number;
169
185
  heartbeatOutgoing: number;
186
+ heartbeatStrategy: TickerStrategy;
170
187
  splitLargeFrames: boolean;
171
188
  maxWebSocketChunkSize: number;
172
189
  forceBinaryWSFrames: boolean;