UNPKG

@stomp/rx-stomp

Version:

RxJS STOMP client for Javascript and Typescript

343 lines (342 loc) 13.4 kB
import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { Client, debugFnType, IFrame, IMessage, publishParams, StompHeaders } from '@stomp/stompjs'; import { RxStompConfig } from './rx-stomp-config.js'; import { IRxStompPublishParams } from './i-rx-stomp-publish-params.js'; import { RxStompState } from './rx-stomp-state.js'; import { IWatchParams } from './i-watch-params.js'; /** * Main RxJS-friendly STOMP client for browsers and Node.js. * * RxStomp wraps {@link Client} from @stomp/stompjs and exposes key interactions * (connection lifecycle, subscriptions, frames, and errors) as RxJS streams. * * What RxStomp adds: * - Simple, observable-based API for consuming messages. * - Connection lifecycle as BehaviorSubjects/Observables for easy UI binding. * - Transparent reconnection support (configurable through {@link RxStompConfig}). * - Convenience helpers for receipts and server headers. * * Typical lifecycle: * - Instantiate: `const rxStomp = new RxStomp();` * - Configure: `rxStomp.configure({...});` * - Activate: `rxStomp.activate();` * - Consume: `rxStomp.watch({ destination: '/topic/foo' }).subscribe(...)` * - Publish: `rxStomp.publish({ destination: '/topic/foo', body: '...' })` * - Deactivate when done: `await rxStomp.deactivate();` * * Notes: * - Except for `beforeConnect`, all callbacks from @stomp/stompjs are exposed * as RxJS Subjects/Observables here. * - RxStomp tries to transparently handle connection failures. * * Part of `@stomp/rx-stomp`. */ export declare class RxStomp { /** * Connection state as a BehaviorSubject. * * - Emits immediately with the current state (initially `CLOSED`). * - Use this for binding UI widgets or guards based on connection status. * - State values are from {@link RxStompState}. */ readonly connectionState$: BehaviorSubject<RxStompState>; /** * Emits whenever a connection is established (including re-connections). * * - If already connected, it emits immediately on subscription. * - The emitted value is always `RxStompState.OPEN`. * - Useful as a trigger to (re)establish subscriptions or flush local queues. */ readonly connected$: Observable<RxStompState>; /** * These will be triggered before connectionState$ and connected$. * During reconnecting, it will allow subscriptions to be reinstated before sending * queued messages. */ private _connectionStatePre$; private _connectedPre$; /** * Provides headers from the most recent CONNECTED frame from the broker. * * - Emits on every successful (re)connection. * - If already connected, emits immediately on subscription. * - Typical headers include `server`, `session`, and negotiated `version`. */ readonly serverHeaders$: Observable<StompHeaders>; protected _serverHeadersBehaviourSubject$: BehaviorSubject<null | StompHeaders>; /** * Streams any unhandled MESSAGE frames. * * Use this to receive: * - (RabbitMQ specific) Messages delivered to temporary or auto-named queues. * - Stray messages arriving during unsubscribe processing. * * Emits raw {@link IMessage} instances. * * Maps to: [Client#onUnhandledMessage]{@link Client#onUnhandledMessage}. */ readonly unhandledMessage$: Subject<IMessage>; /** * Streams any unhandled non-MESSAGE, non-ERROR frames. * * Normally unused unless interacting with non-compliant brokers or testing. * * Emits raw {@link IFrame} instances. * * Maps to: [Client#onUnhandledFrame]{@link Client#onUnhandledFrame}. */ readonly unhandledFrame$: Subject<IFrame>; /** * Streams any RECEIPT frames that do not match a watcher set via asyncReceipt helpers. * * Prefer {@link asyncReceipt} patterns where possible; fall back to this for low-level needs. * * Emits raw {@link IFrame} instances. * * Maps to: [Client#onUnhandledReceipt]{@link Client#onUnhandledReceipt}. */ readonly unhandledReceipts$: Subject<IFrame>; /** * Streams all ERROR frames from the broker. * * Most brokers will close the connection after an ERROR frame. * * Emits raw {@link IFrame} instances. * * Maps to: [Client#onStompError]{@link Client#onStompError}. */ readonly stompErrors$: Subject<IFrame>; /** * Streams errors emitted by the underlying WebSocket. * * Emits a DOM {@link Event}. * * Maps to: [Client#onWebSocketError]{@link Client#onWebSocketError}. */ readonly webSocketErrors$: Subject<Event>; /** * Internal array to hold locally queued messages when STOMP broker is not connected. */ protected _queuedMessages: publishParams[]; /** * Instance of actual * [@stomp/stompjs]{@link https://github.com/stomp-js/stompjs} * {@link Client}. * * **Be careful in calling methods on it directly - you may get unintended consequences.** */ get stompClient(): Client; protected _stompClient: Client; /** * Before connect */ protected _beforeConnect: (client: RxStomp) => void | Promise<void>; /** * Correlate errors */ protected _correlateErrors: (error: IFrame) => string; /** * Will be assigned during configuration, no-op otherwise */ protected _debug: debugFnType; /** * Constructor * * @param stompClient Optional existing {@link Client} to wrap. If omitted, a new instance * will be created internally. * * Tip: Injecting a pre-configured Client is useful for advanced customization or testing. */ constructor(stompClient?: Client); /** * Apply configuration to the underlying STOMP client. * * - Safe to call multiple times; each call merges with existing configuration. * - `beforeConnect` and `correlateErrors` are handled by RxStomp and removed * from the object passed to the underlying {@link Client}. * - Unless otherwise documented by @stomp/stompjs, most options take effect * on the next (re)connection. * * Example: * ```typescript * const rxStomp = new RxStomp(); * rxStomp.configure({ * brokerURL: 'ws://127.0.0.1:15674/ws', * connectHeaders: { * login: 'guest', * passcode: 'guest' * }, * heartbeatIncoming: 0, * heartbeatOutgoing: 20000, * reconnectDelay: 200, * debug: (msg) => console.log(new Date(), msg), * }); * rxStomp.activate(); * ``` * * Maps to: [Client#configure]{@link Client#configure}. */ configure(rxStompConfig: RxStompConfig): void; /** * Activate the client and initiate connection attempts. * * - Emits `CONNECTING` followed by `OPEN` on successful connect. * - Automatically reconnects, according to configuration. * * To stop auto-reconnect and close the connection, call {@link deactivate}. * * Maps to: [Client#activate]{@link Client#activate}. */ activate(): void; /** * Gracefully disconnect (if connected) and stop auto-reconnect. * * Behavior: * - Emits `CLOSING` then `CLOSED`. * - If no active WebSocket exists, resolves immediately. * - If a WebSocket is active, resolves after it is properly closed. * * Experimental: * - `options.force === true` immediately discards the underlying connection. * See [Client#deactivate]{@link Client#deactivate}. * * You can call {@link activate} again after awa. * * Maps to: [Client#deactivate]{@link Client#deactivate}. */ deactivate(options?: { force?: boolean; }): Promise<void>; /** * Whether the broker connection is currently OPEN. * * Equivalent to checking `connectionState$.value === RxStompState.OPEN`. */ connected(): boolean; /** * True if the client is ACTIVE — i.e., connected or attempting reconnection. * * Maps to: [Client#active]{@link Client#active}. */ get active(): boolean; /** * Publish a message to a destination on the broker. * * Destination semantics and supported headers are broker-specific; consult your broker’s docs * for naming conventions (queues, topics, exchanges) and header support. * * Payload * - Text: provide `body` as a string. Convert non-strings yourself (e.g., JSON.stringify). * - Binary: provide `binaryBody` as a Uint8Array and set an appropriate `content-type` header. * Some brokers may require explicit configuration for binary frames. * * Frame sizing and content-length * - For text messages, a `content-length` header is added by default. * Set `skipContentLengthHeader: true` to omit it for text frames. * - For binary messages, `content-length` is always included. * * Caution * - If a message body contains NULL octets and the `content-length` header is omitted, * many brokers will report an error and disconnect. * * Offline/queueing behavior * - If not connected, messages are queued locally and sent upon reconnection. * - To disable this behavior, set `retryIfDisconnected: false` in the parameters. * In that case, this method throws if it cannot send immediately. * * Related * - Broker acknowledgment (receipt) can be tracked using `receipt` headers together with {@link asyncReceipt}. * * Maps to: [Client#publish]{@link Client#publish} * * See: {@link IRxStompPublishParams} and {@link IPublishParams} * * Examples: * ```javascript * // Text with custom headers * rxStomp.publish({ destination: "/queue/test", headers: { priority: 9 }, body: "Hello, STOMP" }); * * // Minimal (destination is required) * rxStomp.publish({ destination: "/queue/test", body: "Hello, STOMP" }); * * // Skip content-length header for text * rxStomp.publish({ destination: "/queue/test", body: "Hello, STOMP", skipContentLengthHeader: true }); * * // Binary payload * const binaryData = generateBinaryData(); // Uint8Array * rxStomp.publish({ * destination: "/topic/special", * binaryBody: binaryData, * headers: { "content-type": "application/octet-stream" } * }); * ``` */ publish(parameters: IRxStompPublishParams): void; /** It will send queued messages. */ protected _sendQueuedMessages(): void; /** * Subscribe to a destination and receive messages as an RxJS Observable. * * Connection resilience * - Safe to call when disconnected; it will subscribe upon the next connection. * - On reconnect, it re-subscribes automatically to the same destination unless * `subscribeOnlyOnce: true` is specified. * - Messages may be missed during broker-side reconnect windows (typical for STOMP brokers). * * Options * - Provide an {@link IWatchParams} object to set subscription (`subHeaders`) and unsubscription * (`unsubHeaders`) headers, and to control resubscription behavior (`subscribeOnlyOnce`). * * Returns * - A hot Observable that remains stable across reconnects. Multiple subscribers share the * same underlying STOMP subscription. * * Maps to: [Client#subscribe]{@link Client#subscribe} */ watch(opts: IWatchParams): Observable<IMessage>; /** * See the other overload for details. * * @param destination Destination to subscribe to * @param headers Optional subscription headers */ watch(destination: string, headers?: StompHeaders): Observable<IMessage>; /** * **Deprecated** Please use {@link asyncReceipt}. */ watchForReceipt(receiptId: string, callback: (frame: IFrame) => void): void; /** * Wait for a broker RECEIPT matching the provided receipt-id. * * How it works * - To request an acknowledgment for an operation (e.g., publish, subscribe, unsubscribe), * include a `receipt` header in that operation with a unique value. * - A compliant broker will respond with a `RECEIPT` frame whose `receipt-id` header equals * the value you sent in the `receipt` header. * - This method returns a Promise that resolves with the matching {@link IFrame} when the * corresponding `RECEIPT` arrives. * * Receipt identifiers * - Must be unique per request; generating a UUID or monotonic sequence is typical. * * Notes * - The Promise resolves once for the first matching receipt and then completes. * - No timeout is enforced by default; to add one, wrap with your own timeout logic (e.g., Promise.race). * * Example: * ```javascript * // Publish with receipt tracking * const receiptId = randomText(); * rxStomp.publish({ * destination: "/topic/special", * headers: { receipt: receiptId }, * body: msg * }); * const receiptFrame = await rxStomp.asyncReceipt(receiptId); // resolves with the RECEIPT frame * ``` * * Maps to: [Client#watchForReceipt]{@link Client#watchForReceipt} */ asyncReceipt(receiptId: string): Promise<IFrame>; protected _changeState(state: RxStompState): void; }