UNPKG

22.2 kBJavaScriptView Raw
1var __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};
10import { StompHandler } from './stomp-handler';
11import { ActivationState, StompSocketState, } from './types';
12import { Versions } from './versions';
13/**
14 * STOMP Client Class.
15 *
16 * Part of `@stomp/stompjs`.
17 */
18export 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