UNPKG

75.6 kBJavaScriptView Raw
1(function (global, factory) {
2 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3 typeof define === 'function' && define.amd ? define(['exports'], factory) :
4 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.StompJs = {}));
5})(this, (function (exports) { 'use strict';
6
7 /**
8 * Some byte values, used as per STOMP specifications.
9 *
10 * Part of `@stomp/stompjs`.
11 *
12 * @internal
13 */
14 const BYTE = {
15 // LINEFEED byte (octet 10)
16 LF: '\x0A',
17 // NULL byte (octet 0)
18 NULL: '\x00',
19 };
20
21 /**
22 * Frame class represents a STOMP frame.
23 *
24 * @internal
25 */
26 class FrameImpl {
27 /**
28 * Frame constructor. `command`, `headers` and `body` are available as properties.
29 *
30 * @internal
31 */
32 constructor(params) {
33 const { command, headers, body, binaryBody, escapeHeaderValues, skipContentLengthHeader, } = params;
34 this.command = command;
35 this.headers = Object.assign({}, headers || {});
36 if (binaryBody) {
37 this._binaryBody = binaryBody;
38 this.isBinaryBody = true;
39 }
40 else {
41 this._body = body || '';
42 this.isBinaryBody = false;
43 }
44 this.escapeHeaderValues = escapeHeaderValues || false;
45 this.skipContentLengthHeader = skipContentLengthHeader || false;
46 }
47 /**
48 * body of the frame
49 */
50 get body() {
51 if (!this._body && this.isBinaryBody) {
52 this._body = new TextDecoder().decode(this._binaryBody);
53 }
54 return this._body || '';
55 }
56 /**
57 * body as Uint8Array
58 */
59 get binaryBody() {
60 if (!this._binaryBody && !this.isBinaryBody) {
61 this._binaryBody = new TextEncoder().encode(this._body);
62 }
63 // At this stage it will definitely have a valid value
64 return this._binaryBody;
65 }
66 /**
67 * deserialize a STOMP Frame from raw data.
68 *
69 * @internal
70 */
71 static fromRawFrame(rawFrame, escapeHeaderValues) {
72 const headers = {};
73 const trim = (str) => str.replace(/^\s+|\s+$/g, '');
74 // In case of repeated headers, as per standards, first value need to be used
75 for (const header of rawFrame.headers.reverse()) {
76 header.indexOf(':');
77 const key = trim(header[0]);
78 let value = trim(header[1]);
79 if (escapeHeaderValues &&
80 rawFrame.command !== 'CONNECT' &&
81 rawFrame.command !== 'CONNECTED') {
82 value = FrameImpl.hdrValueUnEscape(value);
83 }
84 headers[key] = value;
85 }
86 return new FrameImpl({
87 command: rawFrame.command,
88 headers,
89 binaryBody: rawFrame.binaryBody,
90 escapeHeaderValues,
91 });
92 }
93 /**
94 * @internal
95 */
96 toString() {
97 return this.serializeCmdAndHeaders();
98 }
99 /**
100 * serialize this Frame in a format suitable to be passed to WebSocket.
101 * If the body is string the output will be string.
102 * If the body is binary (i.e. of type Unit8Array) it will be serialized to ArrayBuffer.
103 *
104 * @internal
105 */
106 serialize() {
107 const cmdAndHeaders = this.serializeCmdAndHeaders();
108 if (this.isBinaryBody) {
109 return FrameImpl.toUnit8Array(cmdAndHeaders, this._binaryBody).buffer;
110 }
111 else {
112 return cmdAndHeaders + this._body + BYTE.NULL;
113 }
114 }
115 serializeCmdAndHeaders() {
116 const lines = [this.command];
117 if (this.skipContentLengthHeader) {
118 delete this.headers['content-length'];
119 }
120 for (const name of Object.keys(this.headers || {})) {
121 const value = this.headers[name];
122 if (this.escapeHeaderValues &&
123 this.command !== 'CONNECT' &&
124 this.command !== 'CONNECTED') {
125 lines.push(`${name}:${FrameImpl.hdrValueEscape(`${value}`)}`);
126 }
127 else {
128 lines.push(`${name}:${value}`);
129 }
130 }
131 if (this.isBinaryBody ||
132 (!this.isBodyEmpty() && !this.skipContentLengthHeader)) {
133 lines.push(`content-length:${this.bodyLength()}`);
134 }
135 return lines.join(BYTE.LF) + BYTE.LF + BYTE.LF;
136 }
137 isBodyEmpty() {
138 return this.bodyLength() === 0;
139 }
140 bodyLength() {
141 const binaryBody = this.binaryBody;
142 return binaryBody ? binaryBody.length : 0;
143 }
144 /**
145 * Compute the size of a UTF-8 string by counting its number of bytes
146 * (and not the number of characters composing the string)
147 */
148 static sizeOfUTF8(s) {
149 return s ? new TextEncoder().encode(s).length : 0;
150 }
151 static toUnit8Array(cmdAndHeaders, binaryBody) {
152 const uint8CmdAndHeaders = new TextEncoder().encode(cmdAndHeaders);
153 const nullTerminator = new Uint8Array([0]);
154 const uint8Frame = new Uint8Array(uint8CmdAndHeaders.length + binaryBody.length + nullTerminator.length);
155 uint8Frame.set(uint8CmdAndHeaders);
156 uint8Frame.set(binaryBody, uint8CmdAndHeaders.length);
157 uint8Frame.set(nullTerminator, uint8CmdAndHeaders.length + binaryBody.length);
158 return uint8Frame;
159 }
160 /**
161 * Serialize a STOMP frame as per STOMP standards, suitable to be sent to the STOMP broker.
162 *
163 * @internal
164 */
165 static marshall(params) {
166 const frame = new FrameImpl(params);
167 return frame.serialize();
168 }
169 /**
170 * Escape header values
171 */
172 static hdrValueEscape(str) {
173 return str
174 .replace(/\\/g, '\\\\')
175 .replace(/\r/g, '\\r')
176 .replace(/\n/g, '\\n')
177 .replace(/:/g, '\\c');
178 }
179 /**
180 * UnEscape header values
181 */
182 static hdrValueUnEscape(str) {
183 return str
184 .replace(/\\r/g, '\r')
185 .replace(/\\n/g, '\n')
186 .replace(/\\c/g, ':')
187 .replace(/\\\\/g, '\\');
188 }
189 }
190
191 /**
192 * @internal
193 */
194 const NULL = 0;
195 /**
196 * @internal
197 */
198 const LF = 10;
199 /**
200 * @internal
201 */
202 const CR = 13;
203 /**
204 * @internal
205 */
206 const COLON = 58;
207 /**
208 * This is an evented, rec descent parser.
209 * A stream of Octets can be passed and whenever it recognizes
210 * a complete Frame or an incoming ping it will invoke the registered callbacks.
211 *
212 * All incoming Octets are fed into _onByte function.
213 * Depending on current state the _onByte function keeps changing.
214 * Depending on the state it keeps accumulating into _token and _results.
215 * State is indicated by current value of _onByte, all states are named as _collect.
216 *
217 * STOMP standards https://stomp.github.io/stomp-specification-1.2.html
218 * imply that all lengths are considered in bytes (instead of string lengths).
219 * So, before actual parsing, if the incoming data is String it is converted to Octets.
220 * This allows faithful implementation of the protocol and allows NULL Octets to be present in the body.
221 *
222 * There is no peek function on the incoming data.
223 * When a state change occurs based on an Octet without consuming the Octet,
224 * the Octet, after state change, is fed again (_reinjectByte).
225 * This became possible as the state change can be determined by inspecting just one Octet.
226 *
227 * There are two modes to collect the body, if content-length header is there then it by counting Octets
228 * otherwise it is determined by NULL terminator.
229 *
230 * Following the standards, the command and headers are converted to Strings
231 * and the body is returned as Octets.
232 * Headers are returned as an array and not as Hash - to allow multiple occurrence of an header.
233 *
234 * This parser does not use Regular Expressions as that can only operate on Strings.
235 *
236 * It handles if multiple STOMP frames are given as one chunk, a frame is split into multiple chunks, or
237 * any combination there of. The parser remembers its state (any partial frame) and continues when a new chunk
238 * is pushed.
239 *
240 * Typically the higher level function will convert headers to Hash, handle unescaping of header values
241 * (which is protocol version specific), and convert body to text.
242 *
243 * Check the parser.spec.js to understand cases that this parser is supposed to handle.
244 *
245 * Part of `@stomp/stompjs`.
246 *
247 * @internal
248 */
249 class Parser {
250 constructor(onFrame, onIncomingPing) {
251 this.onFrame = onFrame;
252 this.onIncomingPing = onIncomingPing;
253 this._encoder = new TextEncoder();
254 this._decoder = new TextDecoder();
255 this._token = [];
256 this._initState();
257 }
258 parseChunk(segment, appendMissingNULLonIncoming = false) {
259 let chunk;
260 if (typeof segment === 'string') {
261 chunk = this._encoder.encode(segment);
262 }
263 else {
264 chunk = new Uint8Array(segment);
265 }
266 // See https://github.com/stomp-js/stompjs/issues/89
267 // Remove when underlying issue is fixed.
268 //
269 // Send a NULL byte, if the last byte of a Text frame was not NULL.F
270 if (appendMissingNULLonIncoming && chunk[chunk.length - 1] !== 0) {
271 const chunkWithNull = new Uint8Array(chunk.length + 1);
272 chunkWithNull.set(chunk, 0);
273 chunkWithNull[chunk.length] = 0;
274 chunk = chunkWithNull;
275 }
276 // tslint:disable-next-line:prefer-for-of
277 for (let i = 0; i < chunk.length; i++) {
278 const byte = chunk[i];
279 this._onByte(byte);
280 }
281 }
282 // The following implements a simple Rec Descent Parser.
283 // The grammar is simple and just one byte tells what should be the next state
284 _collectFrame(byte) {
285 if (byte === NULL) {
286 // Ignore
287 return;
288 }
289 if (byte === CR) {
290 // Ignore CR
291 return;
292 }
293 if (byte === LF) {
294 // Incoming Ping
295 this.onIncomingPing();
296 return;
297 }
298 this._onByte = this._collectCommand;
299 this._reinjectByte(byte);
300 }
301 _collectCommand(byte) {
302 if (byte === CR) {
303 // Ignore CR
304 return;
305 }
306 if (byte === LF) {
307 this._results.command = this._consumeTokenAsUTF8();
308 this._onByte = this._collectHeaders;
309 return;
310 }
311 this._consumeByte(byte);
312 }
313 _collectHeaders(byte) {
314 if (byte === CR) {
315 // Ignore CR
316 return;
317 }
318 if (byte === LF) {
319 this._setupCollectBody();
320 return;
321 }
322 this._onByte = this._collectHeaderKey;
323 this._reinjectByte(byte);
324 }
325 _reinjectByte(byte) {
326 this._onByte(byte);
327 }
328 _collectHeaderKey(byte) {
329 if (byte === COLON) {
330 this._headerKey = this._consumeTokenAsUTF8();
331 this._onByte = this._collectHeaderValue;
332 return;
333 }
334 this._consumeByte(byte);
335 }
336 _collectHeaderValue(byte) {
337 if (byte === CR) {
338 // Ignore CR
339 return;
340 }
341 if (byte === LF) {
342 this._results.headers.push([
343 this._headerKey,
344 this._consumeTokenAsUTF8(),
345 ]);
346 this._headerKey = undefined;
347 this._onByte = this._collectHeaders;
348 return;
349 }
350 this._consumeByte(byte);
351 }
352 _setupCollectBody() {
353 const contentLengthHeader = this._results.headers.filter((header) => {
354 return header[0] === 'content-length';
355 })[0];
356 if (contentLengthHeader) {
357 this._bodyBytesRemaining = parseInt(contentLengthHeader[1], 10);
358 this._onByte = this._collectBodyFixedSize;
359 }
360 else {
361 this._onByte = this._collectBodyNullTerminated;
362 }
363 }
364 _collectBodyNullTerminated(byte) {
365 if (byte === NULL) {
366 this._retrievedBody();
367 return;
368 }
369 this._consumeByte(byte);
370 }
371 _collectBodyFixedSize(byte) {
372 // It is post decrement, so that we discard the trailing NULL octet
373 if (this._bodyBytesRemaining-- === 0) {
374 this._retrievedBody();
375 return;
376 }
377 this._consumeByte(byte);
378 }
379 _retrievedBody() {
380 this._results.binaryBody = this._consumeTokenAsRaw();
381 try {
382 this.onFrame(this._results);
383 }
384 catch (e) {
385 console.log(`Ignoring an exception thrown by a frame handler. Original exception: `, e);
386 }
387 this._initState();
388 }
389 // Rec Descent Parser helpers
390 _consumeByte(byte) {
391 this._token.push(byte);
392 }
393 _consumeTokenAsUTF8() {
394 return this._decoder.decode(this._consumeTokenAsRaw());
395 }
396 _consumeTokenAsRaw() {
397 const rawResult = new Uint8Array(this._token);
398 this._token = [];
399 return rawResult;
400 }
401 _initState() {
402 this._results = {
403 command: undefined,
404 headers: [],
405 binaryBody: undefined,
406 };
407 this._token = [];
408 this._headerKey = undefined;
409 this._onByte = this._collectFrame;
410 }
411 }
412
413 /**
414 * Possible states for the IStompSocket
415 */
416 exports.StompSocketState = void 0;
417 (function (StompSocketState) {
418 StompSocketState[StompSocketState["CONNECTING"] = 0] = "CONNECTING";
419 StompSocketState[StompSocketState["OPEN"] = 1] = "OPEN";
420 StompSocketState[StompSocketState["CLOSING"] = 2] = "CLOSING";
421 StompSocketState[StompSocketState["CLOSED"] = 3] = "CLOSED";
422 })(exports.StompSocketState = exports.StompSocketState || (exports.StompSocketState = {}));
423 /**
424 * Possible activation state
425 */
426 exports.ActivationState = void 0;
427 (function (ActivationState) {
428 ActivationState[ActivationState["ACTIVE"] = 0] = "ACTIVE";
429 ActivationState[ActivationState["DEACTIVATING"] = 1] = "DEACTIVATING";
430 ActivationState[ActivationState["INACTIVE"] = 2] = "INACTIVE";
431 })(exports.ActivationState = exports.ActivationState || (exports.ActivationState = {}));
432
433 /**
434 * Supported STOMP versions
435 *
436 * Part of `@stomp/stompjs`.
437 */
438 class Versions {
439 /**
440 * Takes an array of versions, typical elements '1.2', '1.1', or '1.0'
441 *
442 * You will be creating an instance of this class if you want to override
443 * supported versions to be declared during STOMP handshake.
444 */
445 constructor(versions) {
446 this.versions = versions;
447 }
448 /**
449 * Used as part of CONNECT STOMP Frame
450 */
451 supportedVersions() {
452 return this.versions.join(',');
453 }
454 /**
455 * Used while creating a WebSocket
456 */
457 protocolVersions() {
458 return this.versions.map(x => `v${x.replace('.', '')}.stomp`);
459 }
460 }
461 /**
462 * Indicates protocol version 1.0
463 */
464 Versions.V1_0 = '1.0';
465 /**
466 * Indicates protocol version 1.1
467 */
468 Versions.V1_1 = '1.1';
469 /**
470 * Indicates protocol version 1.2
471 */
472 Versions.V1_2 = '1.2';
473 /**
474 * @internal
475 */
476 Versions.default = new Versions([
477 Versions.V1_2,
478 Versions.V1_1,
479 Versions.V1_0,
480 ]);
481
482 /**
483 * @internal
484 */
485 function augmentWebsocket(webSocket, debug) {
486 webSocket.terminate = function () {
487 const noOp = () => { };
488 // set all callbacks to no op
489 this.onerror = noOp;
490 this.onmessage = noOp;
491 this.onopen = noOp;
492 const ts = new Date();
493 const id = Math.random().toString().substring(2, 8); // A simulated id
494 const origOnClose = this.onclose;
495 // Track delay in actual closure of the socket
496 this.onclose = closeEvent => {
497 const delay = new Date().getTime() - ts.getTime();
498 debug(`Discarded socket (#${id}) closed after ${delay}ms, with code/reason: ${closeEvent.code}/${closeEvent.reason}`);
499 };
500 this.close();
501 origOnClose?.call(webSocket, {
502 code: 4001,
503 reason: `Quick discarding socket (#${id}) without waiting for the shutdown sequence.`,
504 wasClean: false,
505 });
506 };
507 }
508
509 /**
510 * The STOMP protocol handler
511 *
512 * Part of `@stomp/stompjs`.
513 *
514 * @internal
515 */
516 class StompHandler {
517 constructor(_client, _webSocket, config) {
518 this._client = _client;
519 this._webSocket = _webSocket;
520 this._connected = false;
521 this._serverFrameHandlers = {
522 // [CONNECTED Frame](https://stomp.github.com/stomp-specification-1.2.html#CONNECTED_Frame)
523 CONNECTED: frame => {
524 this.debug(`connected to server ${frame.headers.server}`);
525 this._connected = true;
526 this._connectedVersion = frame.headers.version;
527 // STOMP version 1.2 needs header values to be escaped
528 if (this._connectedVersion === Versions.V1_2) {
529 this._escapeHeaderValues = true;
530 }
531 this._setupHeartbeat(frame.headers);
532 this.onConnect(frame);
533 },
534 // [MESSAGE Frame](https://stomp.github.com/stomp-specification-1.2.html#MESSAGE)
535 MESSAGE: frame => {
536 // the callback is registered when the client calls
537 // `subscribe()`.
538 // If there is no registered subscription for the received message,
539 // the default `onUnhandledMessage` callback is used that the client can set.
540 // This is useful for subscriptions that are automatically created
541 // on the browser side (e.g. [RabbitMQ's temporary
542 // queues](https://www.rabbitmq.com/stomp.html)).
543 const subscription = frame.headers.subscription;
544 const onReceive = this._subscriptions[subscription] || this.onUnhandledMessage;
545 // bless the frame to be a Message
546 const message = frame;
547 const client = this;
548 const messageId = this._connectedVersion === Versions.V1_2
549 ? message.headers.ack
550 : message.headers['message-id'];
551 // add `ack()` and `nack()` methods directly to the returned frame
552 // so that a simple call to `message.ack()` can acknowledge the message.
553 message.ack = (headers = {}) => {
554 return client.ack(messageId, subscription, headers);
555 };
556 message.nack = (headers = {}) => {
557 return client.nack(messageId, subscription, headers);
558 };
559 onReceive(message);
560 },
561 // [RECEIPT Frame](https://stomp.github.com/stomp-specification-1.2.html#RECEIPT)
562 RECEIPT: frame => {
563 const callback = this._receiptWatchers[frame.headers['receipt-id']];
564 if (callback) {
565 callback(frame);
566 // Server will acknowledge only once, remove the callback
567 delete this._receiptWatchers[frame.headers['receipt-id']];
568 }
569 else {
570 this.onUnhandledReceipt(frame);
571 }
572 },
573 // [ERROR Frame](https://stomp.github.com/stomp-specification-1.2.html#ERROR)
574 ERROR: frame => {
575 this.onStompError(frame);
576 },
577 };
578 // used to index subscribers
579 this._counter = 0;
580 // subscription callbacks indexed by subscriber's ID
581 this._subscriptions = {};
582 // receipt-watchers indexed by receipts-ids
583 this._receiptWatchers = {};
584 this._partialData = '';
585 this._escapeHeaderValues = false;
586 this._lastServerActivityTS = Date.now();
587 this.debug = config.debug;
588 this.stompVersions = config.stompVersions;
589 this.connectHeaders = config.connectHeaders;
590 this.disconnectHeaders = config.disconnectHeaders;
591 this.heartbeatIncoming = config.heartbeatIncoming;
592 this.heartbeatOutgoing = config.heartbeatOutgoing;
593 this.splitLargeFrames = config.splitLargeFrames;
594 this.maxWebSocketChunkSize = config.maxWebSocketChunkSize;
595 this.forceBinaryWSFrames = config.forceBinaryWSFrames;
596 this.logRawCommunication = config.logRawCommunication;
597 this.appendMissingNULLonIncoming = config.appendMissingNULLonIncoming;
598 this.discardWebsocketOnCommFailure = config.discardWebsocketOnCommFailure;
599 this.onConnect = config.onConnect;
600 this.onDisconnect = config.onDisconnect;
601 this.onStompError = config.onStompError;
602 this.onWebSocketClose = config.onWebSocketClose;
603 this.onWebSocketError = config.onWebSocketError;
604 this.onUnhandledMessage = config.onUnhandledMessage;
605 this.onUnhandledReceipt = config.onUnhandledReceipt;
606 this.onUnhandledFrame = config.onUnhandledFrame;
607 }
608 get connectedVersion() {
609 return this._connectedVersion;
610 }
611 get connected() {
612 return this._connected;
613 }
614 start() {
615 const parser = new Parser(
616 // On Frame
617 rawFrame => {
618 const frame = FrameImpl.fromRawFrame(rawFrame, this._escapeHeaderValues);
619 // if this.logRawCommunication is set, the rawChunk is logged at this._webSocket.onmessage
620 if (!this.logRawCommunication) {
621 this.debug(`<<< ${frame}`);
622 }
623 const serverFrameHandler = this._serverFrameHandlers[frame.command] || this.onUnhandledFrame;
624 serverFrameHandler(frame);
625 },
626 // On Incoming Ping
627 () => {
628 this.debug('<<< PONG');
629 });
630 this._webSocket.onmessage = (evt) => {
631 this.debug('Received data');
632 this._lastServerActivityTS = Date.now();
633 if (this.logRawCommunication) {
634 const rawChunkAsString = evt.data instanceof ArrayBuffer
635 ? new TextDecoder().decode(evt.data)
636 : evt.data;
637 this.debug(`<<< ${rawChunkAsString}`);
638 }
639 parser.parseChunk(evt.data, this.appendMissingNULLonIncoming);
640 };
641 this._webSocket.onclose = (closeEvent) => {
642 this.debug(`Connection closed to ${this._webSocket.url}`);
643 this._cleanUp();
644 this.onWebSocketClose(closeEvent);
645 };
646 this._webSocket.onerror = (errorEvent) => {
647 this.onWebSocketError(errorEvent);
648 };
649 this._webSocket.onopen = () => {
650 // Clone before updating
651 const connectHeaders = Object.assign({}, this.connectHeaders);
652 this.debug('Web Socket Opened...');
653 connectHeaders['accept-version'] = this.stompVersions.supportedVersions();
654 connectHeaders['heart-beat'] = [
655 this.heartbeatOutgoing,
656 this.heartbeatIncoming,
657 ].join(',');
658 this._transmit({ command: 'CONNECT', headers: connectHeaders });
659 };
660 }
661 _setupHeartbeat(headers) {
662 if (headers.version !== Versions.V1_1 &&
663 headers.version !== Versions.V1_2) {
664 return;
665 }
666 // It is valid for the server to not send this header
667 // https://stomp.github.io/stomp-specification-1.2.html#Heart-beating
668 if (!headers['heart-beat']) {
669 return;
670 }
671 // heart-beat header received from the server looks like:
672 //
673 // heart-beat: sx, sy
674 const [serverOutgoing, serverIncoming] = headers['heart-beat']
675 .split(',')
676 .map((v) => parseInt(v, 10));
677 if (this.heartbeatOutgoing !== 0 && serverIncoming !== 0) {
678 const ttl = Math.max(this.heartbeatOutgoing, serverIncoming);
679 this.debug(`send PING every ${ttl}ms`);
680 this._pinger = setInterval(() => {
681 if (this._webSocket.readyState === exports.StompSocketState.OPEN) {
682 this._webSocket.send(BYTE.LF);
683 this.debug('>>> PING');
684 }
685 }, ttl);
686 }
687 if (this.heartbeatIncoming !== 0 && serverOutgoing !== 0) {
688 const ttl = Math.max(this.heartbeatIncoming, serverOutgoing);
689 this.debug(`check PONG every ${ttl}ms`);
690 this._ponger = setInterval(() => {
691 const delta = Date.now() - this._lastServerActivityTS;
692 // We wait twice the TTL to be flexible on window's setInterval calls
693 if (delta > ttl * 2) {
694 this.debug(`did not receive server activity for the last ${delta}ms`);
695 this._closeOrDiscardWebsocket();
696 }
697 }, ttl);
698 }
699 }
700 _closeOrDiscardWebsocket() {
701 if (this.discardWebsocketOnCommFailure) {
702 this.debug('Discarding websocket, the underlying socket may linger for a while');
703 this.discardWebsocket();
704 }
705 else {
706 this.debug('Issuing close on the websocket');
707 this._closeWebsocket();
708 }
709 }
710 forceDisconnect() {
711 if (this._webSocket) {
712 if (this._webSocket.readyState === exports.StompSocketState.CONNECTING ||
713 this._webSocket.readyState === exports.StompSocketState.OPEN) {
714 this._closeOrDiscardWebsocket();
715 }
716 }
717 }
718 _closeWebsocket() {
719 this._webSocket.onmessage = () => { }; // ignore messages
720 this._webSocket.close();
721 }
722 discardWebsocket() {
723 if (typeof this._webSocket.terminate !== 'function') {
724 augmentWebsocket(this._webSocket, (msg) => this.debug(msg));
725 }
726 // @ts-ignore - this method will be there at this stage
727 this._webSocket.terminate();
728 }
729 _transmit(params) {
730 const { command, headers, body, binaryBody, skipContentLengthHeader } = params;
731 const frame = new FrameImpl({
732 command,
733 headers,
734 body,
735 binaryBody,
736 escapeHeaderValues: this._escapeHeaderValues,
737 skipContentLengthHeader,
738 });
739 let rawChunk = frame.serialize();
740 if (this.logRawCommunication) {
741 this.debug(`>>> ${rawChunk}`);
742 }
743 else {
744 this.debug(`>>> ${frame}`);
745 }
746 if (this.forceBinaryWSFrames && typeof rawChunk === 'string') {
747 rawChunk = new TextEncoder().encode(rawChunk);
748 }
749 if (typeof rawChunk !== 'string' || !this.splitLargeFrames) {
750 this._webSocket.send(rawChunk);
751 }
752 else {
753 let out = rawChunk;
754 while (out.length > 0) {
755 const chunk = out.substring(0, this.maxWebSocketChunkSize);
756 out = out.substring(this.maxWebSocketChunkSize);
757 this._webSocket.send(chunk);
758 this.debug(`chunk sent = ${chunk.length}, remaining = ${out.length}`);
759 }
760 }
761 }
762 dispose() {
763 if (this.connected) {
764 try {
765 // clone before updating
766 const disconnectHeaders = Object.assign({}, this.disconnectHeaders);
767 if (!disconnectHeaders.receipt) {
768 disconnectHeaders.receipt = `close-${this._counter++}`;
769 }
770 this.watchForReceipt(disconnectHeaders.receipt, frame => {
771 this._closeWebsocket();
772 this._cleanUp();
773 this.onDisconnect(frame);
774 });
775 this._transmit({ command: 'DISCONNECT', headers: disconnectHeaders });
776 }
777 catch (error) {
778 this.debug(`Ignoring error during disconnect ${error}`);
779 }
780 }
781 else {
782 if (this._webSocket.readyState === exports.StompSocketState.CONNECTING ||
783 this._webSocket.readyState === exports.StompSocketState.OPEN) {
784 this._closeWebsocket();
785 }
786 }
787 }
788 _cleanUp() {
789 this._connected = false;
790 if (this._pinger) {
791 clearInterval(this._pinger);
792 this._pinger = undefined;
793 }
794 if (this._ponger) {
795 clearInterval(this._ponger);
796 this._ponger = undefined;
797 }
798 }
799 publish(params) {
800 const { destination, headers, body, binaryBody, skipContentLengthHeader } = params;
801 const hdrs = Object.assign({ destination }, headers);
802 this._transmit({
803 command: 'SEND',
804 headers: hdrs,
805 body,
806 binaryBody,
807 skipContentLengthHeader,
808 });
809 }
810 watchForReceipt(receiptId, callback) {
811 this._receiptWatchers[receiptId] = callback;
812 }
813 subscribe(destination, callback, headers = {}) {
814 headers = Object.assign({}, headers);
815 if (!headers.id) {
816 headers.id = `sub-${this._counter++}`;
817 }
818 headers.destination = destination;
819 this._subscriptions[headers.id] = callback;
820 this._transmit({ command: 'SUBSCRIBE', headers });
821 const client = this;
822 return {
823 id: headers.id,
824 unsubscribe(hdrs) {
825 return client.unsubscribe(headers.id, hdrs);
826 },
827 };
828 }
829 unsubscribe(id, headers = {}) {
830 headers = Object.assign({}, headers);
831 delete this._subscriptions[id];
832 headers.id = id;
833 this._transmit({ command: 'UNSUBSCRIBE', headers });
834 }
835 begin(transactionId) {
836 const txId = transactionId || `tx-${this._counter++}`;
837 this._transmit({
838 command: 'BEGIN',
839 headers: {
840 transaction: txId,
841 },
842 });
843 const client = this;
844 return {
845 id: txId,
846 commit() {
847 client.commit(txId);
848 },
849 abort() {
850 client.abort(txId);
851 },
852 };
853 }
854 commit(transactionId) {
855 this._transmit({
856 command: 'COMMIT',
857 headers: {
858 transaction: transactionId,
859 },
860 });
861 }
862 abort(transactionId) {
863 this._transmit({
864 command: 'ABORT',
865 headers: {
866 transaction: transactionId,
867 },
868 });
869 }
870 ack(messageId, subscriptionId, headers = {}) {
871 headers = Object.assign({}, headers);
872 if (this._connectedVersion === Versions.V1_2) {
873 headers.id = messageId;
874 }
875 else {
876 headers['message-id'] = messageId;
877 }
878 headers.subscription = subscriptionId;
879 this._transmit({ command: 'ACK', headers });
880 }
881 nack(messageId, subscriptionId, headers = {}) {
882 headers = Object.assign({}, headers);
883 if (this._connectedVersion === Versions.V1_2) {
884 headers.id = messageId;
885 }
886 else {
887 headers['message-id'] = messageId;
888 }
889 headers.subscription = subscriptionId;
890 return this._transmit({ command: 'NACK', headers });
891 }
892 }
893
894 /**
895 * STOMP Client Class.
896 *
897 * Part of `@stomp/stompjs`.
898 */
899 class Client {
900 /**
901 * Create an instance.
902 */
903 constructor(conf = {}) {
904 /**
905 * STOMP versions to attempt during STOMP handshake. By default, versions `1.2`, `1.1`, and `1.0` are attempted.
906 *
907 * Example:
908 * ```javascript
909 * // Try only versions 1.1 and 1.0
910 * client.stompVersions = new Versions(['1.1', '1.0'])
911 * ```
912 */
913 this.stompVersions = Versions.default;
914 /**
915 * Will retry if Stomp connection is not established in specified milliseconds.
916 * Default 0, which switches off automatic reconnection.
917 */
918 this.connectionTimeout = 0;
919 /**
920 * automatically reconnect with delay in milliseconds, set to 0 to disable.
921 */
922 this.reconnectDelay = 5000;
923 /**
924 * Incoming heartbeat interval in milliseconds. Set to 0 to disable.
925 */
926 this.heartbeatIncoming = 10000;
927 /**
928 * Outgoing heartbeat interval in milliseconds. Set to 0 to disable.
929 */
930 this.heartbeatOutgoing = 10000;
931 /**
932 * This switches on a non-standard behavior while sending WebSocket packets.
933 * It splits larger (text) packets into chunks of [maxWebSocketChunkSize]{@link Client#maxWebSocketChunkSize}.
934 * Only Java Spring brokers seem to support this mode.
935 *
936 * WebSockets, by itself, split large (text) packets,
937 * so it is not needed with a truly compliant STOMP/WebSocket broker.
938 * Setting it for such a broker will cause large messages to fail.
939 *
940 * `false` by default.
941 *
942 * Binary frames are never split.
943 */
944 this.splitLargeFrames = false;
945 /**
946 * See [splitLargeFrames]{@link Client#splitLargeFrames}.
947 * This has no effect if [splitLargeFrames]{@link Client#splitLargeFrames} is `false`.
948 */
949 this.maxWebSocketChunkSize = 8 * 1024;
950 /**
951 * Usually the
952 * [type of WebSocket frame]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send#Parameters}
953 * is automatically decided by type of the payload.
954 * Default is `false`, which should work with all compliant brokers.
955 *
956 * Set this flag to force binary frames.
957 */
958 this.forceBinaryWSFrames = false;
959 /**
960 * A bug in ReactNative chops a string on occurrence of a NULL.
961 * See issue [https://github.com/stomp-js/stompjs/issues/89]{@link https://github.com/stomp-js/stompjs/issues/89}.
962 * This makes incoming WebSocket messages invalid STOMP packets.
963 * Setting this flag attempts to reverse the damage by appending a NULL.
964 * If the broker splits a large message into multiple WebSocket messages,
965 * this flag will cause data loss and abnormal termination of connection.
966 *
967 * This is not an ideal solution, but a stop gap until the underlying issue is fixed at ReactNative library.
968 */
969 this.appendMissingNULLonIncoming = false;
970 /**
971 * Browsers do not immediately close WebSockets when `.close` is issued.
972 * This may cause reconnection to take a significantly long time in case
973 * of some types of failures.
974 * In case of incoming heartbeat failure, this experimental flag instructs
975 * the library to discard the socket immediately
976 * (even before it is actually closed).
977 */
978 this.discardWebsocketOnCommFailure = false;
979 /**
980 * Activation state.
981 *
982 * It will usually be ACTIVE or INACTIVE.
983 * When deactivating, it may go from ACTIVE to INACTIVE without entering DEACTIVATING.
984 */
985 this.state = exports.ActivationState.INACTIVE;
986 // No op callbacks
987 const noOp = () => { };
988 this.debug = noOp;
989 this.beforeConnect = noOp;
990 this.onConnect = noOp;
991 this.onDisconnect = noOp;
992 this.onUnhandledMessage = noOp;
993 this.onUnhandledReceipt = noOp;
994 this.onUnhandledFrame = noOp;
995 this.onStompError = noOp;
996 this.onWebSocketClose = noOp;
997 this.onWebSocketError = noOp;
998 this.logRawCommunication = false;
999 this.onChangeState = noOp;
1000 // These parameters would typically get proper values before connect is called
1001 this.connectHeaders = {};
1002 this._disconnectHeaders = {};
1003 // Apply configuration
1004 this.configure(conf);
1005 }
1006 /**
1007 * Underlying WebSocket instance, READONLY.
1008 */
1009 get webSocket() {
1010 return this._stompHandler?._webSocket;
1011 }
1012 /**
1013 * Disconnection headers.
1014 */
1015 get disconnectHeaders() {
1016 return this._disconnectHeaders;
1017 }
1018 set disconnectHeaders(value) {
1019 this._disconnectHeaders = value;
1020 if (this._stompHandler) {
1021 this._stompHandler.disconnectHeaders = this._disconnectHeaders;
1022 }
1023 }
1024 /**
1025 * `true` if there is an active connection to STOMP Broker
1026 */
1027 get connected() {
1028 return !!this._stompHandler && this._stompHandler.connected;
1029 }
1030 /**
1031 * version of STOMP protocol negotiated with the server, READONLY
1032 */
1033 get connectedVersion() {
1034 return this._stompHandler ? this._stompHandler.connectedVersion : undefined;
1035 }
1036 /**
1037 * if the client is active (connected or going to reconnect)
1038 */
1039 get active() {
1040 return this.state === exports.ActivationState.ACTIVE;
1041 }
1042 _changeState(state) {
1043 this.state = state;
1044 this.onChangeState(state);
1045 }
1046 /**
1047 * Update configuration.
1048 */
1049 configure(conf) {
1050 // bulk assign all properties to this
1051 Object.assign(this, conf);
1052 }
1053 /**
1054 * Initiate the connection with the broker.
1055 * If the connection breaks, as per [Client#reconnectDelay]{@link Client#reconnectDelay},
1056 * it will keep trying to reconnect.
1057 *
1058 * Call [Client#deactivate]{@link Client#deactivate} to disconnect and stop reconnection attempts.
1059 */
1060 activate() {
1061 const _activate = () => {
1062 if (this.active) {
1063 this.debug('Already ACTIVE, ignoring request to activate');
1064 return;
1065 }
1066 this._changeState(exports.ActivationState.ACTIVE);
1067 this._connect();
1068 };
1069 // if it is deactivating, wait for it to complete before activating.
1070 if (this.state === exports.ActivationState.DEACTIVATING) {
1071 this.debug('Waiting for deactivation to finish before activating');
1072 this.deactivate().then(() => {
1073 _activate();
1074 });
1075 }
1076 else {
1077 _activate();
1078 }
1079 }
1080 async _connect() {
1081 await this.beforeConnect();
1082 if (this._stompHandler) {
1083 this.debug('There is already a stompHandler, skipping the call to connect');
1084 return;
1085 }
1086 if (!this.active) {
1087 this.debug('Client has been marked inactive, will not attempt to connect');
1088 return;
1089 }
1090 // setup connection watcher
1091 if (this.connectionTimeout > 0) {
1092 // clear first
1093 if (this._connectionWatcher) {
1094 clearTimeout(this._connectionWatcher);
1095 }
1096 this._connectionWatcher = setTimeout(() => {
1097 if (this.connected) {
1098 return;
1099 }
1100 // Connection not established, close the underlying socket
1101 // a reconnection will be attempted
1102 this.debug(`Connection not established in ${this.connectionTimeout}ms, closing socket`);
1103 this.forceDisconnect();
1104 }, this.connectionTimeout);
1105 }
1106 this.debug('Opening Web Socket...');
1107 // Get the actual WebSocket (or a similar object)
1108 const webSocket = this._createWebSocket();
1109 this._stompHandler = new StompHandler(this, webSocket, {
1110 debug: this.debug,
1111 stompVersions: this.stompVersions,
1112 connectHeaders: this.connectHeaders,
1113 disconnectHeaders: this._disconnectHeaders,
1114 heartbeatIncoming: this.heartbeatIncoming,
1115 heartbeatOutgoing: this.heartbeatOutgoing,
1116 splitLargeFrames: this.splitLargeFrames,
1117 maxWebSocketChunkSize: this.maxWebSocketChunkSize,
1118 forceBinaryWSFrames: this.forceBinaryWSFrames,
1119 logRawCommunication: this.logRawCommunication,
1120 appendMissingNULLonIncoming: this.appendMissingNULLonIncoming,
1121 discardWebsocketOnCommFailure: this.discardWebsocketOnCommFailure,
1122 onConnect: frame => {
1123 // Successfully connected, stop the connection watcher
1124 if (this._connectionWatcher) {
1125 clearTimeout(this._connectionWatcher);
1126 this._connectionWatcher = undefined;
1127 }
1128 if (!this.active) {
1129 this.debug('STOMP got connected while deactivate was issued, will disconnect now');
1130 this._disposeStompHandler();
1131 return;
1132 }
1133 this.onConnect(frame);
1134 },
1135 onDisconnect: frame => {
1136 this.onDisconnect(frame);
1137 },
1138 onStompError: frame => {
1139 this.onStompError(frame);
1140 },
1141 onWebSocketClose: evt => {
1142 this._stompHandler = undefined; // a new one will be created in case of a reconnect
1143 if (this.state === exports.ActivationState.DEACTIVATING) {
1144 // Mark deactivation complete
1145 this._changeState(exports.ActivationState.INACTIVE);
1146 }
1147 // The callback is called before attempting to reconnect, this would allow the client
1148 // to be `deactivated` in the callback.
1149 this.onWebSocketClose(evt);
1150 if (this.active) {
1151 this._schedule_reconnect();
1152 }
1153 },
1154 onWebSocketError: evt => {
1155 this.onWebSocketError(evt);
1156 },
1157 onUnhandledMessage: message => {
1158 this.onUnhandledMessage(message);
1159 },
1160 onUnhandledReceipt: frame => {
1161 this.onUnhandledReceipt(frame);
1162 },
1163 onUnhandledFrame: frame => {
1164 this.onUnhandledFrame(frame);
1165 },
1166 });
1167 this._stompHandler.start();
1168 }
1169 _createWebSocket() {
1170 let webSocket;
1171 if (this.webSocketFactory) {
1172 webSocket = this.webSocketFactory();
1173 }
1174 else if (this.brokerURL) {
1175 webSocket = new WebSocket(this.brokerURL, this.stompVersions.protocolVersions());
1176 }
1177 else {
1178 throw new Error('Either brokerURL or webSocketFactory must be provided');
1179 }
1180 webSocket.binaryType = 'arraybuffer';
1181 return webSocket;
1182 }
1183 _schedule_reconnect() {
1184 if (this.reconnectDelay > 0) {
1185 this.debug(`STOMP: scheduling reconnection in ${this.reconnectDelay}ms`);
1186 this._reconnector = setTimeout(() => {
1187 this._connect();
1188 }, this.reconnectDelay);
1189 }
1190 }
1191 /**
1192 * Disconnect if connected and stop auto reconnect loop.
1193 * Appropriate callbacks will be invoked if there is an underlying STOMP connection.
1194 *
1195 * This call is async. It will resolve immediately if there is no underlying active websocket,
1196 * otherwise, it will resolve after the underlying websocket is properly disposed of.
1197 *
1198 * It is not an error to invoke this method more than once.
1199 * Each of those would resolve on completion of deactivation.
1200 *
1201 * To reactivate, you can call [Client#activate]{@link Client#activate}.
1202 *
1203 * Experimental: pass `force: true` to immediately discard the underlying connection.
1204 * This mode will skip both the STOMP and the Websocket shutdown sequences.
1205 * In some cases, browsers take a long time in the Websocket shutdown
1206 * if the underlying connection had gone stale.
1207 * Using this mode can speed up.
1208 * When this mode is used, the actual Websocket may linger for a while
1209 * and the broker may not realize that the connection is no longer in use.
1210 *
1211 * It is possible to invoke this method initially without the `force` option
1212 * and subsequently, say after a wait, with the `force` option.
1213 */
1214 async deactivate(options = {}) {
1215 const force = options.force || false;
1216 const needToDispose = this.active;
1217 let retPromise;
1218 if (this.state === exports.ActivationState.INACTIVE) {
1219 this.debug(`Already INACTIVE, nothing more to do`);
1220 return Promise.resolve();
1221 }
1222 this._changeState(exports.ActivationState.DEACTIVATING);
1223 // Clear if a reconnection was scheduled
1224 if (this._reconnector) {
1225 clearTimeout(this._reconnector);
1226 this._reconnector = undefined;
1227 }
1228 if (this._stompHandler &&
1229 // @ts-ignore - if there is a _stompHandler, there is the webSocket
1230 this.webSocket.readyState !== exports.StompSocketState.CLOSED) {
1231 const origOnWebSocketClose = this._stompHandler.onWebSocketClose;
1232 // we need to wait for the underlying websocket to close
1233 retPromise = new Promise((resolve, reject) => {
1234 // @ts-ignore - there is a _stompHandler
1235 this._stompHandler.onWebSocketClose = evt => {
1236 origOnWebSocketClose(evt);
1237 resolve();
1238 };
1239 });
1240 }
1241 else {
1242 // indicate that auto reconnect loop should terminate
1243 this._changeState(exports.ActivationState.INACTIVE);
1244 return Promise.resolve();
1245 }
1246 if (force) {
1247 this._stompHandler?.discardWebsocket();
1248 }
1249 else if (needToDispose) {
1250 this._disposeStompHandler();
1251 }
1252 return retPromise;
1253 }
1254 /**
1255 * Force disconnect if there is an active connection by directly closing the underlying WebSocket.
1256 * This is different from a normal disconnect where a DISCONNECT sequence is carried out with the broker.
1257 * After forcing disconnect, automatic reconnect will be attempted.
1258 * To stop further reconnects call [Client#deactivate]{@link Client#deactivate} as well.
1259 */
1260 forceDisconnect() {
1261 if (this._stompHandler) {
1262 this._stompHandler.forceDisconnect();
1263 }
1264 }
1265 _disposeStompHandler() {
1266 // Dispose STOMP Handler
1267 if (this._stompHandler) {
1268 this._stompHandler.dispose();
1269 }
1270 }
1271 /**
1272 * Send a message to a named destination. Refer to your STOMP broker documentation for types
1273 * and naming of destinations.
1274 *
1275 * STOMP protocol specifies and suggests some headers and also allows broker-specific headers.
1276 *
1277 * `body` must be String.
1278 * You will need to covert the payload to string in case it is not string (e.g. JSON).
1279 *
1280 * To send a binary message body, use `binaryBody` parameter. It should be a
1281 * [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array).
1282 * Sometimes brokers may not support binary frames out of the box.
1283 * Please check your broker documentation.
1284 *
1285 * `content-length` header is automatically added to the STOMP Frame sent to the broker.
1286 * Set `skipContentLengthHeader` to indicate that `content-length` header should not be added.
1287 * For binary messages, `content-length` header is always added.
1288 *
1289 * Caution: The broker will, most likely, report an error and disconnect
1290 * if the message body has NULL octet(s) and `content-length` header is missing.
1291 *
1292 * ```javascript
1293 * client.publish({destination: "/queue/test", headers: {priority: 9}, body: "Hello, STOMP"});
1294 *
1295 * // Only destination is mandatory parameter
1296 * client.publish({destination: "/queue/test", body: "Hello, STOMP"});
1297 *
1298 * // Skip content-length header in the frame to the broker
1299 * client.publish({"/queue/test", body: "Hello, STOMP", skipContentLengthHeader: true});
1300 *
1301 * var binaryData = generateBinaryData(); // This need to be of type Uint8Array
1302 * // setting content-type header is not mandatory, however a good practice
1303 * client.publish({destination: '/topic/special', binaryBody: binaryData,
1304 * headers: {'content-type': 'application/octet-stream'}});
1305 * ```
1306 */
1307 publish(params) {
1308 this._checkConnection();
1309 // @ts-ignore - we already checked that there is a _stompHandler, and it is connected
1310 this._stompHandler.publish(params);
1311 }
1312 _checkConnection() {
1313 if (!this.connected) {
1314 throw new TypeError('There is no underlying STOMP connection');
1315 }
1316 }
1317 /**
1318 * STOMP brokers may carry out operation asynchronously and allow requesting for acknowledgement.
1319 * To request an acknowledgement, a `receipt` header needs to be sent with the actual request.
1320 * The value (say receipt-id) for this header needs to be unique for each use.
1321 * Typically, a sequence, a UUID, a random number or a combination may be used.
1322 *
1323 * A complaint broker will send a RECEIPT frame when an operation has actually been completed.
1324 * The operation needs to be matched based on the value of the receipt-id.
1325 *
1326 * This method allows watching for a receipt and invoking the callback
1327 * when the corresponding receipt has been received.
1328 *
1329 * The actual {@link IFrame} will be passed as parameter to the callback.
1330 *
1331 * Example:
1332 * ```javascript
1333 * // Subscribing with acknowledgement
1334 * let receiptId = randomText();
1335 *
1336 * client.watchForReceipt(receiptId, function() {
1337 * // Will be called after server acknowledges
1338 * });
1339 *
1340 * client.subscribe(TEST.destination, onMessage, {receipt: receiptId});
1341 *
1342 *
1343 * // Publishing with acknowledgement
1344 * receiptId = randomText();
1345 *
1346 * client.watchForReceipt(receiptId, function() {
1347 * // Will be called after server acknowledges
1348 * });
1349 * client.publish({destination: TEST.destination, headers: {receipt: receiptId}, body: msg});
1350 * ```
1351 */
1352 watchForReceipt(receiptId, callback) {
1353 this._checkConnection();
1354 // @ts-ignore - we already checked that there is a _stompHandler, and it is connected
1355 this._stompHandler.watchForReceipt(receiptId, callback);
1356 }
1357 /**
1358 * Subscribe to a STOMP Broker location. The callback will be invoked for each
1359 * received message with the {@link IMessage} as argument.
1360 *
1361 * Note: The library will generate a unique ID if there is none provided in the headers.
1362 * To use your own ID, pass it using the `headers` argument.
1363 *
1364 * ```javascript
1365 * callback = function(message) {
1366 * // called when the client receives a STOMP message from the server
1367 * if (message.body) {
1368 * alert("got message with body " + message.body)
1369 * } else {
1370 * alert("got empty message");
1371 * }
1372 * });
1373 *
1374 * var subscription = client.subscribe("/queue/test", callback);
1375 *
1376 * // Explicit subscription id
1377 * var mySubId = 'my-subscription-id-001';
1378 * var subscription = client.subscribe(destination, callback, { id: mySubId });
1379 * ```
1380 */
1381 subscribe(destination, callback, headers = {}) {
1382 this._checkConnection();
1383 // @ts-ignore - we already checked that there is a _stompHandler, and it is connected
1384 return this._stompHandler.subscribe(destination, callback, headers);
1385 }
1386 /**
1387 * It is preferable to unsubscribe from a subscription by calling
1388 * `unsubscribe()` directly on {@link StompSubscription} returned by `client.subscribe()`:
1389 *
1390 * ```javascript
1391 * var subscription = client.subscribe(destination, onmessage);
1392 * // ...
1393 * subscription.unsubscribe();
1394 * ```
1395 *
1396 * See: https://stomp.github.com/stomp-specification-1.2.html#UNSUBSCRIBE UNSUBSCRIBE Frame
1397 */
1398 unsubscribe(id, headers = {}) {
1399 this._checkConnection();
1400 // @ts-ignore - we already checked that there is a _stompHandler, and it is connected
1401 this._stompHandler.unsubscribe(id, headers);
1402 }
1403 /**
1404 * Start a transaction, the returned {@link ITransaction} has methods - [commit]{@link ITransaction#commit}
1405 * and [abort]{@link ITransaction#abort}.
1406 *
1407 * `transactionId` is optional, if not passed the library will generate it internally.
1408 */
1409 begin(transactionId) {
1410 this._checkConnection();
1411 // @ts-ignore - we already checked that there is a _stompHandler, and it is connected
1412 return this._stompHandler.begin(transactionId);
1413 }
1414 /**
1415 * Commit a transaction.
1416 *
1417 * It is preferable to commit a transaction by calling [commit]{@link ITransaction#commit} directly on
1418 * {@link ITransaction} returned by [client.begin]{@link Client#begin}.
1419 *
1420 * ```javascript
1421 * var tx = client.begin(txId);
1422 * //...
1423 * tx.commit();
1424 * ```
1425 */
1426 commit(transactionId) {
1427 this._checkConnection();
1428 // @ts-ignore - we already checked that there is a _stompHandler, and it is connected
1429 this._stompHandler.commit(transactionId);
1430 }
1431 /**
1432 * Abort a transaction.
1433 * It is preferable to abort a transaction by calling [abort]{@link ITransaction#abort} directly on
1434 * {@link ITransaction} returned by [client.begin]{@link Client#begin}.
1435 *
1436 * ```javascript
1437 * var tx = client.begin(txId);
1438 * //...
1439 * tx.abort();
1440 * ```
1441 */
1442 abort(transactionId) {
1443 this._checkConnection();
1444 // @ts-ignore - we already checked that there is a _stompHandler, and it is connected
1445 this._stompHandler.abort(transactionId);
1446 }
1447 /**
1448 * ACK a message. It is preferable to acknowledge a message by calling [ack]{@link IMessage#ack} directly
1449 * on the {@link IMessage} handled by a subscription callback:
1450 *
1451 * ```javascript
1452 * var callback = function (message) {
1453 * // process the message
1454 * // acknowledge it
1455 * message.ack();
1456 * };
1457 * client.subscribe(destination, callback, {'ack': 'client'});
1458 * ```
1459 */
1460 ack(messageId, subscriptionId, headers = {}) {
1461 this._checkConnection();
1462 // @ts-ignore - we already checked that there is a _stompHandler, and it is connected
1463 this._stompHandler.ack(messageId, subscriptionId, headers);
1464 }
1465 /**
1466 * NACK a message. It is preferable to acknowledge a message by calling [nack]{@link IMessage#nack} directly
1467 * on the {@link IMessage} handled by a subscription callback:
1468 *
1469 * ```javascript
1470 * var callback = function (message) {
1471 * // process the message
1472 * // an error occurs, nack it
1473 * message.nack();
1474 * };
1475 * client.subscribe(destination, callback, {'ack': 'client'});
1476 * ```
1477 */
1478 nack(messageId, subscriptionId, headers = {}) {
1479 this._checkConnection();
1480 // @ts-ignore - we already checked that there is a _stompHandler, and it is connected
1481 this._stompHandler.nack(messageId, subscriptionId, headers);
1482 }
1483 }
1484
1485 /**
1486 * Configuration options for STOMP Client, each key corresponds to
1487 * field by the same name in {@link Client}. This can be passed to
1488 * the constructor of {@link Client} or to [Client#configure]{@link Client#configure}.
1489 *
1490 * Part of `@stomp/stompjs`.
1491 */
1492 class StompConfig {
1493 }
1494
1495 /**
1496 * STOMP headers. Many functions calls will accept headers as parameters.
1497 * The headers sent by Broker will be available as [IFrame#headers]{@link IFrame#headers}.
1498 *
1499 * `key` and `value` must be valid strings.
1500 * In addition, `key` must not contain `CR`, `LF`, or `:`.
1501 *
1502 * Part of `@stomp/stompjs`.
1503 */
1504 class StompHeaders {
1505 }
1506
1507 /**
1508 * Part of `@stomp/stompjs`.
1509 *
1510 * @internal
1511 */
1512 class HeartbeatInfo {
1513 constructor(client) {
1514 this.client = client;
1515 }
1516 get outgoing() {
1517 return this.client.heartbeatOutgoing;
1518 }
1519 set outgoing(value) {
1520 this.client.heartbeatOutgoing = value;
1521 }
1522 get incoming() {
1523 return this.client.heartbeatIncoming;
1524 }
1525 set incoming(value) {
1526 this.client.heartbeatIncoming = value;
1527 }
1528 }
1529
1530 /**
1531 * Available for backward compatibility, please shift to using {@link Client}.
1532 *
1533 * **Deprecated**
1534 *
1535 * Part of `@stomp/stompjs`.
1536 *
1537 * To upgrade, please follow the [Upgrade Guide](https://stomp-js.github.io/guide/stompjs/upgrading-stompjs.html)
1538 */
1539 class CompatClient extends Client {
1540 /**
1541 * Available for backward compatibility, please shift to using {@link Client}
1542 * and [Client#webSocketFactory]{@link Client#webSocketFactory}.
1543 *
1544 * **Deprecated**
1545 *
1546 * @internal
1547 */
1548 constructor(webSocketFactory) {
1549 super();
1550 /**
1551 * It is no op now. No longer needed. Large packets work out of the box.
1552 */
1553 this.maxWebSocketFrameSize = 16 * 1024;
1554 this._heartbeatInfo = new HeartbeatInfo(this);
1555 this.reconnect_delay = 0;
1556 this.webSocketFactory = webSocketFactory;
1557 // Default from previous version
1558 this.debug = (...message) => {
1559 console.log(...message);
1560 };
1561 }
1562 _parseConnect(...args) {
1563 let closeEventCallback;
1564 let connectCallback;
1565 let errorCallback;
1566 let headers = {};
1567 if (args.length < 2) {
1568 throw new Error('Connect requires at least 2 arguments');
1569 }
1570 if (typeof args[1] === 'function') {
1571 [headers, connectCallback, errorCallback, closeEventCallback] = args;
1572 }
1573 else {
1574 switch (args.length) {
1575 case 6:
1576 [
1577 headers.login,
1578 headers.passcode,
1579 connectCallback,
1580 errorCallback,
1581 closeEventCallback,
1582 headers.host,
1583 ] = args;
1584 break;
1585 default:
1586 [
1587 headers.login,
1588 headers.passcode,
1589 connectCallback,
1590 errorCallback,
1591 closeEventCallback,
1592 ] = args;
1593 }
1594 }
1595 return [headers, connectCallback, errorCallback, closeEventCallback];
1596 }
1597 /**
1598 * Available for backward compatibility, please shift to using [Client#activate]{@link Client#activate}.
1599 *
1600 * **Deprecated**
1601 *
1602 * The `connect` method accepts different number of arguments and types. See the Overloads list. Use the
1603 * version with headers to pass your broker specific options.
1604 *
1605 * overloads:
1606 * - connect(headers, connectCallback)
1607 * - connect(headers, connectCallback, errorCallback)
1608 * - connect(login, passcode, connectCallback)
1609 * - connect(login, passcode, connectCallback, errorCallback)
1610 * - connect(login, passcode, connectCallback, errorCallback, closeEventCallback)
1611 * - connect(login, passcode, connectCallback, errorCallback, closeEventCallback, host)
1612 *
1613 * params:
1614 * - headers, see [Client#connectHeaders]{@link Client#connectHeaders}
1615 * - connectCallback, see [Client#onConnect]{@link Client#onConnect}
1616 * - errorCallback, see [Client#onStompError]{@link Client#onStompError}
1617 * - closeEventCallback, see [Client#onWebSocketClose]{@link Client#onWebSocketClose}
1618 * - login [String], see [Client#connectHeaders](../classes/Client.html#connectHeaders)
1619 * - passcode [String], [Client#connectHeaders](../classes/Client.html#connectHeaders)
1620 * - host [String], see [Client#connectHeaders](../classes/Client.html#connectHeaders)
1621 *
1622 * To upgrade, please follow the [Upgrade Guide](../additional-documentation/upgrading.html)
1623 */
1624 connect(...args) {
1625 const out = this._parseConnect(...args);
1626 if (out[0]) {
1627 this.connectHeaders = out[0];
1628 }
1629 if (out[1]) {
1630 this.onConnect = out[1];
1631 }
1632 if (out[2]) {
1633 this.onStompError = out[2];
1634 }
1635 if (out[3]) {
1636 this.onWebSocketClose = out[3];
1637 }
1638 super.activate();
1639 }
1640 /**
1641 * Available for backward compatibility, please shift to using [Client#deactivate]{@link Client#deactivate}.
1642 *
1643 * **Deprecated**
1644 *
1645 * See:
1646 * [Client#onDisconnect]{@link Client#onDisconnect}, and
1647 * [Client#disconnectHeaders]{@link Client#disconnectHeaders}
1648 *
1649 * To upgrade, please follow the [Upgrade Guide](../additional-documentation/upgrading.html)
1650 */
1651 disconnect(disconnectCallback, headers = {}) {
1652 if (disconnectCallback) {
1653 this.onDisconnect = disconnectCallback;
1654 }
1655 this.disconnectHeaders = headers;
1656 super.deactivate();
1657 }
1658 /**
1659 * Available for backward compatibility, use [Client#publish]{@link Client#publish}.
1660 *
1661 * Send a message to a named destination. Refer to your STOMP broker documentation for types
1662 * and naming of destinations. The headers will, typically, be available to the subscriber.
1663 * However, there may be special purpose headers corresponding to your STOMP broker.
1664 *
1665 * **Deprecated**, use [Client#publish]{@link Client#publish}
1666 *
1667 * Note: Body must be String. You will need to covert the payload to string in case it is not string (e.g. JSON)
1668 *
1669 * ```javascript
1670 * client.send("/queue/test", {priority: 9}, "Hello, STOMP");
1671 *
1672 * // If you want to send a message with a body, you must also pass the headers argument.
1673 * client.send("/queue/test", {}, "Hello, STOMP");
1674 * ```
1675 *
1676 * To upgrade, please follow the [Upgrade Guide](../additional-documentation/upgrading.html)
1677 */
1678 send(destination, headers = {}, body = '') {
1679 headers = Object.assign({}, headers);
1680 const skipContentLengthHeader = headers['content-length'] === false;
1681 if (skipContentLengthHeader) {
1682 delete headers['content-length'];
1683 }
1684 this.publish({
1685 destination,
1686 headers: headers,
1687 body,
1688 skipContentLengthHeader,
1689 });
1690 }
1691 /**
1692 * Available for backward compatibility, renamed to [Client#reconnectDelay]{@link Client#reconnectDelay}.
1693 *
1694 * **Deprecated**
1695 */
1696 set reconnect_delay(value) {
1697 this.reconnectDelay = value;
1698 }
1699 /**
1700 * Available for backward compatibility, renamed to [Client#webSocket]{@link Client#webSocket}.
1701 *
1702 * **Deprecated**
1703 */
1704 get ws() {
1705 return this.webSocket;
1706 }
1707 /**
1708 * Available for backward compatibility, renamed to [Client#connectedVersion]{@link Client#connectedVersion}.
1709 *
1710 * **Deprecated**
1711 */
1712 get version() {
1713 return this.connectedVersion;
1714 }
1715 /**
1716 * Available for backward compatibility, renamed to [Client#onUnhandledMessage]{@link Client#onUnhandledMessage}.
1717 *
1718 * **Deprecated**
1719 */
1720 get onreceive() {
1721 return this.onUnhandledMessage;
1722 }
1723 /**
1724 * Available for backward compatibility, renamed to [Client#onUnhandledMessage]{@link Client#onUnhandledMessage}.
1725 *
1726 * **Deprecated**
1727 */
1728 set onreceive(value) {
1729 this.onUnhandledMessage = value;
1730 }
1731 /**
1732 * Available for backward compatibility, renamed to [Client#onUnhandledReceipt]{@link Client#onUnhandledReceipt}.
1733 * Prefer using [Client#watchForReceipt]{@link Client#watchForReceipt}.
1734 *
1735 * **Deprecated**
1736 */
1737 get onreceipt() {
1738 return this.onUnhandledReceipt;
1739 }
1740 /**
1741 * Available for backward compatibility, renamed to [Client#onUnhandledReceipt]{@link Client#onUnhandledReceipt}.
1742 *
1743 * **Deprecated**
1744 */
1745 set onreceipt(value) {
1746 this.onUnhandledReceipt = value;
1747 }
1748 /**
1749 * Available for backward compatibility, renamed to [Client#heartbeatIncoming]{@link Client#heartbeatIncoming}
1750 * [Client#heartbeatOutgoing]{@link Client#heartbeatOutgoing}.
1751 *
1752 * **Deprecated**
1753 */
1754 get heartbeat() {
1755 return this._heartbeatInfo;
1756 }
1757 /**
1758 * Available for backward compatibility, renamed to [Client#heartbeatIncoming]{@link Client#heartbeatIncoming}
1759 * [Client#heartbeatOutgoing]{@link Client#heartbeatOutgoing}.
1760 *
1761 * **Deprecated**
1762 */
1763 set heartbeat(value) {
1764 this.heartbeatIncoming = value.incoming;
1765 this.heartbeatOutgoing = value.outgoing;
1766 }
1767 }
1768
1769 /**
1770 * STOMP Class, acts like a factory to create {@link Client}.
1771 *
1772 * Part of `@stomp/stompjs`.
1773 *
1774 * **Deprecated**
1775 *
1776 * It will be removed in next major version. Please switch to {@link Client}.
1777 */
1778 class Stomp {
1779 /**
1780 * This method creates a WebSocket client that is connected to
1781 * the STOMP server located at the url.
1782 *
1783 * ```javascript
1784 * var url = "ws://localhost:61614/stomp";
1785 * var client = Stomp.client(url);
1786 * ```
1787 *
1788 * **Deprecated**
1789 *
1790 * It will be removed in next major version. Please switch to {@link Client}
1791 * using [Client#brokerURL]{@link Client#brokerURL}.
1792 */
1793 static client(url, protocols) {
1794 // This is a hack to allow another implementation than the standard
1795 // HTML5 WebSocket class.
1796 //
1797 // It is possible to use another class by calling
1798 //
1799 // Stomp.WebSocketClass = MozWebSocket
1800 //
1801 // *prior* to call `Stomp.client()`.
1802 //
1803 // This hack is deprecated and `Stomp.over()` method should be used
1804 // instead.
1805 // See remarks on the function Stomp.over
1806 if (protocols == null) {
1807 protocols = Versions.default.protocolVersions();
1808 }
1809 const wsFn = () => {
1810 const klass = Stomp.WebSocketClass || WebSocket;
1811 return new klass(url, protocols);
1812 };
1813 return new CompatClient(wsFn);
1814 }
1815 /**
1816 * This method is an alternative to [Stomp#client]{@link Stomp#client} to let the user
1817 * specify the WebSocket to use (either a standard HTML5 WebSocket or
1818 * a similar object).
1819 *
1820 * In order to support reconnection, the function Client._connect should be callable more than once.
1821 * While reconnecting
1822 * a new instance of underlying transport (TCP Socket, WebSocket or SockJS) will be needed. So, this function
1823 * alternatively allows passing a function that should return a new instance of the underlying socket.
1824 *
1825 * ```javascript
1826 * var client = Stomp.over(function(){
1827 * return new WebSocket('ws://localhost:15674/ws')
1828 * });
1829 * ```
1830 *
1831 * **Deprecated**
1832 *
1833 * It will be removed in next major version. Please switch to {@link Client}
1834 * using [Client#webSocketFactory]{@link Client#webSocketFactory}.
1835 */
1836 static over(ws) {
1837 let wsFn;
1838 if (typeof ws === 'function') {
1839 wsFn = ws;
1840 }
1841 else {
1842 console.warn('Stomp.over did not receive a factory, auto reconnect will not work. ' +
1843 'Please see https://stomp-js.github.io/api-docs/latest/classes/Stomp.html#over');
1844 wsFn = () => ws;
1845 }
1846 return new CompatClient(wsFn);
1847 }
1848 }
1849 /**
1850 * In case you need to use a non standard class for WebSocket.
1851 *
1852 * For example when using within NodeJS environment:
1853 *
1854 * ```javascript
1855 * StompJs = require('../../esm5/');
1856 * Stomp = StompJs.Stomp;
1857 * Stomp.WebSocketClass = require('websocket').w3cwebsocket;
1858 * ```
1859 *
1860 * **Deprecated**
1861 *
1862 *
1863 * It will be removed in next major version. Please switch to {@link Client}
1864 * using [Client#webSocketFactory]{@link Client#webSocketFactory}.
1865 */
1866 // tslint:disable-next-line:variable-name
1867 Stomp.WebSocketClass = null;
1868
1869 exports.Client = Client;
1870 exports.CompatClient = CompatClient;
1871 exports.FrameImpl = FrameImpl;
1872 exports.Parser = Parser;
1873 exports.Stomp = Stomp;
1874 exports.StompConfig = StompConfig;
1875 exports.StompHeaders = StompHeaders;
1876 exports.Versions = Versions;
1877
1878}));
1879//# sourceMappingURL=stomp.umd.js.map