File

consolidated/stompjs/src/client.ts

Description

STOMP Client Class.

Part of @stomp/stompjs.

Index

Properties
Methods
Accessors

Constructor

constructor(conf: StompConfig)

Create an instance.

Parameters :
Name Type Optional
conf StompConfig No

Properties

Public appendMissingNULLonIncoming
Type : boolean
Default value : false

A bug in ReactNative chops a string on occurrence of a NULL. See issue [https://github.com/stomp-js/stompjs/issues/89]{@link https://github.com/stomp-js/stompjs/issues/89}. This makes incoming WebSocket messages invalid STOMP packets. Setting this flag attempts to reverse the damage by appending a NULL. If the broker splits a large message into multiple WebSocket messages, this flag will cause data loss and abnormal termination of connection.

This is not an ideal solution, but a stop gap until the underlying issue is fixed at ReactNative library.

Public beforeConnect
Type : function

Callback, invoked on before a connection to the STOMP broker.

You can change options on the client, which will impact the immediate connecting. It is valid to call Client#decativate in this callback.

As of version 5.1, this callback can be async (i.e., it can return a Promise). In that case, connect will be called only after the Promise is resolved. This can be used to reliably fetch credentials, access token etc. from some other service in an asynchronous way.

Public brokerURL
Type : string | undefined

The URL for the STOMP broker to connect to. Typically like "ws://broker.329broker.com:15674/ws" or "wss://broker.329broker.com:15674/ws".

Only one of this or Client#webSocketFactory need to be set. If both are set, Client#webSocketFactory will be used.

If your environment does not support WebSockets natively, please refer to Polyfills.

Public connectHeaders
Type : StompHeaders

Connection headers, important keys - login, passcode, host. Though STOMP 1.2 standard marks these keys to be present, check your broker documentation for details specific to your broker.

Public connectionTimeout
Type : number
Default value : 0

Will retry if Stomp connection is not established in specified milliseconds. Default 0, which switches off automatic reconnection.

Public debug
Type : debugFnType

By default, debug messages are discarded. To log to console following can be used:

       client.debug = function(str) {
         console.log(str);
       };

Currently this method does not support levels of log. Be aware that the output can be quite verbose and may contain sensitive information (like passwords, tokens etc.).

Public discardWebsocketOnCommFailure
Type : boolean
Default value : false

Browsers do not immediately close WebSockets when .close is issued. This may cause reconnection to take a significantly long time in case of some types of failures. In case of incoming heartbeat failure, this experimental flag instructs the library to discard the socket immediately (even before it is actually closed).

Public forceBinaryWSFrames
Type : boolean
Default value : false

Usually the type of WebSocket frame is automatically decided by type of the payload. Default is false, which should work with all compliant brokers.

Set this flag to force binary frames.

Public heartbeatIncoming
Type : number
Default value : 10000

Incoming heartbeat interval in milliseconds. Set to 0 to disable.

Public heartbeatOutgoing
Type : number
Default value : 10000

Outgoing heartbeat interval in milliseconds. Set to 0 to disable.

Public logRawCommunication
Type : boolean

Set it to log the actual raw communication with the broker. When unset, it logs headers of the parsed frames.

Changes effect from the next broker reconnect.

Caution: this assumes that frames only have valid UTF8 strings.

Public maxWebSocketChunkSize
Type : number
Default value : 8 * 1024

See splitLargeFrames. This has no effect if splitLargeFrames is false.

Public onChangeState
Type : function

It will be called on state change.

When deactivating, it may go from ACTIVE to INACTIVE without entering DEACTIVATING.

Public onConnect
Type : frameCallbackType

Callback, invoked on every successful connection to the STOMP broker.

The actual IFrame will be passed as parameter to the callback. Sometimes clients will like to use headers from this frame.

Public onDisconnect
Type : frameCallbackType

Callback, invoked on every successful disconnection from the STOMP broker. It will not be invoked if the STOMP broker disconnected due to an error.

The actual Receipt IFrame acknowledging the DISCONNECT will be passed as parameter to the callback.

The way STOMP protocol is designed, the connection may close/terminate without the client receiving the Receipt IFrame acknowledging the DISCONNECT. You might find Client#onWebSocketClose more appropriate to watch STOMP broker disconnects.

Public onStompError
Type : frameCallbackType

Callback, invoked on an ERROR frame received from the STOMP Broker. A compliant STOMP Broker will close the connection after this type of frame. Please check broker specific documentation for exact behavior.

The actual IFrame will be passed as parameter to the callback.

Public onUnhandledFrame
Type : frameCallbackType

Will be invoked if IFrame of an unknown type is received from the STOMP broker.

The actual IFrame will be passed as parameter to the callback.

Public onUnhandledMessage
Type : messageCallbackType

This function will be called for any unhandled messages. It is useful for receiving messages sent to RabbitMQ temporary queues.

It can also get invoked with stray messages while the server is processing a request to Client#unsubscribe from an endpoint.

The actual IMessage will be passed as parameter to the callback.

Public onUnhandledReceipt
Type : frameCallbackType

STOMP brokers can be requested to notify when an operation is actually completed. Prefer using Client#watchForReceipt. See Client#watchForReceipt for examples.

The actual IFrame will be passed as parameter to the callback.

Public onWebSocketClose
Type : closeEventCallbackType

Callback, invoked when underlying WebSocket is closed.

Actual CloseEvent is passed as parameter to the callback.

Public onWebSocketError
Type : wsErrorCallbackType

Callback, invoked when underlying WebSocket raises an error.

Actual Event is passed as parameter to the callback.

Public reconnectDelay
Type : number
Default value : 5000

automatically reconnect with delay in milliseconds, set to 0 to disable.

Public splitLargeFrames
Type : boolean
Default value : false

This switches on a non-standard behavior while sending WebSocket packets. It splits larger (text) packets into chunks of maxWebSocketChunkSize. Only Java Spring brokers seem to support this mode.

WebSockets, by itself, split large (text) packets, so it is not needed with a truly compliant STOMP/WebSocket broker. Setting it for such a broker will cause large messages to fail.

false by default.

Binary frames are never split.

Public state
Type : ActivationState
Default value : ActivationState.INACTIVE

Activation state.

It will usually be ACTIVE or INACTIVE. When deactivating, it may go from ACTIVE to INACTIVE without entering DEACTIVATING.

Public stompVersions
Default value : Versions.default

STOMP versions to attempt during STOMP handshake. By default, versions 1.2, 1.1, and 1.0 are attempted.

Example:

       // Try only versions 1.1 and 1.0
       client.stompVersions = new Versions(['1.1', '1.0'])
Public webSocketFactory
Type : | undefined

This function should return a WebSocket or a similar (e.g. SockJS) object. If your environment does not support WebSockets natively, please refer to Polyfills. If your STOMP Broker supports WebSockets, prefer setting Client#brokerURL.

If both this and Client#brokerURL are set, this will be used.

Example:

       // use a WebSocket
       client.webSocketFactory= function () {
         return new WebSocket("wss://broker.329broker.com:15674/ws");
       };

       // Typical usage with SockJS
       client.webSocketFactory= function () {
         return new SockJS("http://broker.329broker.com/stomp");
       };

Methods

Public abort
abort(transactionId: string)

Abort a transaction. It is preferable to abort a transaction by calling abort directly on ITransaction returned by client.begin.

       var tx = client.begin(txId);
       //...
       tx.abort();
Parameters :
Name Type Optional
transactionId string No
Returns : void
Public ack
ack(messageId: string, subscriptionId: string, headers: StompHeaders)

ACK a message. It is preferable to acknowledge a message by calling ack directly on the IMessage handled by a subscription callback:

       var callback = function (message) {
         // process the message
         // acknowledge it
         message.ack();
       };
       client.subscribe(destination, callback, {'ack': 'client'});
Parameters :
Name Type Optional Default value
messageId string No
subscriptionId string No
headers StompHeaders No {}
Returns : void
Public activate
activate()

Initiate the connection with the broker. If the connection breaks, as per Client#reconnectDelay, it will keep trying to reconnect.

Call Client#deactivate to disconnect and stop reconnection attempts.

Returns : void
Public begin
begin(transactionId?: string)

Start a transaction, the returned ITransaction has methods - commit and abort.

transactionId is optional, if not passed the library will generate it internally.

Parameters :
Name Type Optional
transactionId string Yes
Returns : ITransaction
Public commit
commit(transactionId: string)

Commit a transaction.

It is preferable to commit a transaction by calling commit directly on ITransaction returned by client.begin.

       var tx = client.begin(txId);
       //...
       tx.commit();
Parameters :
Name Type Optional
transactionId string No
Returns : void
Public configure
configure(conf: StompConfig)

Update configuration.

Parameters :
Name Type Optional
conf StompConfig No
Returns : void
Public Async deactivate
deactivate(options: literal type)

Disconnect if connected and stop auto reconnect loop. Appropriate callbacks will be invoked if there is an underlying STOMP connection.

This call is async. It will resolve immediately if there is no underlying active websocket, otherwise, it will resolve after the underlying websocket is properly disposed of.

It is not an error to invoke this method more than once. Each of those would resolve on completion of deactivation.

To reactivate, you can call Client#activate.

Experimental: pass force: true to immediately discard the underlying connection. This mode will skip both the STOMP and the Websocket shutdown sequences. In some cases, browsers take a long time in the Websocket shutdown if the underlying connection had gone stale. Using this mode can speed up. When this mode is used, the actual Websocket may linger for a while and the broker may not realize that the connection is no longer in use.

It is possible to invoke this method initially without the force option and subsequently, say after a wait, with the force option.

Parameters :
Name Type Optional Default value
options literal type No {}
Returns : Promise<void>
Public forceDisconnect
forceDisconnect()

Force disconnect if there is an active connection by directly closing the underlying WebSocket. This is different from a normal disconnect where a DISCONNECT sequence is carried out with the broker. After forcing disconnect, automatic reconnect will be attempted. To stop further reconnects call Client#deactivate as well.

Returns : void
Public nack
nack(messageId: string, subscriptionId: string, headers: StompHeaders)

NACK a message. It is preferable to acknowledge a message by calling nack directly on the IMessage handled by a subscription callback:

       var callback = function (message) {
         // process the message
         // an error occurs, nack it
         message.nack();
       };
       client.subscribe(destination, callback, {'ack': 'client'});
Parameters :
Name Type Optional Default value
messageId string No
subscriptionId string No
headers StompHeaders No {}
Returns : void
Public publish
publish(params: IPublishParams)

Send a message to a named destination. Refer to your STOMP broker documentation for types and naming of destinations.

STOMP protocol specifies and suggests some headers and also allows broker-specific headers.

body must be String. You will need to covert the payload to string in case it is not string (e.g. JSON).

To send a binary message body, use binaryBody parameter. It should be a Uint8Array. Sometimes brokers may not support binary frames out of the box. Please check your broker documentation.

content-length header is automatically added to the STOMP Frame sent to the broker. Set skipContentLengthHeader to indicate that content-length header should not be added. For binary messages, content-length header is always added.

Caution: The broker will, most likely, report an error and disconnect if the message body has NULL octet(s) and content-length header is missing.

       client.publish({destination: "/queue/test", headers: {priority: 9}, body: "Hello, STOMP"});

       // Only destination is mandatory parameter
       client.publish({destination: "/queue/test", body: "Hello, STOMP"});

       // Skip content-length header in the frame to the broker
       client.publish({"/queue/test", body: "Hello, STOMP", skipContentLengthHeader: true});

       var binaryData = generateBinaryData(); // This need to be of type Uint8Array
       // setting content-type header is not mandatory, however a good practice
       client.publish({destination: '/topic/special', binaryBody: binaryData,
                        headers: {'content-type': 'application/octet-stream'}});
Parameters :
Name Type Optional
params IPublishParams No
Returns : void
Public subscribe
subscribe(destination: string, callback: messageCallbackType, headers: StompHeaders)

Subscribe to a STOMP Broker location. The callback will be invoked for each received message with the IMessage as argument.

Note: The library will generate a unique ID if there is none provided in the headers. To use your own ID, pass it using the headers argument.

       callback = function(message) {
       // called when the client receives a STOMP message from the server
         if (message.body) {
           alert("got message with body " + message.body)
         } else {
           alert("got empty message");
         }
       });

       var subscription = client.subscribe("/queue/test", callback);

       // Explicit subscription id
       var mySubId = 'my-subscription-id-001';
       var subscription = client.subscribe(destination, callback, { id: mySubId });
Parameters :
Name Type Optional Default value
destination string No
callback messageCallbackType No
headers StompHeaders No {}
Returns : StompSubscription
Public unsubscribe
unsubscribe(id: string, headers: StompHeaders)

It is preferable to unsubscribe from a subscription by calling unsubscribe() directly on StompSubscription returned by client.subscribe():

       var subscription = client.subscribe(destination, onmessage);
       // ...
       subscription.unsubscribe();

See: https://stomp.github.com/stomp-specification-1.2.html#UNSUBSCRIBE UNSUBSCRIBE Frame

Parameters :
Name Type Optional Default value
id string No
headers StompHeaders No {}
Returns : void
Public watchForReceipt
watchForReceipt(receiptId: string, callback: frameCallbackType)

STOMP brokers may carry out operation asynchronously and allow requesting for acknowledgement. To request an acknowledgement, a receipt header needs to be sent with the actual request. The value (say receipt-id) for this header needs to be unique for each use. Typically, a sequence, a UUID, a random number or a combination may be used.

A complaint broker will send a RECEIPT frame when an operation has actually been completed. The operation needs to be matched based on the value of the receipt-id.

This method allows watching for a receipt and invoking the callback when the corresponding receipt has been received.

The actual IFrame will be passed as parameter to the callback.

Example:

       // Subscribing with acknowledgement
       let receiptId = randomText();

       client.watchForReceipt(receiptId, function() {
         // Will be called after server acknowledges
       });

       client.subscribe(TEST.destination, onMessage, {receipt: receiptId});


       // Publishing with acknowledgement
       receiptId = randomText();

       client.watchForReceipt(receiptId, function() {
         // Will be called after server acknowledges
       });
       client.publish({destination: TEST.destination, headers: {receipt: receiptId}, body: msg});
Parameters :
Name Type Optional
receiptId string No
callback frameCallbackType No
Returns : void

Accessors

webSocket
getwebSocket()

Underlying WebSocket instance, READONLY.

disconnectHeaders
getdisconnectHeaders()

Disconnection headers.

Returns : StompHeaders
setdisconnectHeaders(value: StompHeaders)
Parameters :
Name Type Optional
value StompHeaders No
Returns : void
connected
getconnected()

true if there is an active connection to STOMP Broker

Returns : boolean
connectedVersion
getconnectedVersion()

version of STOMP protocol negotiated with the server, READONLY

Returns : string | undefined
active
getactive()

if the client is active (connected or going to reconnect)

Returns : boolean
import { ITransaction } from './i-transaction.js';
import { StompConfig } from './stomp-config.js';
import { StompHandler } from './stomp-handler.js';
import { StompHeaders } from './stomp-headers.js';
import { StompSubscription } from './stomp-subscription.js';
import {
  ActivationState,
  closeEventCallbackType,
  debugFnType,
  frameCallbackType,
  IPublishParams,
  IStompSocket,
  messageCallbackType,
  StompSocketState,
  wsErrorCallbackType,
} from './types.js';
import { Versions } from './versions.js';

/**
 * @internal
 */
declare const WebSocket: {
  prototype: IStompSocket;
  new (url: string, protocols?: string | string[]): IStompSocket;
};

/**
 * STOMP Client Class.
 *
 * Part of `@stomp/stompjs`.
 */
export class Client {
  /**
   * The URL for the STOMP broker to connect to.
   * Typically like `"ws://broker.329broker.com:15674/ws"` or `"wss://broker.329broker.com:15674/ws"`.
   *
   * Only one of this or [Client#webSocketFactory]{@link Client#webSocketFactory} need to be set.
   * If both are set, [Client#webSocketFactory]{@link Client#webSocketFactory} will be used.
   *
   * If your environment does not support WebSockets natively, please refer to
   * [Polyfills]{@link https://stomp-js.github.io/guide/stompjs/rx-stomp/ng2-stompjs/pollyfils-for-stompjs-v5.html}.
   */
  public brokerURL: string | undefined;

  /**
   * STOMP versions to attempt during STOMP handshake. By default, versions `1.2`, `1.1`, and `1.0` are attempted.
   *
   * Example:
   * ```javascript
   *        // Try only versions 1.1 and 1.0
   *        client.stompVersions = new Versions(['1.1', '1.0'])
   * ```
   */
  public stompVersions = Versions.default;

  /**
   * This function should return a WebSocket or a similar (e.g. SockJS) object.
   * If your environment does not support WebSockets natively, please refer to
   * [Polyfills]{@link https://stomp-js.github.io/guide/stompjs/rx-stomp/ng2-stompjs/pollyfils-for-stompjs-v5.html}.
   * If your STOMP Broker supports WebSockets, prefer setting [Client#brokerURL]{@link Client#brokerURL}.
   *
   * If both this and [Client#brokerURL]{@link Client#brokerURL} are set, this will be used.
   *
   * Example:
   * ```javascript
   *        // use a WebSocket
   *        client.webSocketFactory= function () {
   *          return new WebSocket("wss://broker.329broker.com:15674/ws");
   *        };
   *
   *        // Typical usage with SockJS
   *        client.webSocketFactory= function () {
   *          return new SockJS("http://broker.329broker.com/stomp");
   *        };
   * ```
   */
  public webSocketFactory: (() => IStompSocket) | undefined;

  /**
   * Will retry if Stomp connection is not established in specified milliseconds.
   * Default 0, which switches off automatic reconnection.
   */
  public connectionTimeout: number = 0;

  // As per https://stackoverflow.com/questions/45802988/typescript-use-correct-version-of-settimeout-node-vs-window/56239226#56239226
  private _connectionWatcher: ReturnType<typeof setTimeout> | undefined; // Timer

  /**
   *  automatically reconnect with delay in milliseconds, set to 0 to disable.
   */
  public reconnectDelay: number = 5000;

  /**
   * Incoming heartbeat interval in milliseconds. Set to 0 to disable.
   */
  public heartbeatIncoming: number = 10000;

  /**
   * Outgoing heartbeat interval in milliseconds. Set to 0 to disable.
   */
  public heartbeatOutgoing: number = 10000;

  /**
   * This switches on a non-standard behavior while sending WebSocket packets.
   * It splits larger (text) packets into chunks of [maxWebSocketChunkSize]{@link Client#maxWebSocketChunkSize}.
   * Only Java Spring brokers seem to support this mode.
   *
   * WebSockets, by itself, split large (text) packets,
   * so it is not needed with a truly compliant STOMP/WebSocket broker.
   * Setting it for such a broker will cause large messages to fail.
   *
   * `false` by default.
   *
   * Binary frames are never split.
   */
  public splitLargeFrames: boolean = false;

  /**
   * See [splitLargeFrames]{@link Client#splitLargeFrames}.
   * This has no effect if [splitLargeFrames]{@link Client#splitLargeFrames} is `false`.
   */
  public maxWebSocketChunkSize: number = 8 * 1024;

  /**
   * Usually the
   * [type of WebSocket frame]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send#Parameters}
   * is automatically decided by type of the payload.
   * Default is `false`, which should work with all compliant brokers.
   *
   * Set this flag to force binary frames.
   */
  public forceBinaryWSFrames: boolean = false;

  /**
   * A bug in ReactNative chops a string on occurrence of a NULL.
   * See issue [https://github.com/stomp-js/stompjs/issues/89]{@link https://github.com/stomp-js/stompjs/issues/89}.
   * This makes incoming WebSocket messages invalid STOMP packets.
   * Setting this flag attempts to reverse the damage by appending a NULL.
   * If the broker splits a large message into multiple WebSocket messages,
   * this flag will cause data loss and abnormal termination of connection.
   *
   * This is not an ideal solution, but a stop gap until the underlying issue is fixed at ReactNative library.
   */
  public appendMissingNULLonIncoming: boolean = false;

  /**
   * Underlying WebSocket instance, READONLY.
   */
  get webSocket(): IStompSocket | undefined {
    return this._stompHandler?._webSocket;
  }

  /**
   * Connection headers, important keys - `login`, `passcode`, `host`.
   * Though STOMP 1.2 standard marks these keys to be present, check your broker documentation for
   * details specific to your broker.
   */
  public connectHeaders: StompHeaders;

  /**
   * Disconnection headers.
   */
  get disconnectHeaders(): StompHeaders {
    return this._disconnectHeaders;
  }

  set disconnectHeaders(value: StompHeaders) {
    this._disconnectHeaders = value;
    if (this._stompHandler) {
      this._stompHandler.disconnectHeaders = this._disconnectHeaders;
    }
  }
  private _disconnectHeaders: StompHeaders;

  /**
   * This function will be called for any unhandled messages.
   * It is useful for receiving messages sent to RabbitMQ temporary queues.
   *
   * It can also get invoked with stray messages while the server is processing
   * a request to [Client#unsubscribe]{@link Client#unsubscribe}
   * from an endpoint.
   *
   * The actual {@link IMessage} will be passed as parameter to the callback.
   */
  public onUnhandledMessage: messageCallbackType;

  /**
   * STOMP brokers can be requested to notify when an operation is actually completed.
   * Prefer using [Client#watchForReceipt]{@link Client#watchForReceipt}. See
   * [Client#watchForReceipt]{@link Client#watchForReceipt} for examples.
   *
   * The actual {@link IFrame} will be passed as parameter to the callback.
   */
  public onUnhandledReceipt: frameCallbackType;

  /**
   * Will be invoked if {@link IFrame} of an unknown type is received from the STOMP broker.
   *
   * The actual {@link IFrame} will be passed as parameter to the callback.
   */
  public onUnhandledFrame: frameCallbackType;

  /**
   * `true` if there is an active connection to STOMP Broker
   */
  get connected(): boolean {
    return !!this._stompHandler && this._stompHandler.connected;
  }

  /**
   * Callback, invoked on before a connection to the STOMP broker.
   *
   * You can change options on the client, which will impact the immediate connecting.
   * It is valid to call [Client#decativate]{@link Client#deactivate} in this callback.
   *
   * As of version 5.1, this callback can be
   * [async](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)
   * (i.e., it can return a
   * [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)).
   * In that case, connect will be called only after the Promise is resolved.
   * This can be used to reliably fetch credentials, access token etc. from some other service
   * in an asynchronous way.
   */
  public beforeConnect: () => void | Promise<void>;

  /**
   * Callback, invoked on every successful connection to the STOMP broker.
   *
   * The actual {@link IFrame} will be passed as parameter to the callback.
   * Sometimes clients will like to use headers from this frame.
   */
  public onConnect: frameCallbackType;

  /**
   * Callback, invoked on every successful disconnection from the STOMP broker. It will not be invoked if
   * the STOMP broker disconnected due to an error.
   *
   * The actual Receipt {@link IFrame} acknowledging the DISCONNECT will be passed as parameter to the callback.
   *
   * The way STOMP protocol is designed, the connection may close/terminate without the client
   * receiving the Receipt {@link IFrame} acknowledging the DISCONNECT.
   * You might find [Client#onWebSocketClose]{@link Client#onWebSocketClose} more appropriate to watch
   * STOMP broker disconnects.
   */
  public onDisconnect: frameCallbackType;

  /**
   * Callback, invoked on an ERROR frame received from the STOMP Broker.
   * A compliant STOMP Broker will close the connection after this type of frame.
   * Please check broker specific documentation for exact behavior.
   *
   * The actual {@link IFrame} will be passed as parameter to the callback.
   */
  public onStompError: frameCallbackType;

  /**
   * Callback, invoked when underlying WebSocket is closed.
   *
   * Actual [CloseEvent]{@link https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent}
   * is passed as parameter to the callback.
   */
  public onWebSocketClose: closeEventCallbackType;

  /**
   * Callback, invoked when underlying WebSocket raises an error.
   *
   * Actual [Event]{@link https://developer.mozilla.org/en-US/docs/Web/API/Event}
   * is passed as parameter to the callback.
   */
  public onWebSocketError: wsErrorCallbackType;

  /**
   * Set it to log the actual raw communication with the broker.
   * When unset, it logs headers of the parsed frames.
   *
   * Changes effect from the next broker reconnect.
   *
   * **Caution: this assumes that frames only have valid UTF8 strings.**
   */
  public logRawCommunication: boolean;

  /**
   * By default, debug messages are discarded. To log to `console` following can be used:
   *
   * ```javascript
   *        client.debug = function(str) {
   *          console.log(str);
   *        };
   * ```
   *
   * Currently this method does not support levels of log. Be aware that the
   * output can be quite verbose
   * and may contain sensitive information (like passwords, tokens etc.).
   */
  public debug: debugFnType;

  /**
   * Browsers do not immediately close WebSockets when `.close` is issued.
   * This may cause reconnection to take a significantly long time in case
   *  of some types of failures.
   * In case of incoming heartbeat failure, this experimental flag instructs
   * the library to discard the socket immediately
   * (even before it is actually closed).
   */
  public discardWebsocketOnCommFailure: boolean = false;

  /**
   * version of STOMP protocol negotiated with the server, READONLY
   */
  get connectedVersion(): string | undefined {
    return this._stompHandler ? this._stompHandler.connectedVersion : undefined;
  }

  private _stompHandler: StompHandler | undefined;

  /**
   * if the client is active (connected or going to reconnect)
   */
  get active(): boolean {
    return this.state === ActivationState.ACTIVE;
  }

  /**
   * It will be called on state change.
   *
   * When deactivating, it may go from ACTIVE to INACTIVE without entering DEACTIVATING.
   */
  public onChangeState: (state: ActivationState) => void;

  private _changeState(state: ActivationState) {
    this.state = state;
    this.onChangeState(state);
  }

  /**
   * Activation state.
   *
   * It will usually be ACTIVE or INACTIVE.
   * When deactivating, it may go from ACTIVE to INACTIVE without entering DEACTIVATING.
   */
  public state: ActivationState = ActivationState.INACTIVE;

  private _reconnector: any;

  /**
   * Create an instance.
   */
  constructor(conf: StompConfig = {}) {
    // No op callbacks
    const noOp = () => {};
    this.debug = noOp;
    this.beforeConnect = noOp;
    this.onConnect = noOp;
    this.onDisconnect = noOp;
    this.onUnhandledMessage = noOp;
    this.onUnhandledReceipt = noOp;
    this.onUnhandledFrame = noOp;
    this.onStompError = noOp;
    this.onWebSocketClose = noOp;
    this.onWebSocketError = noOp;
    this.logRawCommunication = false;
    this.onChangeState = noOp;

    // These parameters would typically get proper values before connect is called
    this.connectHeaders = {};
    this._disconnectHeaders = {};

    // Apply configuration
    this.configure(conf);
  }

  /**
   * Update configuration.
   */
  public configure(conf: StompConfig): void {
    // bulk assign all properties to this
    (Object as any).assign(this, conf);
  }

  /**
   * Initiate the connection with the broker.
   * If the connection breaks, as per [Client#reconnectDelay]{@link Client#reconnectDelay},
   * it will keep trying to reconnect.
   *
   * Call [Client#deactivate]{@link Client#deactivate} to disconnect and stop reconnection attempts.
   */
  public activate(): void {
    const _activate = () => {
      if (this.active) {
        this.debug('Already ACTIVE, ignoring request to activate');
        return;
      }

      this._changeState(ActivationState.ACTIVE);

      this._connect();
    };

    // if it is deactivating, wait for it to complete before activating.
    if (this.state === ActivationState.DEACTIVATING) {
      this.debug('Waiting for deactivation to finish before activating');
      this.deactivate().then(() => {
        _activate();
      });
    } else {
      _activate();
    }
  }

  private async _connect(): Promise<void> {
    await this.beforeConnect();

    if (this._stompHandler) {
      this.debug('There is already a stompHandler, skipping the call to connect');
      return;
    }

    if (!this.active) {
      this.debug(
        'Client has been marked inactive, will not attempt to connect'
      );
      return;
    }

    // setup connection watcher
    if (this.connectionTimeout > 0) {
      // clear first
      if (this._connectionWatcher) {
        clearTimeout(this._connectionWatcher);
      }
      this._connectionWatcher = setTimeout(() => {
        if (this.connected) {
          return;
        }
        // Connection not established, close the underlying socket
        // a reconnection will be attempted
        this.debug(
          `Connection not established in ${this.connectionTimeout}ms, closing socket`
        );
        this.forceDisconnect();
      }, this.connectionTimeout);
    }

    this.debug('Opening Web Socket...');

    // Get the actual WebSocket (or a similar object)
    const webSocket = this._createWebSocket();

    this._stompHandler = new StompHandler(this, webSocket, {
      debug: this.debug,
      stompVersions: this.stompVersions,
      connectHeaders: this.connectHeaders,
      disconnectHeaders: this._disconnectHeaders,
      heartbeatIncoming: this.heartbeatIncoming,
      heartbeatOutgoing: this.heartbeatOutgoing,
      splitLargeFrames: this.splitLargeFrames,
      maxWebSocketChunkSize: this.maxWebSocketChunkSize,
      forceBinaryWSFrames: this.forceBinaryWSFrames,
      logRawCommunication: this.logRawCommunication,
      appendMissingNULLonIncoming: this.appendMissingNULLonIncoming,
      discardWebsocketOnCommFailure: this.discardWebsocketOnCommFailure,

      onConnect: frame => {
        // Successfully connected, stop the connection watcher
        if (this._connectionWatcher) {
          clearTimeout(this._connectionWatcher);
          this._connectionWatcher = undefined;
        }

        if (!this.active) {
          this.debug(
            'STOMP got connected while deactivate was issued, will disconnect now'
          );
          this._disposeStompHandler();
          return;
        }
        this.onConnect(frame);
      },
      onDisconnect: frame => {
        this.onDisconnect(frame);
      },
      onStompError: frame => {
        this.onStompError(frame);
      },
      onWebSocketClose: evt => {
        this._stompHandler = undefined; // a new one will be created in case of a reconnect

        if (this.state === ActivationState.DEACTIVATING) {
          // Mark deactivation complete
          this._changeState(ActivationState.INACTIVE);
        }

        // The callback is called before attempting to reconnect, this would allow the client
        // to be `deactivated` in the callback.
        this.onWebSocketClose(evt);

        if (this.active) {
          this._schedule_reconnect();
        }
      },
      onWebSocketError: evt => {
        this.onWebSocketError(evt);
      },
      onUnhandledMessage: message => {
        this.onUnhandledMessage(message);
      },
      onUnhandledReceipt: frame => {
        this.onUnhandledReceipt(frame);
      },
      onUnhandledFrame: frame => {
        this.onUnhandledFrame(frame);
      },
    });

    this._stompHandler.start();
  }

  private _createWebSocket(): IStompSocket {
    let webSocket: IStompSocket;

    if (this.webSocketFactory) {
      webSocket = this.webSocketFactory();
    } else if (this.brokerURL) {
      webSocket = new WebSocket(
        this.brokerURL,
        this.stompVersions.protocolVersions()
      );
    } else {
      throw new Error('Either brokerURL or webSocketFactory must be provided');
    }
    webSocket.binaryType = 'arraybuffer';
    return webSocket;
  }

  private _schedule_reconnect(): void {
    if (this.reconnectDelay > 0) {
      this.debug(`STOMP: scheduling reconnection in ${this.reconnectDelay}ms`);

      this._reconnector = setTimeout(() => {
        this._connect();
      }, this.reconnectDelay);
    }
  }

  /**
   * Disconnect if connected and stop auto reconnect loop.
   * Appropriate callbacks will be invoked if there is an underlying STOMP connection.
   *
   * This call is async. It will resolve immediately if there is no underlying active websocket,
   * otherwise, it will resolve after the underlying websocket is properly disposed of.
   *
   * It is not an error to invoke this method more than once.
   * Each of those would resolve on completion of deactivation.
   *
   * To reactivate, you can call [Client#activate]{@link Client#activate}.
   *
   * Experimental: pass `force: true` to immediately discard the underlying connection.
   * This mode will skip both the STOMP and the Websocket shutdown sequences.
   * In some cases, browsers take a long time in the Websocket shutdown
   * if the underlying connection had gone stale.
   * Using this mode can speed up.
   * When this mode is used, the actual Websocket may linger for a while
   * and the broker may not realize that the connection is no longer in use.
   *
   * It is possible to invoke this method initially without the `force` option
   * and subsequently, say after a wait, with the `force` option.
   */
  public async deactivate(options: { force?: boolean } = {}): Promise<void> {
    const force: boolean = options.force || false;
    const needToDispose = this.active;
    let retPromise: Promise<void>;

    if (this.state === ActivationState.INACTIVE) {
      this.debug(`Already INACTIVE, nothing more to do`);
      return Promise.resolve();
    }

    this._changeState(ActivationState.DEACTIVATING);

    // Clear if a reconnection was scheduled
    if (this._reconnector) {
      clearTimeout(this._reconnector);
      this._reconnector = undefined;
    }

    if (
      this._stompHandler &&
      // @ts-ignore - if there is a _stompHandler, there is the webSocket
      this.webSocket.readyState !== StompSocketState.CLOSED
    ) {
      const origOnWebSocketClose = this._stompHandler.onWebSocketClose;
      // we need to wait for the underlying websocket to close
      retPromise = new Promise<void>((resolve, reject) => {
        // @ts-ignore - there is a _stompHandler
        this._stompHandler.onWebSocketClose = evt => {
          origOnWebSocketClose(evt);
          resolve();
        };
      });
    } else {
      // indicate that auto reconnect loop should terminate
      this._changeState(ActivationState.INACTIVE);
      return Promise.resolve();
    }

    if (force) {
      this._stompHandler?.discardWebsocket();
    } else if (needToDispose) {
      this._disposeStompHandler();
    }

    return retPromise;
  }

  /**
   * Force disconnect if there is an active connection by directly closing the underlying WebSocket.
   * This is different from a normal disconnect where a DISCONNECT sequence is carried out with the broker.
   * After forcing disconnect, automatic reconnect will be attempted.
   * To stop further reconnects call [Client#deactivate]{@link Client#deactivate} as well.
   */
  public forceDisconnect() {
    if (this._stompHandler) {
      this._stompHandler.forceDisconnect();
    }
  }

  private _disposeStompHandler() {
    // Dispose STOMP Handler
    if (this._stompHandler) {
      this._stompHandler.dispose();
    }
  }

  /**
   * Send a message to a named destination. Refer to your STOMP broker documentation for types
   * and naming of destinations.
   *
   * STOMP protocol specifies and suggests some headers and also allows broker-specific headers.
   *
   * `body` must be String.
   * You will need to covert the payload to string in case it is not string (e.g. JSON).
   *
   * To send a binary message body, use `binaryBody` parameter. It should be a
   * [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array).
   * Sometimes brokers may not support binary frames out of the box.
   * Please check your broker documentation.
   *
   * `content-length` header is automatically added to the STOMP Frame sent to the broker.
   * Set `skipContentLengthHeader` to indicate that `content-length` header should not be added.
   * For binary messages, `content-length` header is always added.
   *
   * Caution: The broker will, most likely, report an error and disconnect
   * if the message body has NULL octet(s) and `content-length` header is missing.
   *
   * ```javascript
   *        client.publish({destination: "/queue/test", headers: {priority: 9}, body: "Hello, STOMP"});
   *
   *        // Only destination is mandatory parameter
   *        client.publish({destination: "/queue/test", body: "Hello, STOMP"});
   *
   *        // Skip content-length header in the frame to the broker
   *        client.publish({"/queue/test", body: "Hello, STOMP", skipContentLengthHeader: true});
   *
   *        var binaryData = generateBinaryData(); // This need to be of type Uint8Array
   *        // setting content-type header is not mandatory, however a good practice
   *        client.publish({destination: '/topic/special', binaryBody: binaryData,
   *                         headers: {'content-type': 'application/octet-stream'}});
   * ```
   */
  public publish(params: IPublishParams) {
    this._checkConnection();
    // @ts-ignore - we already checked that there is a _stompHandler, and it is connected
    this._stompHandler.publish(params);
  }

  private _checkConnection() {
    if (!this.connected) {
      throw new TypeError('There is no underlying STOMP connection');
    }
  }

  /**
   * STOMP brokers may carry out operation asynchronously and allow requesting for acknowledgement.
   * To request an acknowledgement, a `receipt` header needs to be sent with the actual request.
   * The value (say receipt-id) for this header needs to be unique for each use.
   * Typically, a sequence, a UUID, a random number or a combination may be used.
   *
   * A complaint broker will send a RECEIPT frame when an operation has actually been completed.
   * The operation needs to be matched based on the value of the receipt-id.
   *
   * This method allows watching for a receipt and invoking the callback
   *  when the corresponding receipt has been received.
   *
   * The actual {@link IFrame} will be passed as parameter to the callback.
   *
   * Example:
   * ```javascript
   *        // Subscribing with acknowledgement
   *        let receiptId = randomText();
   *
   *        client.watchForReceipt(receiptId, function() {
   *          // Will be called after server acknowledges
   *        });
   *
   *        client.subscribe(TEST.destination, onMessage, {receipt: receiptId});
   *
   *
   *        // Publishing with acknowledgement
   *        receiptId = randomText();
   *
   *        client.watchForReceipt(receiptId, function() {
   *          // Will be called after server acknowledges
   *        });
   *        client.publish({destination: TEST.destination, headers: {receipt: receiptId}, body: msg});
   * ```
   */
  public watchForReceipt(receiptId: string, callback: frameCallbackType): void {
    this._checkConnection();
    // @ts-ignore - we already checked that there is a _stompHandler, and it is connected
    this._stompHandler.watchForReceipt(receiptId, callback);
  }

  /**
   * Subscribe to a STOMP Broker location. The callback will be invoked for each
   * received message with the {@link IMessage} as argument.
   *
   * Note: The library will generate a unique ID if there is none provided in the headers.
   *       To use your own ID, pass it using the `headers` argument.
   *
   * ```javascript
   *        callback = function(message) {
   *        // called when the client receives a STOMP message from the server
   *          if (message.body) {
   *            alert("got message with body " + message.body)
   *          } else {
   *            alert("got empty message");
   *          }
   *        });
   *
   *        var subscription = client.subscribe("/queue/test", callback);
   *
   *        // Explicit subscription id
   *        var mySubId = 'my-subscription-id-001';
   *        var subscription = client.subscribe(destination, callback, { id: mySubId });
   * ```
   */
  public subscribe(
    destination: string,
    callback: messageCallbackType,
    headers: StompHeaders = {}
  ): StompSubscription {
    this._checkConnection();
    // @ts-ignore - we already checked that there is a _stompHandler, and it is connected
    return this._stompHandler.subscribe(destination, callback, headers);
  }

  /**
   * It is preferable to unsubscribe from a subscription by calling
   * `unsubscribe()` directly on {@link StompSubscription} returned by `client.subscribe()`:
   *
   * ```javascript
   *        var subscription = client.subscribe(destination, onmessage);
   *        // ...
   *        subscription.unsubscribe();
   * ```
   *
   * See: https://stomp.github.com/stomp-specification-1.2.html#UNSUBSCRIBE UNSUBSCRIBE Frame
   */
  public unsubscribe(id: string, headers: StompHeaders = {}): void {
    this._checkConnection();
    // @ts-ignore - we already checked that there is a _stompHandler, and it is connected
    this._stompHandler.unsubscribe(id, headers);
  }

  /**
   * Start a transaction, the returned {@link ITransaction} has methods - [commit]{@link ITransaction#commit}
   * and [abort]{@link ITransaction#abort}.
   *
   * `transactionId` is optional, if not passed the library will generate it internally.
   */
  public begin(transactionId?: string): ITransaction {
    this._checkConnection();
    // @ts-ignore - we already checked that there is a _stompHandler, and it is connected
    return this._stompHandler.begin(transactionId);
  }

  /**
   * Commit a transaction.
   *
   * It is preferable to commit a transaction by calling [commit]{@link ITransaction#commit} directly on
   * {@link ITransaction} returned by [client.begin]{@link Client#begin}.
   *
   * ```javascript
   *        var tx = client.begin(txId);
   *        //...
   *        tx.commit();
   * ```
   */
  public commit(transactionId: string): void {
    this._checkConnection();
    // @ts-ignore - we already checked that there is a _stompHandler, and it is connected
    this._stompHandler.commit(transactionId);
  }

  /**
   * Abort a transaction.
   * It is preferable to abort a transaction by calling [abort]{@link ITransaction#abort} directly on
   * {@link ITransaction} returned by [client.begin]{@link Client#begin}.
   *
   * ```javascript
   *        var tx = client.begin(txId);
   *        //...
   *        tx.abort();
   * ```
   */
  public abort(transactionId: string): void {
    this._checkConnection();
    // @ts-ignore - we already checked that there is a _stompHandler, and it is connected
    this._stompHandler.abort(transactionId);
  }

  /**
   * ACK a message. It is preferable to acknowledge a message by calling [ack]{@link IMessage#ack} directly
   * on the {@link IMessage} handled by a subscription callback:
   *
   * ```javascript
   *        var callback = function (message) {
   *          // process the message
   *          // acknowledge it
   *          message.ack();
   *        };
   *        client.subscribe(destination, callback, {'ack': 'client'});
   * ```
   */
  public ack(
    messageId: string,
    subscriptionId: string,
    headers: StompHeaders = {}
  ): void {
    this._checkConnection();
    // @ts-ignore - we already checked that there is a _stompHandler, and it is connected
    this._stompHandler.ack(messageId, subscriptionId, headers);
  }

  /**
   * NACK a message. It is preferable to acknowledge a message by calling [nack]{@link IMessage#nack} directly
   * on the {@link IMessage} handled by a subscription callback:
   *
   * ```javascript
   *        var callback = function (message) {
   *          // process the message
   *          // an error occurs, nack it
   *          message.nack();
   *        };
   *        client.subscribe(destination, callback, {'ack': 'client'});
   * ```
   */
  public nack(
    messageId: string,
    subscriptionId: string,
    headers: StompHeaders = {}
  ): void {
    this._checkConnection();
    // @ts-ignore - we already checked that there is a _stompHandler, and it is connected
    this._stompHandler.nack(messageId, subscriptionId, headers);
  }
}

results matching ""

    No results matching ""