1 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
2 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
3 | return new (P || (P = Promise))(function (resolve, reject) {
|
4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
6 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
7 | step((generator = generator.apply(thisArg, _arguments || [])).next());
|
8 | });
|
9 | };
|
10 | import { StompHandler } from './stomp-handler';
|
11 | import { ActivationState, StompSocketState, } from './types';
|
12 | import { Versions } from './versions';
|
13 | /**
|
14 | * STOMP Client Class.
|
15 | *
|
16 | * Part of `@stomp/stompjs`.
|
17 | */
|
18 | export class Client {
|
19 | /**
|
20 | * Create an instance.
|
21 | */
|
22 | constructor(conf = {}) {
|
23 | /**
|
24 | * STOMP versions to attempt during STOMP handshake. By default versions `1.0`, `1.1`, and `1.2` are attempted.
|
25 | *
|
26 | * Example:
|
27 | * ```javascript
|
28 | * // Try only versions 1.0 and 1.1
|
29 | * client.stompVersions = new Versions(['1.0', '1.1'])
|
30 | * ```
|
31 | */
|
32 | this.stompVersions = Versions.default;
|
33 | /**
|
34 | * Will retry if Stomp connection is not established in specified milliseconds.
|
35 | * Default 0, which implies wait for ever.
|
36 | */
|
37 | this.connectionTimeout = 0;
|
38 | /**
|
39 | * automatically reconnect with delay in milliseconds, set to 0 to disable.
|
40 | */
|
41 | this.reconnectDelay = 5000;
|
42 | /**
|
43 | * Incoming heartbeat interval in milliseconds. Set to 0 to disable.
|
44 | */
|
45 | this.heartbeatIncoming = 10000;
|
46 | /**
|
47 | * Outgoing heartbeat interval in milliseconds. Set to 0 to disable.
|
48 | */
|
49 | this.heartbeatOutgoing = 10000;
|
50 | /**
|
51 | * This switches on a non standard behavior while sending WebSocket packets.
|
52 | * It splits larger (text) packets into chunks of [maxWebSocketChunkSize]{@link Client#maxWebSocketChunkSize}.
|
53 | * Only Java Spring brokers seems to use this mode.
|
54 | *
|
55 | * WebSockets, by itself, split large (text) packets,
|
56 | * so it is not needed with a truly compliant STOMP/WebSocket broker.
|
57 | * Actually setting it for such broker will cause large messages to fail.
|
58 | *
|
59 | * `false` by default.
|
60 | *
|
61 | * Binary frames are never split.
|
62 | */
|
63 | this.splitLargeFrames = false;
|
64 | /**
|
65 | * See [splitLargeFrames]{@link Client#splitLargeFrames}.
|
66 | * This has no effect if [splitLargeFrames]{@link Client#splitLargeFrames} is `false`.
|
67 | */
|
68 | this.maxWebSocketChunkSize = 8 * 1024;
|
69 | /**
|
70 | * Usually the
|
71 | * [type of WebSocket frame]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send#Parameters}
|
72 | * is automatically decided by type of the payload.
|
73 | * Default is `false`, which should work with all compliant brokers.
|
74 | *
|
75 | * Set this flag to force binary frames.
|
76 | */
|
77 | this.forceBinaryWSFrames = false;
|
78 | /**
|
79 | * A bug in ReactNative chops a string on occurrence of a NULL.
|
80 | * See issue [https://github.com/stomp-js/stompjs/issues/89]{@link https://github.com/stomp-js/stompjs/issues/89}.
|
81 | * This makes incoming WebSocket messages invalid STOMP packets.
|
82 | * Setting this flag attempts to reverse the damage by appending a NULL.
|
83 | * If the broker splits a large message into multiple WebSocket messages,
|
84 | * this flag will cause data loss and abnormal termination of connection.
|
85 | *
|
86 | * This is not an ideal solution, but a stop gap until the underlying issue is fixed at ReactNative library.
|
87 | */
|
88 | this.appendMissingNULLonIncoming = false;
|
89 | /**
|
90 | * Activation state.
|
91 | *
|
92 | * It will usually be ACTIVE or INACTIVE.
|
93 | * When deactivating it may go from ACTIVE to INACTIVE without entering DEACTIVATING.
|
94 | */
|
95 | this.state = ActivationState.INACTIVE;
|
96 | // Dummy callbacks
|
97 | const noOp = () => { };
|
98 | this.debug = noOp;
|
99 | this.beforeConnect = noOp;
|
100 | this.onConnect = noOp;
|
101 | this.onDisconnect = noOp;
|
102 | this.onUnhandledMessage = noOp;
|
103 | this.onUnhandledReceipt = noOp;
|
104 | this.onUnhandledFrame = noOp;
|
105 | this.onStompError = noOp;
|
106 | this.onWebSocketClose = noOp;
|
107 | this.onWebSocketError = noOp;
|
108 | this.logRawCommunication = false;
|
109 | this.onChangeState = noOp;
|
110 | // These parameters would typically get proper values before connect is called
|
111 | this.connectHeaders = {};
|
112 | this._disconnectHeaders = {};
|
113 | // Apply configuration
|
114 | this.configure(conf);
|
115 | }
|
116 | /**
|
117 | * Underlying WebSocket instance, READONLY.
|
118 | */
|
119 | get webSocket() {
|
120 | return this._stompHandler ? this._stompHandler._webSocket : undefined;
|
121 | }
|
122 | /**
|
123 | * Disconnection headers.
|
124 | */
|
125 | get disconnectHeaders() {
|
126 | return this._disconnectHeaders;
|
127 | }
|
128 | set disconnectHeaders(value) {
|
129 | this._disconnectHeaders = value;
|
130 | if (this._stompHandler) {
|
131 | this._stompHandler.disconnectHeaders = this._disconnectHeaders;
|
132 | }
|
133 | }
|
134 | /**
|
135 | * `true` if there is a active connection with STOMP Broker
|
136 | */
|
137 | get connected() {
|
138 | return !!this._stompHandler && this._stompHandler.connected;
|
139 | }
|
140 | /**
|
141 | * version of STOMP protocol negotiated with the server, READONLY
|
142 | */
|
143 | get connectedVersion() {
|
144 | return this._stompHandler ? this._stompHandler.connectedVersion : undefined;
|
145 | }
|
146 | /**
|
147 | * if the client is active (connected or going to reconnect)
|
148 | */
|
149 | get active() {
|
150 | return this.state === ActivationState.ACTIVE;
|
151 | }
|
152 | _changeState(state) {
|
153 | this.state = state;
|
154 | this.onChangeState(state);
|
155 | }
|
156 | /**
|
157 | * Update configuration.
|
158 | */
|
159 | configure(conf) {
|
160 | // bulk assign all properties to this
|
161 | Object.assign(this, conf);
|
162 | }
|
163 | /**
|
164 | * Initiate the connection with the broker.
|
165 | * If the connection breaks, as per [Client#reconnectDelay]{@link Client#reconnectDelay},
|
166 | * it will keep trying to reconnect.
|
167 | *
|
168 | * Call [Client#deactivate]{@link Client#deactivate} to disconnect and stop reconnection attempts.
|
169 | */
|
170 | activate() {
|
171 | if (this.state === ActivationState.DEACTIVATING) {
|
172 | this.debug('Still DEACTIVATING, please await call to deactivate before trying to re-activate');
|
173 | throw new Error('Still DEACTIVATING, can not activate now');
|
174 | }
|
175 | if (this.active) {
|
176 | this.debug('Already ACTIVE, ignoring request to activate');
|
177 | return;
|
178 | }
|
179 | this._changeState(ActivationState.ACTIVE);
|
180 | this._connect();
|
181 | }
|
182 | _connect() {
|
183 | return __awaiter(this, void 0, void 0, function* () {
|
184 | if (this.connected) {
|
185 | this.debug('STOMP: already connected, nothing to do');
|
186 | return;
|
187 | }
|
188 | yield this.beforeConnect();
|
189 | if (!this.active) {
|
190 | this.debug('Client has been marked inactive, will not attempt to connect');
|
191 | return;
|
192 | }
|
193 | // setup connection watcher
|
194 | if (this.connectionTimeout > 0) {
|
195 | // clear first
|
196 | if (this._connectionWatcher) {
|
197 | clearTimeout(this._connectionWatcher);
|
198 | }
|
199 | this._connectionWatcher = setTimeout(() => {
|
200 | if (this.connected) {
|
201 | return;
|
202 | }
|
203 | // Connection not established, close the underlying socket
|
204 | // a reconnection will be attempted
|
205 | this.debug(`Connection not established in ${this.connectionTimeout}ms, closing socket`);
|
206 | this.forceDisconnect();
|
207 | }, this.connectionTimeout);
|
208 | }
|
209 | this.debug('Opening Web Socket...');
|
210 | // Get the actual WebSocket (or a similar object)
|
211 | const webSocket = this._createWebSocket();
|
212 | this._stompHandler = new StompHandler(this, webSocket, {
|
213 | debug: this.debug,
|
214 | stompVersions: this.stompVersions,
|
215 | connectHeaders: this.connectHeaders,
|
216 | disconnectHeaders: this._disconnectHeaders,
|
217 | heartbeatIncoming: this.heartbeatIncoming,
|
218 | heartbeatOutgoing: this.heartbeatOutgoing,
|
219 | splitLargeFrames: this.splitLargeFrames,
|
220 | maxWebSocketChunkSize: this.maxWebSocketChunkSize,
|
221 | forceBinaryWSFrames: this.forceBinaryWSFrames,
|
222 | logRawCommunication: this.logRawCommunication,
|
223 | appendMissingNULLonIncoming: this.appendMissingNULLonIncoming,
|
224 | discardWebsocketOnCommFailure: this.discardWebsocketOnCommFailure,
|
225 | onConnect: frame => {
|
226 | // Successfully connected, stop the connection watcher
|
227 | if (this._connectionWatcher) {
|
228 | clearTimeout(this._connectionWatcher);
|
229 | this._connectionWatcher = undefined;
|
230 | }
|
231 | if (!this.active) {
|
232 | this.debug('STOMP got connected while deactivate was issued, will disconnect now');
|
233 | this._disposeStompHandler();
|
234 | return;
|
235 | }
|
236 | this.onConnect(frame);
|
237 | },
|
238 | onDisconnect: frame => {
|
239 | this.onDisconnect(frame);
|
240 | },
|
241 | onStompError: frame => {
|
242 | this.onStompError(frame);
|
243 | },
|
244 | onWebSocketClose: evt => {
|
245 | this._stompHandler = undefined; // a new one will be created in case of a reconnect
|
246 | if (this.state === ActivationState.DEACTIVATING) {
|
247 | // Mark deactivation complete
|
248 | this._resolveSocketClose();
|
249 | this._resolveSocketClose = undefined;
|
250 | this._changeState(ActivationState.INACTIVE);
|
251 | }
|
252 | this.onWebSocketClose(evt);
|
253 | // The callback is called before attempting to reconnect, this would allow the client
|
254 | // to be `deactivated` in the callback.
|
255 | if (this.active) {
|
256 | this._schedule_reconnect();
|
257 | }
|
258 | },
|
259 | onWebSocketError: evt => {
|
260 | this.onWebSocketError(evt);
|
261 | },
|
262 | onUnhandledMessage: message => {
|
263 | this.onUnhandledMessage(message);
|
264 | },
|
265 | onUnhandledReceipt: frame => {
|
266 | this.onUnhandledReceipt(frame);
|
267 | },
|
268 | onUnhandledFrame: frame => {
|
269 | this.onUnhandledFrame(frame);
|
270 | },
|
271 | });
|
272 | this._stompHandler.start();
|
273 | });
|
274 | }
|
275 | _createWebSocket() {
|
276 | let webSocket;
|
277 | if (this.webSocketFactory) {
|
278 | webSocket = this.webSocketFactory();
|
279 | }
|
280 | else {
|
281 | webSocket = new WebSocket(this.brokerURL, this.stompVersions.protocolVersions());
|
282 | }
|
283 | webSocket.binaryType = 'arraybuffer';
|
284 | return webSocket;
|
285 | }
|
286 | _schedule_reconnect() {
|
287 | if (this.reconnectDelay > 0) {
|
288 | this.debug(`STOMP: scheduling reconnection in ${this.reconnectDelay}ms`);
|
289 | this._reconnector = setTimeout(() => {
|
290 | this._connect();
|
291 | }, this.reconnectDelay);
|
292 | }
|
293 | }
|
294 | /**
|
295 | * Disconnect if connected and stop auto reconnect loop.
|
296 | * Appropriate callbacks will be invoked if underlying STOMP connection was connected.
|
297 | *
|
298 | * This call is async, it will resolve immediately if there is no underlying active websocket,
|
299 | * otherwise, it will resolve after underlying websocket is properly disposed.
|
300 | *
|
301 | * To reactivate you can call [Client#activate]{@link Client#activate}.
|
302 | */
|
303 | deactivate() {
|
304 | return __awaiter(this, void 0, void 0, function* () {
|
305 | let retPromise;
|
306 | if (this.state !== ActivationState.ACTIVE) {
|
307 | this.debug(`Already ${ActivationState[this.state]}, ignoring call to deactivate`);
|
308 | return Promise.resolve();
|
309 | }
|
310 | this._changeState(ActivationState.DEACTIVATING);
|
311 | // Clear if a reconnection was scheduled
|
312 | if (this._reconnector) {
|
313 | clearTimeout(this._reconnector);
|
314 | }
|
315 | if (this._stompHandler &&
|
316 | this.webSocket.readyState !== StompSocketState.CLOSED) {
|
317 | // we need to wait for underlying websocket to close
|
318 | retPromise = new Promise((resolve, reject) => {
|
319 | this._resolveSocketClose = resolve;
|
320 | });
|
321 | }
|
322 | else {
|
323 | // indicate that auto reconnect loop should terminate
|
324 | this._changeState(ActivationState.INACTIVE);
|
325 | return Promise.resolve();
|
326 | }
|
327 | this._disposeStompHandler();
|
328 | return retPromise;
|
329 | });
|
330 | }
|
331 | /**
|
332 | * Force disconnect if there is an active connection by directly closing the underlying WebSocket.
|
333 | * This is different than a normal disconnect where a DISCONNECT sequence is carried out with the broker.
|
334 | * After forcing disconnect, automatic reconnect will be attempted.
|
335 | * To stop further reconnects call [Client#deactivate]{@link Client#deactivate} as well.
|
336 | */
|
337 | forceDisconnect() {
|
338 | if (this._stompHandler) {
|
339 | this._stompHandler.forceDisconnect();
|
340 | }
|
341 | }
|
342 | _disposeStompHandler() {
|
343 | // Dispose STOMP Handler
|
344 | if (this._stompHandler) {
|
345 | this._stompHandler.dispose();
|
346 | this._stompHandler = null;
|
347 | }
|
348 | }
|
349 | /**
|
350 | * Send a message to a named destination. Refer to your STOMP broker documentation for types
|
351 | * and naming of destinations.
|
352 | *
|
353 | * STOMP protocol specifies and suggests some headers and also allows broker specific headers.
|
354 | *
|
355 | * `body` must be String.
|
356 | * You will need to covert the payload to string in case it is not string (e.g. JSON).
|
357 | *
|
358 | * To send a binary message body use binaryBody parameter. It should be a
|
359 | * [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array).
|
360 | * Sometimes brokers may not support binary frames out of the box.
|
361 | * Please check your broker documentation.
|
362 | *
|
363 | * `content-length` header is automatically added to the STOMP Frame sent to the broker.
|
364 | * Set `skipContentLengthHeader` to indicate that `content-length` header should not be added.
|
365 | * For binary messages `content-length` header is always added.
|
366 | *
|
367 | * Caution: The broker will, most likely, report an error and disconnect if message body has NULL octet(s)
|
368 | * and `content-length` header is missing.
|
369 | *
|
370 | * ```javascript
|
371 | * client.publish({destination: "/queue/test", headers: {priority: 9}, body: "Hello, STOMP"});
|
372 | *
|
373 | * // Only destination is mandatory parameter
|
374 | * client.publish({destination: "/queue/test", body: "Hello, STOMP"});
|
375 | *
|
376 | * // Skip content-length header in the frame to the broker
|
377 | * client.publish({"/queue/test", body: "Hello, STOMP", skipContentLengthHeader: true});
|
378 | *
|
379 | * var binaryData = generateBinaryData(); // This need to be of type Uint8Array
|
380 | * // setting content-type header is not mandatory, however a good practice
|
381 | * client.publish({destination: '/topic/special', binaryBody: binaryData,
|
382 | * headers: {'content-type': 'application/octet-stream'}});
|
383 | * ```
|
384 | */
|
385 | publish(params) {
|
386 | this._stompHandler.publish(params);
|
387 | }
|
388 | /**
|
389 | * STOMP brokers may carry out operation asynchronously and allow requesting for acknowledgement.
|
390 | * To request an acknowledgement, a `receipt` header needs to be sent with the actual request.
|
391 | * The value (say receipt-id) for this header needs to be unique for each use. Typically a sequence, a UUID, a
|
392 | * random number or a combination may be used.
|
393 | *
|
394 | * A complaint broker will send a RECEIPT frame when an operation has actually been completed.
|
395 | * The operation needs to be matched based in the value of the receipt-id.
|
396 | *
|
397 | * This method allow watching for a receipt and invoke the callback
|
398 | * when corresponding receipt has been received.
|
399 | *
|
400 | * The actual {@link FrameImpl} will be passed as parameter to the callback.
|
401 | *
|
402 | * Example:
|
403 | * ```javascript
|
404 | * // Subscribing with acknowledgement
|
405 | * let receiptId = randomText();
|
406 | *
|
407 | * client.watchForReceipt(receiptId, function() {
|
408 | * // Will be called after server acknowledges
|
409 | * });
|
410 | *
|
411 | * client.subscribe(TEST.destination, onMessage, {receipt: receiptId});
|
412 | *
|
413 | *
|
414 | * // Publishing with acknowledgement
|
415 | * receiptId = randomText();
|
416 | *
|
417 | * client.watchForReceipt(receiptId, function() {
|
418 | * // Will be called after server acknowledges
|
419 | * });
|
420 | * client.publish({destination: TEST.destination, headers: {receipt: receiptId}, body: msg});
|
421 | * ```
|
422 | */
|
423 | watchForReceipt(receiptId, callback) {
|
424 | this._stompHandler.watchForReceipt(receiptId, callback);
|
425 | }
|
426 | /**
|
427 | * Subscribe to a STOMP Broker location. The callback will be invoked for each received message with
|
428 | * the {@link IMessage} as argument.
|
429 | *
|
430 | * Note: The library will generate an unique ID if there is none provided in the headers.
|
431 | * To use your own ID, pass it using the headers argument.
|
432 | *
|
433 | * ```javascript
|
434 | * callback = function(message) {
|
435 | * // called when the client receives a STOMP message from the server
|
436 | * if (message.body) {
|
437 | * alert("got message with body " + message.body)
|
438 | * } else {
|
439 | * alert("got empty message");
|
440 | * }
|
441 | * });
|
442 | *
|
443 | * var subscription = client.subscribe("/queue/test", callback);
|
444 | *
|
445 | * // Explicit subscription id
|
446 | * var mySubId = 'my-subscription-id-001';
|
447 | * var subscription = client.subscribe(destination, callback, { id: mySubId });
|
448 | * ```
|
449 | */
|
450 | subscribe(destination, callback, headers = {}) {
|
451 | return this._stompHandler.subscribe(destination, callback, headers);
|
452 | }
|
453 | /**
|
454 | * It is preferable to unsubscribe from a subscription by calling
|
455 | * `unsubscribe()` directly on {@link StompSubscription} returned by `client.subscribe()`:
|
456 | *
|
457 | * ```javascript
|
458 | * var subscription = client.subscribe(destination, onmessage);
|
459 | * // ...
|
460 | * subscription.unsubscribe();
|
461 | * ```
|
462 | *
|
463 | * See: http://stomp.github.com/stomp-specification-1.2.html#UNSUBSCRIBE UNSUBSCRIBE Frame
|
464 | */
|
465 | unsubscribe(id, headers = {}) {
|
466 | this._stompHandler.unsubscribe(id, headers);
|
467 | }
|
468 | /**
|
469 | * Start a transaction, the returned {@link ITransaction} has methods - [commit]{@link ITransaction#commit}
|
470 | * and [abort]{@link ITransaction#abort}.
|
471 | *
|
472 | * `transactionId` is optional, if not passed the library will generate it internally.
|
473 | */
|
474 | begin(transactionId) {
|
475 | return this._stompHandler.begin(transactionId);
|
476 | }
|
477 | /**
|
478 | * Commit a transaction.
|
479 | *
|
480 | * It is preferable to commit a transaction by calling [commit]{@link ITransaction#commit} directly on
|
481 | * {@link ITransaction} returned by [client.begin]{@link Client#begin}.
|
482 | *
|
483 | * ```javascript
|
484 | * var tx = client.begin(txId);
|
485 | * //...
|
486 | * tx.commit();
|
487 | * ```
|
488 | */
|
489 | commit(transactionId) {
|
490 | this._stompHandler.commit(transactionId);
|
491 | }
|
492 | /**
|
493 | * Abort a transaction.
|
494 | * It is preferable to abort a transaction by calling [abort]{@link ITransaction#abort} directly on
|
495 | * {@link ITransaction} returned by [client.begin]{@link Client#begin}.
|
496 | *
|
497 | * ```javascript
|
498 | * var tx = client.begin(txId);
|
499 | * //...
|
500 | * tx.abort();
|
501 | * ```
|
502 | */
|
503 | abort(transactionId) {
|
504 | this._stompHandler.abort(transactionId);
|
505 | }
|
506 | /**
|
507 | * ACK a message. It is preferable to acknowledge a message by calling [ack]{@link IMessage#ack} directly
|
508 | * on the {@link IMessage} handled by a subscription callback:
|
509 | *
|
510 | * ```javascript
|
511 | * var callback = function (message) {
|
512 | * // process the message
|
513 | * // acknowledge it
|
514 | * message.ack();
|
515 | * };
|
516 | * client.subscribe(destination, callback, {'ack': 'client'});
|
517 | * ```
|
518 | */
|
519 | ack(messageId, subscriptionId, headers = {}) {
|
520 | this._stompHandler.ack(messageId, subscriptionId, headers);
|
521 | }
|
522 | /**
|
523 | * NACK a message. It is preferable to acknowledge a message by calling [nack]{@link IMessage#nack} directly
|
524 | * on the {@link IMessage} handled by a subscription callback:
|
525 | *
|
526 | * ```javascript
|
527 | * var callback = function (message) {
|
528 | * // process the message
|
529 | * // an error occurs, nack it
|
530 | * message.nack();
|
531 | * };
|
532 | * client.subscribe(destination, callback, {'ack': 'client'});
|
533 | * ```
|
534 | */
|
535 | nack(messageId, subscriptionId, headers = {}) {
|
536 | this._stompHandler.nack(messageId, subscriptionId, headers);
|
537 | }
|
538 | }
|
539 | //# sourceMappingURL=client.js.map |
\ | No newline at end of file |