@stomp/stompjs 7.1.1 → 7.2.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.
@@ -499,13 +499,13 @@
499
499
  this.disposeInterval();
500
500
  }
501
501
  shouldUseWorker() {
502
- return typeof (Worker) !== 'undefined' && this._strategy === exports.TickerStrategy.Worker;
502
+ return (typeof Worker !== 'undefined' && this._strategy === exports.TickerStrategy.Worker);
503
503
  }
504
504
  runWorker(tick) {
505
505
  this._debug('Using runWorker for outgoing pings');
506
506
  if (!this._worker) {
507
507
  this._worker = new Worker(URL.createObjectURL(new Blob([this._workerScript], { type: 'text/javascript' })));
508
- this._worker.onmessage = (message) => tick(message.data);
508
+ this._worker.onmessage = message => tick(message.data);
509
509
  }
510
510
  }
511
511
  runInterval(tick) {
@@ -671,6 +671,7 @@
671
671
  this.connectHeaders = config.connectHeaders;
672
672
  this.disconnectHeaders = config.disconnectHeaders;
673
673
  this.heartbeatIncoming = config.heartbeatIncoming;
674
+ this.heartbeatToleranceMultiplier = config.heartbeatGracePeriods;
674
675
  this.heartbeatOutgoing = config.heartbeatOutgoing;
675
676
  this.splitLargeFrames = config.splitLargeFrames;
676
677
  this.maxWebSocketChunkSize = config.maxWebSocketChunkSize;
@@ -686,6 +687,8 @@
686
687
  this.onUnhandledMessage = config.onUnhandledMessage;
687
688
  this.onUnhandledReceipt = config.onUnhandledReceipt;
688
689
  this.onUnhandledFrame = config.onUnhandledFrame;
690
+ this.onHeartbeatReceived = config.onHeartbeatReceived;
691
+ this.onHeartbeatLost = config.onHeartbeatLost;
689
692
  }
690
693
  start() {
691
694
  const parser = new Parser(
@@ -702,6 +705,7 @@
702
705
  // On Incoming Ping
703
706
  () => {
704
707
  this.debug('<<< PONG');
708
+ this.onHeartbeatReceived();
705
709
  });
706
710
  this._webSocket.onmessage = (evt) => {
707
711
  this.debug('Received data');
@@ -766,9 +770,10 @@
766
770
  this.debug(`check PONG every ${ttl}ms`);
767
771
  this._ponger = setInterval(() => {
768
772
  const delta = Date.now() - this._lastServerActivityTS;
769
- // We wait twice the TTL to be flexible on window's setInterval calls
770
- if (delta > ttl * 2) {
773
+ // We wait multiple grace periods to be flexible on window's setInterval calls
774
+ if (delta > ttl * this.heartbeatToleranceMultiplier) {
771
775
  this.debug(`did not receive server activity for the last ${delta}ms`);
776
+ this.onHeartbeatLost();
772
777
  this._closeOrDiscardWebsocket();
773
778
  }
774
779
  }, ttl);
@@ -972,16 +977,52 @@
972
977
  * STOMP Client Class.
973
978
  *
974
979
  * Part of `@stomp/stompjs`.
980
+ *
981
+ * This class provides a robust implementation for connecting to and interacting with a
982
+ * STOMP-compliant messaging broker over WebSocket. It supports STOMP versions 1.2, 1.1, and 1.0.
983
+ *
984
+ * Features:
985
+ * - Handles automatic reconnections.
986
+ * - Supports heartbeat mechanisms to detect and report communication failures.
987
+ * - Allows customization of connection and WebSocket behaviors through configurations.
988
+ * - Compatible with both browser environments and Node.js with polyfill support for WebSocket.
975
989
  */
976
990
  class Client {
977
991
  /**
978
- * Underlying WebSocket instance, READONLY.
992
+ * Provides access to the underlying WebSocket instance.
993
+ * This property is **read-only**.
994
+ *
995
+ * Example:
996
+ * ```javascript
997
+ * const webSocket = client.webSocket;
998
+ * if (webSocket) {
999
+ * console.log('WebSocket is connected:', webSocket.readyState === WebSocket.OPEN);
1000
+ * }
1001
+ * ```
1002
+ *
1003
+ * **Caution:**
1004
+ * Directly interacting with the WebSocket instance (e.g., sending or receiving frames)
1005
+ * can interfere with the proper functioning of this library. Such actions may cause
1006
+ * unexpected behavior, disconnections, or invalid state in the library's internal mechanisms.
1007
+ *
1008
+ * Instead, use the library's provided methods to manage STOMP communication.
1009
+ *
1010
+ * @returns The WebSocket instance used by the STOMP handler, or `undefined` if not connected.
979
1011
  */
980
1012
  get webSocket() {
981
1013
  return this._stompHandler?._webSocket;
982
1014
  }
983
1015
  /**
984
- * Disconnection headers.
1016
+ * Allows customization of the disconnection headers.
1017
+ *
1018
+ * Any changes made during an active session will also be applied immediately.
1019
+ *
1020
+ * Example:
1021
+ * ```javascript
1022
+ * client.disconnectHeaders = {
1023
+ * receipt: 'custom-receipt-id'
1024
+ * };
1025
+ * ```
985
1026
  */
986
1027
  get disconnectHeaders() {
987
1028
  return this._disconnectHeaders;
@@ -993,19 +1034,53 @@
993
1034
  }
994
1035
  }
995
1036
  /**
996
- * `true` if there is an active connection to STOMP Broker
1037
+ * Indicates whether there is an active connection to the STOMP broker.
1038
+ *
1039
+ * Usage:
1040
+ * ```javascript
1041
+ * if (client.connected) {
1042
+ * console.log('Client is connected to the broker.');
1043
+ * } else {
1044
+ * console.log('No connection to the broker.');
1045
+ * }
1046
+ * ```
1047
+ *
1048
+ * @returns `true` if the client is currently connected, `false` otherwise.
997
1049
  */
998
1050
  get connected() {
999
1051
  return !!this._stompHandler && this._stompHandler.connected;
1000
1052
  }
1001
1053
  /**
1002
- * version of STOMP protocol negotiated with the server, READONLY
1054
+ * The version of the STOMP protocol negotiated with the server during connection.
1055
+ *
1056
+ * This is a **read-only** property and reflects the negotiated protocol version after
1057
+ * a successful connection.
1058
+ *
1059
+ * Example:
1060
+ * ```javascript
1061
+ * console.log('Connected STOMP version:', client.connectedVersion);
1062
+ * ```
1063
+ *
1064
+ * @returns The negotiated STOMP protocol version or `undefined` if not connected.
1003
1065
  */
1004
1066
  get connectedVersion() {
1005
1067
  return this._stompHandler ? this._stompHandler.connectedVersion : undefined;
1006
1068
  }
1007
1069
  /**
1008
- * if the client is active (connected or going to reconnect)
1070
+ * Indicates whether the client is currently active.
1071
+ *
1072
+ * A client is considered active if it is connected or actively attempting to reconnect.
1073
+ *
1074
+ * Example:
1075
+ * ```javascript
1076
+ * if (client.active) {
1077
+ * console.log('The client is active.');
1078
+ * } else {
1079
+ * console.log('The client is inactive.');
1080
+ * }
1081
+ * ```
1082
+ *
1083
+ * @returns `true` if the client is active, otherwise `false`.
1009
1084
  */
1010
1085
  get active() {
1011
1086
  return this.state === exports.ActivationState.ACTIVE;
@@ -1015,129 +1090,217 @@
1015
1090
  this.onChangeState(state);
1016
1091
  }
1017
1092
  /**
1018
- * Create an instance.
1093
+ * Constructs a new STOMP client instance.
1094
+ *
1095
+ * The constructor initializes default values and sets up no-op callbacks for all events.
1096
+ * Configuration can be passed during construction, or updated later using `configure`.
1097
+ *
1098
+ * Example:
1099
+ * ```javascript
1100
+ * const client = new Client({
1101
+ * brokerURL: 'wss://broker.example.com',
1102
+ * reconnectDelay: 5000
1103
+ * });
1104
+ * ```
1105
+ *
1106
+ * @param conf Optional configuration object to initialize the client with.
1019
1107
  */
1020
1108
  constructor(conf = {}) {
1021
1109
  /**
1022
- * STOMP versions to attempt during STOMP handshake. By default, versions `1.2`, `1.1`, and `1.0` are attempted.
1110
+ * STOMP protocol versions to use during the handshake. By default, the client will attempt
1111
+ * versions `1.2`, `1.1`, and `1.0` in descending order of preference.
1023
1112
  *
1024
1113
  * Example:
1025
1114
  * ```javascript
1026
- * // Try only versions 1.1 and 1.0
1027
- * client.stompVersions = new Versions(['1.1', '1.0'])
1115
+ * // Configure the client to only use versions 1.1 and 1.0
1116
+ * client.stompVersions = new Versions(['1.1', '1.0']);
1028
1117
  * ```
1029
1118
  */
1030
1119
  this.stompVersions = Versions.default;
1031
1120
  /**
1032
- * Will retry if Stomp connection is not established in specified milliseconds.
1033
- * Default 0, which switches off automatic reconnection.
1121
+ * Timeout for establishing STOMP connection, in milliseconds.
1122
+ *
1123
+ * If the connection is not established within this period, the attempt will fail.
1124
+ * The default is `0`, meaning no timeout is set for connection attempts.
1125
+ *
1126
+ * Example:
1127
+ * ```javascript
1128
+ * client.connectionTimeout = 5000; // Fail connection if not established in 5 seconds
1129
+ * ```
1034
1130
  */
1035
1131
  this.connectionTimeout = 0;
1036
1132
  /**
1037
- * automatically reconnect with delay in milliseconds, set to 0 to disable.
1133
+ * Delay (in milliseconds) between reconnection attempts if the connection drops.
1134
+ *
1135
+ * Set to `0` to disable automatic reconnections. The default value is `5000` ms (5 seconds).
1136
+ *
1137
+ * Example:
1138
+ * ```javascript
1139
+ * client.reconnectDelay = 3000; // Attempt reconnection every 3 seconds
1140
+ * client.reconnectDelay = 0; // Disable automatic reconnection
1141
+ * ```
1038
1142
  */
1039
1143
  this.reconnectDelay = 5000;
1040
1144
  /**
1041
- * tracking the time to the next reconnection. Initialized to [Client#reconnectDelay]{@link Client#reconnectDelay}'s value and it may
1042
- * change depending on the [Client#reconnectTimeMode]{@link Client#reconnectTimeMode} setting
1145
+ * The next reconnection delay, used internally.
1146
+ * Initialized to the value of [Client#reconnectDelay]{@link Client#reconnectDelay}, and it may
1147
+ * dynamically change based on [Client#reconnectTimeMode]{@link Client#reconnectTimeMode}.
1043
1148
  */
1044
1149
  this._nextReconnectDelay = 0;
1045
1150
  /**
1046
- * Maximum time to wait between reconnects, in milliseconds. Defaults to 15 minutes.
1047
- * Only relevant when [Client#reconnectTimeMode]{@link Client#reconnectTimeMode} not LINEAR (e.g., EXPONENTIAL).
1048
- * Set to 0 for no limit on wait time.
1151
+ * Maximum delay (in milliseconds) between reconnection attempts when using exponential backoff.
1152
+ *
1153
+ * Default is 15 minutes (`15 * 60 * 1000` milliseconds). If `0`, there will be no upper limit.
1154
+ *
1155
+ * Example:
1156
+ * ```javascript
1157
+ * client.maxReconnectDelay = 10000; // Maximum wait time is 10 seconds
1158
+ * ```
1049
1159
  */
1050
- this.maxReconnectDelay = 15 * 60 * 1000; // 15 minutes in ms
1160
+ this.maxReconnectDelay = 15 * 60 * 1000;
1051
1161
  /**
1052
- * Reconnection wait time mode, either linear (default) or exponential.
1053
- * Note: See [Client#maxReconnectDelay]{@link Client#maxReconnectDelay} for setting the maximum delay when exponential
1162
+ * Mode for determining the time interval between reconnection attempts.
1163
+ *
1164
+ * Available modes:
1165
+ * - `ReconnectionTimeMode.LINEAR` (default): Fixed delays between reconnection attempts.
1166
+ * - `ReconnectionTimeMode.EXPONENTIAL`: Delay doubles after each attempt, capped by [maxReconnectDelay]{@link Client#maxReconnectDelay}.
1054
1167
  *
1168
+ * Example:
1055
1169
  * ```javascript
1056
- * client.configure({
1057
- * reconnectTimeMode: ReconnectionTimeMode.EXPONENTIAL,
1058
- * reconnectDelay: 200, // It will wait 200, 400, 800 ms...
1059
- * maxReconnectDelay: 10000, // Optional, when provided, it will not wait more that these ms
1060
- * })
1170
+ * client.reconnectTimeMode = ReconnectionTimeMode.EXPONENTIAL;
1171
+ * client.reconnectDelay = 200; // Initial delay of 200 ms, doubles with each attempt
1172
+ * client.maxReconnectDelay = 2 * 60 * 1000; // Cap delay at 10 minutes
1061
1173
  * ```
1062
1174
  */
1063
1175
  this.reconnectTimeMode = exports.ReconnectionTimeMode.LINEAR;
1064
1176
  /**
1065
- * Incoming heartbeat interval in milliseconds. Set to 0 to disable.
1177
+ * Interval (in milliseconds) for receiving heartbeat signals from the server.
1178
+ *
1179
+ * Specifies the expected frequency of heartbeats sent by the server. Set to `0` to disable.
1180
+ *
1181
+ * Example:
1182
+ * ```javascript
1183
+ * client.heartbeatIncoming = 10000; // Expect a heartbeat every 10 seconds
1184
+ * ```
1066
1185
  */
1067
1186
  this.heartbeatIncoming = 10000;
1068
1187
  /**
1069
- * Outgoing heartbeat interval in milliseconds. Set to 0 to disable.
1188
+ * Multiplier for adjusting tolerance when processing heartbeat signals.
1189
+ *
1190
+ * Tolerance level is calculated using the multiplier:
1191
+ * `tolerance = heartbeatIncoming * heartbeatToleranceMultiplier`.
1192
+ * This helps account for delays in network communication or variations in timings.
1193
+ *
1194
+ * Default value is `2`.
1195
+ *
1196
+ * Example:
1197
+ * ```javascript
1198
+ * client.heartbeatToleranceMultiplier = 2.5; // Tolerates longer delays
1199
+ * ```
1200
+ */
1201
+ this.heartbeatToleranceMultiplier = 2;
1202
+ /**
1203
+ * Interval (in milliseconds) for sending heartbeat signals to the server.
1204
+ *
1205
+ * Specifies how frequently heartbeats should be sent to the server. Set to `0` to disable.
1206
+ *
1207
+ * Example:
1208
+ * ```javascript
1209
+ * client.heartbeatOutgoing = 5000; // Send a heartbeat every 5 seconds
1210
+ * ```
1070
1211
  */
1071
1212
  this.heartbeatOutgoing = 10000;
1072
1213
  /**
1073
- * Outgoing heartbeat strategy.
1074
- * See https://github.com/stomp-js/stompjs/pull/579
1075
- *
1076
- * Can be worker or interval strategy, but will always use `interval`
1077
- * if web workers are unavailable, for example, in a non-browser environment.
1214
+ * Strategy for sending outgoing heartbeats.
1078
1215
  *
1079
- * Using Web Workers may work better on long-running pages
1080
- * and mobile apps, as the browser may suspend Timers in the main page.
1081
- * Try the `Worker` mode if you discover disconnects when the browser tab is in the background.
1216
+ * Options:
1217
+ * - `TickerStrategy.Worker`: Uses Web Workers for sending heartbeats (recommended for long-running or background sessions).
1218
+ * - `TickerStrategy.Interval`: Uses standard JavaScript `setInterval` (default).
1082
1219
  *
1083
- * When used in a JS environment, use 'worker' or 'interval' as valid values.
1220
+ * Note:
1221
+ * - If Web Workers are unavailable (e.g., in Node.js), the `Interval` strategy is used automatically.
1222
+ * - Web Workers are preferable in browsers for reducing disconnects when tabs are in the background.
1084
1223
  *
1085
- * Defaults to `interval` strategy.
1224
+ * Example:
1225
+ * ```javascript
1226
+ * client.heartbeatStrategy = TickerStrategy.Worker;
1227
+ * ```
1086
1228
  */
1087
1229
  this.heartbeatStrategy = exports.TickerStrategy.Interval;
1088
1230
  /**
1089
- * This switches on a non-standard behavior while sending WebSocket packets.
1090
- * It splits larger (text) packets into chunks of [maxWebSocketChunkSize]{@link Client#maxWebSocketChunkSize}.
1091
- * Only Java Spring brokers seem to support this mode.
1231
+ * Enables splitting of large text WebSocket frames into smaller chunks.
1092
1232
  *
1093
- * WebSockets, by itself, split large (text) packets,
1094
- * so it is not needed with a truly compliant STOMP/WebSocket broker.
1095
- * Setting it for such a broker will cause large messages to fail.
1233
+ * This setting is enabled for brokers that support only chunked messages (e.g., Java Spring-based brokers).
1234
+ * Default is `false`.
1096
1235
  *
1097
- * `false` by default.
1236
+ * Warning:
1237
+ * - Should not be used with WebSocket-compliant brokers, as chunking may cause large message failures.
1238
+ * - Binary WebSocket frames are never split.
1098
1239
  *
1099
- * Binary frames are never split.
1240
+ * Example:
1241
+ * ```javascript
1242
+ * client.splitLargeFrames = true;
1243
+ * client.maxWebSocketChunkSize = 4096; // Allow chunks of 4 KB
1244
+ * ```
1100
1245
  */
1101
1246
  this.splitLargeFrames = false;
1102
1247
  /**
1103
- * See [splitLargeFrames]{@link Client#splitLargeFrames}.
1104
- * This has no effect if [splitLargeFrames]{@link Client#splitLargeFrames} is `false`.
1248
+ * Maximum size (in bytes) for individual WebSocket chunks if [splitLargeFrames]{@link Client#splitLargeFrames} is enabled.
1249
+ *
1250
+ * Default is 8 KB (`8 * 1024` bytes). This value has no effect if [splitLargeFrames]{@link Client#splitLargeFrames} is `false`.
1105
1251
  */
1106
1252
  this.maxWebSocketChunkSize = 8 * 1024;
1107
1253
  /**
1108
- * Usually the
1109
- * [type of WebSocket frame]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send#Parameters}
1110
- * is automatically decided by type of the payload.
1111
- * Default is `false`, which should work with all compliant brokers.
1254
+ * Forces all WebSocket frames to use binary transport, irrespective of payload type.
1255
+ *
1256
+ * Default behavior determines frame type based on payload (e.g., binary data for ArrayBuffers).
1112
1257
  *
1113
- * Set this flag to force binary frames.
1258
+ * Example:
1259
+ * ```javascript
1260
+ * client.forceBinaryWSFrames = true;
1261
+ * ```
1114
1262
  */
1115
1263
  this.forceBinaryWSFrames = false;
1116
1264
  /**
1117
- * A bug in ReactNative chops a string on occurrence of a NULL.
1118
- * See issue [https://github.com/stomp-js/stompjs/issues/89]{@link https://github.com/stomp-js/stompjs/issues/89}.
1119
- * This makes incoming WebSocket messages invalid STOMP packets.
1120
- * Setting this flag attempts to reverse the damage by appending a NULL.
1121
- * If the broker splits a large message into multiple WebSocket messages,
1122
- * this flag will cause data loss and abnormal termination of connection.
1265
+ * Workaround for a React Native WebSocket bug, where messages containing `NULL` are chopped.
1266
+ *
1267
+ * Enabling this appends a `NULL` character to incoming frames to ensure they remain valid STOMP packets.
1268
+ *
1269
+ * Warning:
1270
+ * - For brokers that split large messages, this may cause data loss or connection termination.
1123
1271
  *
1124
- * This is not an ideal solution, but a stop gap until the underlying issue is fixed at ReactNative library.
1272
+ * Example:
1273
+ * ```javascript
1274
+ * client.appendMissingNULLonIncoming = true;
1275
+ * ```
1125
1276
  */
1126
1277
  this.appendMissingNULLonIncoming = false;
1127
1278
  /**
1128
- * Browsers do not immediately close WebSockets when `.close` is issued.
1129
- * This may cause reconnection to take a significantly long time in case
1130
- * of some types of failures.
1131
- * In case of incoming heartbeat failure, this experimental flag instructs
1132
- * the library to discard the socket immediately
1133
- * (even before it is actually closed).
1279
+ * Instruct the library to immediately terminate the socket on communication failures, even
1280
+ * before the WebSocket is completely closed.
1281
+ *
1282
+ * This is particularly useful in browser environments where WebSocket closure may get delayed,
1283
+ * causing prolonged reconnection intervals under certain failure conditions.
1284
+ *
1285
+ *
1286
+ * Example:
1287
+ * ```javascript
1288
+ * client.discardWebsocketOnCommFailure = true; // Enable aggressive closing of WebSocket
1289
+ * ```
1290
+ *
1291
+ * Default value: `false`.
1134
1292
  */
1135
1293
  this.discardWebsocketOnCommFailure = false;
1136
1294
  /**
1137
- * Activation state.
1295
+ * Current activation state of the client.
1138
1296
  *
1139
- * It will usually be ACTIVE or INACTIVE.
1140
- * When deactivating, it may go from ACTIVE to INACTIVE without entering DEACTIVATING.
1297
+ * Possible states:
1298
+ * - `ActivationState.ACTIVE`: Client is connected or actively attempting to connect.
1299
+ * - `ActivationState.INACTIVE`: Client is disconnected and not attempting to reconnect.
1300
+ * - `ActivationState.DEACTIVATING`: Client is in the process of disconnecting.
1301
+ *
1302
+ * Note: The client may transition directly from `ACTIVE` to `INACTIVE` without entering
1303
+ * the `DEACTIVATING` state.
1141
1304
  */
1142
1305
  this.state = exports.ActivationState.INACTIVE;
1143
1306
  // No op callbacks
@@ -1149,6 +1312,8 @@
1149
1312
  this.onUnhandledMessage = noOp;
1150
1313
  this.onUnhandledReceipt = noOp;
1151
1314
  this.onUnhandledFrame = noOp;
1315
+ this.onHeartbeatReceived = noOp;
1316
+ this.onHeartbeatLost = noOp;
1152
1317
  this.onStompError = noOp;
1153
1318
  this.onWebSocketClose = noOp;
1154
1319
  this.onWebSocketError = noOp;
@@ -1161,7 +1326,22 @@
1161
1326
  this.configure(conf);
1162
1327
  }
1163
1328
  /**
1164
- * Update configuration.
1329
+ * Updates the client's configuration.
1330
+ *
1331
+ * All properties in the provided configuration object will override the current settings.
1332
+ *
1333
+ * Additionally, a warning is logged if `maxReconnectDelay` is configured to a
1334
+ * value lower than `reconnectDelay`, and `maxReconnectDelay` is adjusted to match `reconnectDelay`.
1335
+ *
1336
+ * Example:
1337
+ * ```javascript
1338
+ * client.configure({
1339
+ * reconnectDelay: 3000,
1340
+ * maxReconnectDelay: 10000
1341
+ * });
1342
+ * ```
1343
+ *
1344
+ * @param conf Configuration object containing the new settings.
1165
1345
  */
1166
1346
  configure(conf) {
1167
1347
  // bulk assign all properties to this
@@ -1174,12 +1354,20 @@
1174
1354
  }
1175
1355
  }
1176
1356
  /**
1177
- * Initiate the connection with the broker.
1178
- * If the connection breaks, as per [Client#reconnectDelay]{@link Client#reconnectDelay},
1179
- * it will keep trying to reconnect. If the [Client#reconnectTimeMode]{@link Client#reconnectTimeMode}
1180
- * is set to EXPONENTIAL it will increase the wait time exponentially
1357
+ * Activates the client, initiating a connection to the STOMP broker.
1358
+ *
1359
+ * On activation, the client attempts to connect and sets its state to `ACTIVE`. If the connection
1360
+ * is lost, it will automatically retry based on `reconnectDelay` or `maxReconnectDelay`. If
1361
+ * `reconnectTimeMode` is set to `EXPONENTIAL`, the reconnect delay increases exponentially.
1362
+ *
1363
+ * To stop reconnection attempts and disconnect, call [Client#deactivate]{@link Client#deactivate}.
1181
1364
  *
1182
- * Call [Client#deactivate]{@link Client#deactivate} to disconnect and stop reconnection attempts.
1365
+ * Example:
1366
+ * ```javascript
1367
+ * client.activate(); // Connect to the broker
1368
+ * ```
1369
+ *
1370
+ * If the client is currently `DEACTIVATING`, connection is delayed until the deactivation process completes.
1183
1371
  */
1184
1372
  activate() {
1185
1373
  const _activate = () => {
@@ -1237,6 +1425,7 @@
1237
1425
  connectHeaders: this.connectHeaders,
1238
1426
  disconnectHeaders: this._disconnectHeaders,
1239
1427
  heartbeatIncoming: this.heartbeatIncoming,
1428
+ heartbeatGracePeriods: this.heartbeatToleranceMultiplier,
1240
1429
  heartbeatOutgoing: this.heartbeatOutgoing,
1241
1430
  heartbeatStrategy: this.heartbeatStrategy,
1242
1431
  splitLargeFrames: this.splitLargeFrames,
@@ -1251,6 +1440,8 @@
1251
1440
  clearTimeout(this._connectionWatcher);
1252
1441
  this._connectionWatcher = undefined;
1253
1442
  }
1443
+ // Reset reconnect delay after successful connection
1444
+ this._nextReconnectDelay = this.reconnectDelay;
1254
1445
  if (!this.active) {
1255
1446
  this.debug('STOMP got connected while deactivate was issued, will disconnect now');
1256
1447
  this._disposeStompHandler();
@@ -1289,6 +1480,12 @@
1289
1480
  onUnhandledFrame: frame => {
1290
1481
  this.onUnhandledFrame(frame);
1291
1482
  },
1483
+ onHeartbeatReceived: () => {
1484
+ this.onHeartbeatReceived();
1485
+ },
1486
+ onHeartbeatLost: () => {
1487
+ this.onHeartbeatLost();
1488
+ },
1292
1489
  });
1293
1490
  this._stompHandler.start();
1294
1491
  }
@@ -1322,27 +1519,36 @@
1322
1519
  }
1323
1520
  }
1324
1521
  /**
1325
- * Disconnect if connected and stop auto reconnect loop.
1326
- * Appropriate callbacks will be invoked if there is an underlying STOMP connection.
1522
+ * Disconnects the client and stops the automatic reconnection loop.
1327
1523
  *
1328
- * This call is async. It will resolve immediately if there is no underlying active websocket,
1329
- * otherwise, it will resolve after the underlying websocket is properly disposed of.
1524
+ * If there is an active STOMP connection at the time of invocation, the appropriate callbacks
1525
+ * will be triggered during the shutdown sequence. Once deactivated, the client will enter the
1526
+ * `INACTIVE` state, and no further reconnection attempts will be made.
1330
1527
  *
1331
- * It is not an error to invoke this method more than once.
1332
- * Each of those would resolve on completion of deactivation.
1528
+ * **Behavior**:
1529
+ * - If there is no active WebSocket connection, this method resolves immediately.
1530
+ * - If there is an active connection, the method waits for the underlying WebSocket
1531
+ * to properly close before resolving.
1532
+ * - Multiple calls to this method are safe. Each invocation resolves upon completion.
1533
+ * - To reactivate, call [Client#activate]{@link Client#activate}.
1333
1534
  *
1334
- * To reactivate, you can call [Client#activate]{@link Client#activate}.
1535
+ * **Experimental Option:**
1536
+ * - By specifying the `force: true` option, the WebSocket connection is discarded immediately,
1537
+ * bypassing both the STOMP and WebSocket shutdown sequences.
1538
+ * - **Caution:** Using `force: true` may leave the WebSocket in an inconsistent state,
1539
+ * and brokers may not immediately detect the termination.
1335
1540
  *
1336
- * Experimental: pass `force: true` to immediately discard the underlying connection.
1337
- * This mode will skip both the STOMP and the Websocket shutdown sequences.
1338
- * In some cases, browsers take a long time in the Websocket shutdown
1339
- * if the underlying connection had gone stale.
1340
- * Using this mode can speed up.
1341
- * When this mode is used, the actual Websocket may linger for a while
1342
- * and the broker may not realize that the connection is no longer in use.
1541
+ * Example:
1542
+ * ```javascript
1543
+ * // Graceful disconnect
1544
+ * await client.deactivate();
1545
+ *
1546
+ * // Forced disconnect to speed up shutdown when the connection is stale
1547
+ * await client.deactivate({ force: true });
1548
+ * ```
1343
1549
  *
1344
- * It is possible to invoke this method initially without the `force` option
1345
- * and subsequently, say after a wait, with the `force` option.
1550
+ * @param options Configuration options for deactivation. Use `force: true` for immediate shutdown.
1551
+ * @returns A Promise that resolves when the deactivation process completes.
1346
1552
  */
1347
1553
  async deactivate(options = {}) {
1348
1554
  const force = options.force || false;
@@ -1353,7 +1559,7 @@
1353
1559
  return Promise.resolve();
1354
1560
  }
1355
1561
  this._changeState(exports.ActivationState.DEACTIVATING);
1356
- // Reset reconnection timer just to be safe
1562
+ // Clear reconnection timer just to be safe
1357
1563
  this._nextReconnectDelay = 0;
1358
1564
  // Clear if a reconnection was scheduled
1359
1565
  if (this._reconnector) {
@@ -1387,10 +1593,18 @@
1387
1593
  return retPromise;
1388
1594
  }
1389
1595
  /**
1390
- * Force disconnect if there is an active connection by directly closing the underlying WebSocket.
1391
- * This is different from a normal disconnect where a DISCONNECT sequence is carried out with the broker.
1392
- * After forcing disconnect, automatic reconnect will be attempted.
1393
- * To stop further reconnects call [Client#deactivate]{@link Client#deactivate} as well.
1596
+ * Forces a disconnect by directly closing the WebSocket.
1597
+ *
1598
+ * Unlike a normal disconnect, this does not send a DISCONNECT sequence to the broker but
1599
+ * instead closes the WebSocket connection directly. After forcing a disconnect, the client
1600
+ * will automatically attempt to reconnect based on its `reconnectDelay` configuration.
1601
+ *
1602
+ * **Note:** To prevent further reconnect attempts, call [Client#deactivate]{@link Client#deactivate}.
1603
+ *
1604
+ * Example:
1605
+ * ```javascript
1606
+ * client.forceDisconnect();
1607
+ * ```
1394
1608
  */
1395
1609
  forceDisconnect() {
1396
1610
  if (this._stompHandler) {
@@ -1404,39 +1618,38 @@
1404
1618
  }
1405
1619
  }
1406
1620
  /**
1407
- * Send a message to a named destination. Refer to your STOMP broker documentation for types
1408
- * and naming of destinations.
1409
- *
1410
- * STOMP protocol specifies and suggests some headers and also allows broker-specific headers.
1621
+ * Sends a message to the specified destination on the STOMP broker.
1411
1622
  *
1412
- * `body` must be String.
1413
- * You will need to covert the payload to string in case it is not string (e.g. JSON).
1623
+ * The `body` must be a `string`. For non-string payloads (e.g., JSON), encode it as a string before sending.
1624
+ * If sending binary data, use the `binaryBody` parameter as a [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array).
1414
1625
  *
1415
- * To send a binary message body, use `binaryBody` parameter. It should be a
1416
- * [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array).
1417
- * Sometimes brokers may not support binary frames out of the box.
1418
- * Please check your broker documentation.
1626
+ * **Content-Length Behavior**:
1627
+ * - For non-binary messages, the `content-length` header is added by default.
1628
+ * - The `content-length` header can be skipped for text frames by setting `skipContentLengthHeader: true` in the parameters.
1629
+ * - For binary messages, the `content-length` header is always included.
1419
1630
  *
1420
- * `content-length` header is automatically added to the STOMP Frame sent to the broker.
1421
- * Set `skipContentLengthHeader` to indicate that `content-length` header should not be added.
1422
- * For binary messages, `content-length` header is always added.
1423
- *
1424
- * Caution: The broker will, most likely, report an error and disconnect
1425
- * if the message body has NULL octet(s) and `content-length` header is missing.
1631
+ * **Notes**:
1632
+ * - Ensure that brokers support binary frames before using `binaryBody`.
1633
+ * - Sending messages with NULL octets and missing `content-length` headers can cause brokers to disconnect and throw errors.
1426
1634
  *
1635
+ * Example:
1427
1636
  * ```javascript
1428
- * client.publish({destination: "/queue/test", headers: {priority: 9}, body: "Hello, STOMP"});
1429
- *
1430
- * // Only destination is mandatory parameter
1431
- * client.publish({destination: "/queue/test", body: "Hello, STOMP"});
1432
- *
1433
- * // Skip content-length header in the frame to the broker
1434
- * client.publish({"/queue/test", body: "Hello, STOMP", skipContentLengthHeader: true});
1435
- *
1436
- * var binaryData = generateBinaryData(); // This need to be of type Uint8Array
1437
- * // setting content-type header is not mandatory, however a good practice
1438
- * client.publish({destination: '/topic/special', binaryBody: binaryData,
1439
- * headers: {'content-type': 'application/octet-stream'}});
1637
+ * // Basic text message
1638
+ * client.publish({ destination: "/queue/test", body: "Hello, STOMP" });
1639
+ *
1640
+ * // Text message with additional headers
1641
+ * client.publish({ destination: "/queue/test", headers: { priority: 9 }, body: "Hello, STOMP" });
1642
+ *
1643
+ * // Skip content-length header
1644
+ * client.publish({ destination: "/queue/test", body: "Hello, STOMP", skipContentLengthHeader: true });
1645
+ *
1646
+ * // Binary message
1647
+ * const binaryData = new Uint8Array([1, 2, 3, 4]);
1648
+ * client.publish({
1649
+ * destination: '/topic/special',
1650
+ * binaryBody: binaryData,
1651
+ * headers: { 'content-type': 'application/octet-stream' }
1652
+ * });
1440
1653
  * ```
1441
1654
  */
1442
1655
  publish(params) {
@@ -1450,39 +1663,27 @@
1450
1663
  }
1451
1664
  }
1452
1665
  /**
1453
- * STOMP brokers may carry out operation asynchronously and allow requesting for acknowledgement.
1454
- * To request an acknowledgement, a `receipt` header needs to be sent with the actual request.
1455
- * The value (say receipt-id) for this header needs to be unique for each use.
1456
- * Typically, a sequence, a UUID, a random number or a combination may be used.
1666
+ * Monitors for a receipt acknowledgment from the broker for specific operations.
1457
1667
  *
1458
- * A complaint broker will send a RECEIPT frame when an operation has actually been completed.
1459
- * The operation needs to be matched based on the value of the receipt-id.
1668
+ * Add a `receipt` header to the operation (like subscribe or publish), and use this method with
1669
+ * the same receipt ID to detect when the broker has acknowledged the operation's completion.
1460
1670
  *
1461
- * This method allows watching for a receipt and invoking the callback
1462
- * when the corresponding receipt has been received.
1463
- *
1464
- * The actual {@link IFrame} will be passed as parameter to the callback.
1671
+ * The callback is invoked with the corresponding {@link IFrame} when the receipt is received.
1465
1672
  *
1466
1673
  * Example:
1467
1674
  * ```javascript
1468
- * // Subscribing with acknowledgement
1469
- * let receiptId = randomText();
1470
- *
1471
- * client.watchForReceipt(receiptId, function() {
1472
- * // Will be called after server acknowledges
1473
- * });
1474
- *
1475
- * client.subscribe(TEST.destination, onMessage, {receipt: receiptId});
1476
- *
1675
+ * const receiptId = "unique-receipt-id";
1477
1676
  *
1478
- * // Publishing with acknowledgement
1479
- * receiptId = randomText();
1677
+ * client.watchForReceipt(receiptId, (frame) => {
1678
+ * console.log("Operation acknowledged by the broker:", frame);
1679
+ * });
1480
1680
  *
1481
- * client.watchForReceipt(receiptId, function() {
1482
- * // Will be called after server acknowledges
1483
- * });
1484
- * client.publish({destination: TEST.destination, headers: {receipt: receiptId}, body: msg});
1681
+ * // Attach the receipt header to an operation
1682
+ * client.publish({ destination: "/queue/test", headers: { receipt: receiptId }, body: "Hello" });
1485
1683
  * ```
1684
+ *
1685
+ * @param receiptId Unique identifier for the receipt.
1686
+ * @param callback Callback function invoked on receiving the RECEIPT frame.
1486
1687
  */
1487
1688
  watchForReceipt(receiptId, callback) {
1488
1689
  this._checkConnection();
@@ -1490,28 +1691,33 @@
1490
1691
  this._stompHandler.watchForReceipt(receiptId, callback);
1491
1692
  }
1492
1693
  /**
1493
- * Subscribe to a STOMP Broker location. The callback will be invoked for each
1494
- * received message with the {@link IMessage} as argument.
1694
+ * Subscribes to a destination on the STOMP broker.
1495
1695
  *
1496
- * Note: The library will generate a unique ID if there is none provided in the headers.
1497
- * To use your own ID, pass it using the `headers` argument.
1696
+ * The callback is triggered for each message received from the subscribed destination. The message
1697
+ * is passed as an {@link IMessage} instance.
1498
1698
  *
1699
+ * **Subscription ID**:
1700
+ * - If no `id` is provided in `headers`, the library generates a unique subscription ID automatically.
1701
+ * - Provide an explicit `id` in `headers` if you wish to manage the subscription ID manually.
1702
+ *
1703
+ * Example:
1499
1704
  * ```javascript
1500
- * callback = function(message) {
1501
- * // called when the client receives a STOMP message from the server
1502
- * if (message.body) {
1503
- * alert("got message with body " + message.body)
1504
- * } else {
1505
- * alert("got empty message");
1506
- * }
1507
- * });
1705
+ * const callback = (message) => {
1706
+ * console.log("Received message:", message.body);
1707
+ * };
1508
1708
  *
1509
- * var subscription = client.subscribe("/queue/test", callback);
1709
+ * // Auto-generated subscription ID
1710
+ * const subscription = client.subscribe("/queue/test", callback);
1510
1711
  *
1511
- * // Explicit subscription id
1512
- * var mySubId = 'my-subscription-id-001';
1513
- * var subscription = client.subscribe(destination, callback, { id: mySubId });
1712
+ * // Explicit subscription ID
1713
+ * const mySubId = "my-subscription-id";
1714
+ * const subscription = client.subscribe("/queue/test", callback, { id: mySubId });
1514
1715
  * ```
1716
+ *
1717
+ * @param destination Destination to subscribe to.
1718
+ * @param callback Function invoked for each received message.
1719
+ * @param headers Optional headers for subscription, such as `id`.
1720
+ * @returns A {@link StompSubscription} which can be used to manage the subscription.
1515
1721
  */
1516
1722
  subscribe(destination, callback, headers = {}) {
1517
1723
  this._checkConnection();
@@ -1519,16 +1725,24 @@
1519
1725
  return this._stompHandler.subscribe(destination, callback, headers);
1520
1726
  }
1521
1727
  /**
1522
- * It is preferable to unsubscribe from a subscription by calling
1523
- * `unsubscribe()` directly on {@link StompSubscription} returned by `client.subscribe()`:
1728
+ * Unsubscribes from a subscription on the STOMP broker.
1729
+ *
1730
+ * Prefer using the `unsubscribe` method directly on the {@link StompSubscription} returned from `subscribe` for cleaner management:
1731
+ * ```javascript
1732
+ * const subscription = client.subscribe("/queue/test", callback);
1733
+ * // Unsubscribe using the subscription object
1734
+ * subscription.unsubscribe();
1735
+ * ```
1736
+ *
1737
+ * This method can also be used directly with the subscription ID.
1524
1738
  *
1739
+ * Example:
1525
1740
  * ```javascript
1526
- * var subscription = client.subscribe(destination, onmessage);
1527
- * // ...
1528
- * subscription.unsubscribe();
1741
+ * client.unsubscribe("my-subscription-id");
1529
1742
  * ```
1530
1743
  *
1531
- * See: https://stomp.github.com/stomp-specification-1.2.html#UNSUBSCRIBE UNSUBSCRIBE Frame
1744
+ * @param id Subscription ID to unsubscribe.
1745
+ * @param headers Optional headers to pass for the UNSUBSCRIBE frame.
1532
1746
  */
1533
1747
  unsubscribe(id, headers = {}) {
1534
1748
  this._checkConnection();
@@ -1536,10 +1750,21 @@
1536
1750
  this._stompHandler.unsubscribe(id, headers);
1537
1751
  }
1538
1752
  /**
1539
- * Start a transaction, the returned {@link ITransaction} has methods - [commit]{@link ITransaction#commit}
1540
- * and [abort]{@link ITransaction#abort}.
1753
+ * Starts a new transaction. The returned {@link ITransaction} object provides
1754
+ * methods for [commit]{@link ITransaction#commit} and [abort]{@link ITransaction#abort}.
1755
+ *
1756
+ * If `transactionId` is not provided, the library generates a unique ID internally.
1757
+ *
1758
+ * Example:
1759
+ * ```javascript
1760
+ * const tx = client.begin(); // Auto-generated ID
1761
+ *
1762
+ * // Or explicitly specify a transaction ID
1763
+ * const tx = client.begin("my-transaction-id");
1764
+ * ```
1541
1765
  *
1542
- * `transactionId` is optional, if not passed the library will generate it internally.
1766
+ * @param transactionId Optional transaction ID.
1767
+ * @returns An instance of {@link ITransaction}.
1543
1768
  */
1544
1769
  begin(transactionId) {
1545
1770
  this._checkConnection();
@@ -1547,16 +1772,19 @@
1547
1772
  return this._stompHandler.begin(transactionId);
1548
1773
  }
1549
1774
  /**
1550
- * Commit a transaction.
1775
+ * Commits a transaction.
1551
1776
  *
1552
- * It is preferable to commit a transaction by calling [commit]{@link ITransaction#commit} directly on
1553
- * {@link ITransaction} returned by [client.begin]{@link Client#begin}.
1777
+ * It is strongly recommended to call [commit]{@link ITransaction#commit} on
1778
+ * the transaction object returned by [client#begin]{@link Client#begin}.
1554
1779
  *
1780
+ * Example:
1555
1781
  * ```javascript
1556
- * var tx = client.begin(txId);
1557
- * //...
1558
- * tx.commit();
1782
+ * const tx = client.begin();
1783
+ * // Perform operations under this transaction
1784
+ * tx.commit();
1559
1785
  * ```
1786
+ *
1787
+ * @param transactionId The ID of the transaction to commit.
1560
1788
  */
1561
1789
  commit(transactionId) {
1562
1790
  this._checkConnection();
@@ -1564,15 +1792,19 @@
1564
1792
  this._stompHandler.commit(transactionId);
1565
1793
  }
1566
1794
  /**
1567
- * Abort a transaction.
1568
- * It is preferable to abort a transaction by calling [abort]{@link ITransaction#abort} directly on
1569
- * {@link ITransaction} returned by [client.begin]{@link Client#begin}.
1795
+ * Aborts a transaction.
1796
+ *
1797
+ * It is strongly recommended to call [abort]{@link ITransaction#abort} directly
1798
+ * on the transaction object returned by [client#begin]{@link Client#begin}.
1570
1799
  *
1800
+ * Example:
1571
1801
  * ```javascript
1572
- * var tx = client.begin(txId);
1573
- * //...
1574
- * tx.abort();
1802
+ * const tx = client.begin();
1803
+ * // Perform operations under this transaction
1804
+ * tx.abort(); // Abort the transaction
1575
1805
  * ```
1806
+ *
1807
+ * @param transactionId The ID of the transaction to abort.
1576
1808
  */
1577
1809
  abort(transactionId) {
1578
1810
  this._checkConnection();
@@ -1580,17 +1812,23 @@
1580
1812
  this._stompHandler.abort(transactionId);
1581
1813
  }
1582
1814
  /**
1583
- * ACK a message. It is preferable to acknowledge a message by calling [ack]{@link IMessage#ack} directly
1584
- * on the {@link IMessage} handled by a subscription callback:
1815
+ * Acknowledges receipt of a message. Typically, this should be done by calling
1816
+ * [ack]{@link IMessage#ack} directly on the {@link IMessage} instance passed
1817
+ * to the subscription callback.
1585
1818
  *
1819
+ * Example:
1586
1820
  * ```javascript
1587
- * var callback = function (message) {
1588
- * // process the message
1589
- * // acknowledge it
1590
- * message.ack();
1591
- * };
1592
- * client.subscribe(destination, callback, {'ack': 'client'});
1821
+ * const callback = (message) => {
1822
+ * // Process the message
1823
+ * message.ack(); // Acknowledge the message
1824
+ * };
1825
+ *
1826
+ * client.subscribe("/queue/example", callback, { ack: "client" });
1593
1827
  * ```
1828
+ *
1829
+ * @param messageId The ID of the message to acknowledge.
1830
+ * @param subscriptionId The ID of the subscription.
1831
+ * @param headers Optional headers for the acknowledgment frame.
1594
1832
  */
1595
1833
  ack(messageId, subscriptionId, headers = {}) {
1596
1834
  this._checkConnection();
@@ -1598,17 +1836,25 @@
1598
1836
  this._stompHandler.ack(messageId, subscriptionId, headers);
1599
1837
  }
1600
1838
  /**
1601
- * NACK a message. It is preferable to acknowledge a message by calling [nack]{@link IMessage#nack} directly
1602
- * on the {@link IMessage} handled by a subscription callback:
1839
+ * Rejects a message (negative acknowledgment). Like acknowledgments, this should
1840
+ * typically be done by calling [nack]{@link IMessage#nack} directly on the {@link IMessage}
1841
+ * instance passed to the subscription callback.
1603
1842
  *
1843
+ * Example:
1604
1844
  * ```javascript
1605
- * var callback = function (message) {
1606
- * // process the message
1607
- * // an error occurs, nack it
1608
- * message.nack();
1609
- * };
1610
- * client.subscribe(destination, callback, {'ack': 'client'});
1845
+ * const callback = (message) => {
1846
+ * // Process the message
1847
+ * if (isError(message)) {
1848
+ * message.nack(); // Reject the message
1849
+ * }
1850
+ * };
1851
+ *
1852
+ * client.subscribe("/queue/example", callback, { ack: "client" });
1611
1853
  * ```
1854
+ *
1855
+ * @param messageId The ID of the message to negatively acknowledge.
1856
+ * @param subscriptionId The ID of the subscription.
1857
+ * @param headers Optional headers for the NACK frame.
1612
1858
  */
1613
1859
  nack(messageId, subscriptionId, headers = {}) {
1614
1860
  this._checkConnection();
@@ -1628,7 +1874,7 @@
1628
1874
  }
1629
1875
 
1630
1876
  /**
1631
- * STOMP headers. Many functions calls will accept headers as parameters.
1877
+ * STOMP headers. Many function calls will accept headers as parameters.
1632
1878
  * The headers sent by Broker will be available as [IFrame#headers]{@link IFrame#headers}.
1633
1879
  *
1634
1880
  * `key` and `value` must be valid strings.