UNPKG

@stomp/stompjs

Version:

STOMP client for Javascript and Typescript

348 lines 14.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var byte_1 = require("./byte"); var frame_impl_1 = require("./frame-impl"); var parser_1 = require("./parser"); var versions_1 = require("./versions"); var web_socket_state_1 = require("./web-socket-state"); /** * The STOMP protocol handler * * Part of `@stomp/stompjs`. * * @internal */ var StompHandler = /** @class */ (function () { function StompHandler(_client, _webSocket, config) { var _this = this; if (config === void 0) { config = {}; } this._client = _client; this._webSocket = _webSocket; this._serverFrameHandlers = { // [CONNECTED Frame](http://stomp.github.com/stomp-specification-1.2.html#CONNECTED_Frame) CONNECTED: function (frame) { _this.debug("connected to server " + frame.headers.server); _this._connected = true; _this._connectedVersion = frame.headers.version; // STOMP version 1.2 needs header values to be escaped if (_this._connectedVersion === versions_1.Versions.V1_2) { _this._escapeHeaderValues = true; } _this._setupHeartbeat(frame.headers); _this.onConnect(frame); }, // [MESSAGE Frame](http://stomp.github.com/stomp-specification-1.2.html#MESSAGE) MESSAGE: function (frame) { // the callback is registered when the client calls // `subscribe()`. // If there is no registered subscription for the received message, // the default `onUnhandledMessage` callback is used that the client can set. // This is useful for subscriptions that are automatically created // on the browser side (e.g. [RabbitMQ's temporary // queues](http://www.rabbitmq.com/stomp.html)). var subscription = frame.headers.subscription; var onReceive = _this._subscriptions[subscription] || _this.onUnhandledMessage; // bless the frame to be a Message var message = frame; var client = _this; var messageId = _this._connectedVersion === versions_1.Versions.V1_2 ? message.headers.ack : message.headers['message-id']; // add `ack()` and `nack()` methods directly to the returned frame // so that a simple call to `message.ack()` can acknowledge the message. message.ack = function (headers) { if (headers === void 0) { headers = {}; } return client.ack(messageId, subscription, headers); }; message.nack = function (headers) { if (headers === void 0) { headers = {}; } return client.nack(messageId, subscription, headers); }; onReceive(message); }, // [RECEIPT Frame](http://stomp.github.com/stomp-specification-1.2.html#RECEIPT) RECEIPT: function (frame) { var callback = _this._receiptWatchers[frame.headers['receipt-id']]; if (callback) { callback(frame); // Server will acknowledge only once, remove the callback delete _this._receiptWatchers[frame.headers['receipt-id']]; } else { _this.onUnhandledReceipt(frame); } }, // [ERROR Frame](http://stomp.github.com/stomp-specification-1.2.html#ERROR) ERROR: function (frame) { _this.onStompError(frame); } }; // used to index subscribers this._counter = 0; // subscription callbacks indexed by subscriber's ID this._subscriptions = {}; // receipt-watchers indexed by receipts-ids this._receiptWatchers = {}; this._partialData = ''; this._escapeHeaderValues = false; this._lastServerActivityTS = Date.now(); this.configure(config); } Object.defineProperty(StompHandler.prototype, "connectedVersion", { get: function () { return this._connectedVersion; }, enumerable: true, configurable: true }); Object.defineProperty(StompHandler.prototype, "connected", { get: function () { return this._connected; }, enumerable: true, configurable: true }); StompHandler.prototype.configure = function (conf) { // bulk assign all properties to this Object.assign(this, conf); }; StompHandler.prototype.start = function () { var _this = this; var parser = new parser_1.Parser( // On Frame function (rawFrame) { var frame = frame_impl_1.FrameImpl.fromRawFrame(rawFrame, _this._escapeHeaderValues); // if this.logRawCommunication is set, the rawChunk is logged at this._webSocket.onmessage if (!_this.logRawCommunication) { _this.debug("<<< " + frame); } var serverFrameHandler = _this._serverFrameHandlers[frame.command] || _this.onUnhandledFrame; serverFrameHandler(frame); }, // On Incoming Ping function () { _this.debug('<<< PONG'); }); this._webSocket.onmessage = function (evt) { _this.debug('Received data'); _this._lastServerActivityTS = Date.now(); if (_this.logRawCommunication) { var rawChunkAsString = (evt.data instanceof ArrayBuffer) ? new TextDecoder().decode(evt.data) : evt.data; _this.debug("<<< " + rawChunkAsString); } parser.parseChunk(evt.data, _this.appendMissingNULLonIncoming); }; this._webSocket.onclose = function (closeEvent) { _this.debug("Connection closed to " + _this._webSocket.url); _this.onWebSocketClose(closeEvent); _this._cleanUp(); }; this._webSocket.onerror = function (errorEvent) { _this.onWebSocketError(errorEvent); }; this._webSocket.onopen = function () { // Clone before updating var connectHeaders = Object.assign({}, _this.connectHeaders); _this.debug('Web Socket Opened...'); connectHeaders['accept-version'] = _this.stompVersions.supportedVersions(); connectHeaders['heart-beat'] = [_this.heartbeatOutgoing, _this.heartbeatIncoming].join(','); _this._transmit({ command: 'CONNECT', headers: connectHeaders }); }; }; StompHandler.prototype._setupHeartbeat = function (headers) { var _this = this; if ((headers.version !== versions_1.Versions.V1_1 && headers.version !== versions_1.Versions.V1_2)) { return; } // heart-beat header received from the server looks like: // // heart-beat: sx, sy var _a = (headers['heart-beat']).split(',').map(function (v) { return parseInt(v, 10); }), serverOutgoing = _a[0], serverIncoming = _a[1]; if ((this.heartbeatOutgoing !== 0) && (serverIncoming !== 0)) { var ttl = Math.max(this.heartbeatOutgoing, serverIncoming); this.debug("send PING every " + ttl + "ms"); this._pinger = setInterval(function () { if (_this._webSocket.readyState === web_socket_state_1.WebSocketState.OPEN) { _this._webSocket.send(byte_1.BYTE.LF); _this.debug('>>> PING'); } }, ttl); } if ((this.heartbeatIncoming !== 0) && (serverOutgoing !== 0)) { var ttl_1 = Math.max(this.heartbeatIncoming, serverOutgoing); this.debug("check PONG every " + ttl_1 + "ms"); this._ponger = setInterval(function () { var delta = Date.now() - _this._lastServerActivityTS; // We wait twice the TTL to be flexible on window's setInterval calls if (delta > (ttl_1 * 2)) { _this.debug("did not receive server activity for the last " + delta + "ms"); _this._webSocket.close(); } }, ttl_1); } }; StompHandler.prototype._transmit = function (params) { var command = params.command, headers = params.headers, body = params.body, binaryBody = params.binaryBody, skipContentLengthHeader = params.skipContentLengthHeader; var frame = new frame_impl_1.FrameImpl({ command: command, headers: headers, body: body, binaryBody: binaryBody, escapeHeaderValues: this._escapeHeaderValues, skipContentLengthHeader: skipContentLengthHeader }); var rawChunk = frame.serialize(); if (this.logRawCommunication) { this.debug(">>> " + rawChunk); } else { this.debug(">>> " + frame); } if (this.forceBinaryWSFrames && typeof rawChunk === 'string') { rawChunk = new TextEncoder().encode(rawChunk); } if (typeof rawChunk !== 'string' || !this.splitLargeFrames) { this._webSocket.send(rawChunk); } else { var out = rawChunk; while (out.length > 0) { var chunk = out.substring(0, this.maxWebSocketChunkSize); out = out.substring(this.maxWebSocketChunkSize); this._webSocket.send(chunk); this.debug("chunk sent = " + chunk.length + ", remaining = " + out.length); } } }; StompHandler.prototype.dispose = function () { var _this = this; if (this.connected) { try { // clone before updating var disconnectHeaders = Object.assign({}, this.disconnectHeaders); if (!disconnectHeaders.receipt) { disconnectHeaders.receipt = "close-" + this._counter++; } this.watchForReceipt(disconnectHeaders.receipt, function (frame) { _this._webSocket.close(); _this._cleanUp(); _this.onDisconnect(frame); }); this._transmit({ command: 'DISCONNECT', headers: disconnectHeaders }); } catch (error) { this.debug("Ignoring error during disconnect " + error); } } else { if (this._webSocket.readyState === web_socket_state_1.WebSocketState.CONNECTING || this._webSocket.readyState === web_socket_state_1.WebSocketState.OPEN) { this._webSocket.close(); } } }; StompHandler.prototype._cleanUp = function () { this._connected = false; if (this._pinger) { clearInterval(this._pinger); } if (this._ponger) { clearInterval(this._ponger); } }; StompHandler.prototype.publish = function (params) { var destination = params.destination, headers = params.headers, body = params.body, binaryBody = params.binaryBody, skipContentLengthHeader = params.skipContentLengthHeader; var hdrs = Object.assign({ destination: destination }, headers); this._transmit({ command: 'SEND', headers: hdrs, body: body, binaryBody: binaryBody, skipContentLengthHeader: skipContentLengthHeader }); }; StompHandler.prototype.watchForReceipt = function (receiptId, callback) { this._receiptWatchers[receiptId] = callback; }; StompHandler.prototype.subscribe = function (destination, callback, headers) { if (headers === void 0) { headers = {}; } headers = Object.assign({}, headers); if (!headers.id) { headers.id = "sub-" + this._counter++; } headers.destination = destination; this._subscriptions[headers.id] = callback; this._transmit({ command: 'SUBSCRIBE', headers: headers }); var client = this; return { id: headers.id, unsubscribe: function (hdrs) { return client.unsubscribe(headers.id, hdrs); } }; }; StompHandler.prototype.unsubscribe = function (id, headers) { if (headers === void 0) { headers = {}; } headers = Object.assign({}, headers); delete this._subscriptions[id]; headers.id = id; this._transmit({ command: 'UNSUBSCRIBE', headers: headers }); }; StompHandler.prototype.begin = function (transactionId) { var txId = transactionId || ("tx-" + this._counter++); this._transmit({ command: 'BEGIN', headers: { transaction: txId } }); var client = this; return { id: txId, commit: function () { client.commit(txId); }, abort: function () { client.abort(txId); } }; }; StompHandler.prototype.commit = function (transactionId) { this._transmit({ command: 'COMMIT', headers: { transaction: transactionId } }); }; StompHandler.prototype.abort = function (transactionId) { this._transmit({ command: 'ABORT', headers: { transaction: transactionId } }); }; StompHandler.prototype.ack = function (messageId, subscriptionId, headers) { if (headers === void 0) { headers = {}; } headers = Object.assign({}, headers); if (this._connectedVersion === versions_1.Versions.V1_2) { headers.id = messageId; } else { headers['message-id'] = messageId; } headers.subscription = subscriptionId; this._transmit({ command: 'ACK', headers: headers }); }; StompHandler.prototype.nack = function (messageId, subscriptionId, headers) { if (headers === void 0) { headers = {}; } headers = Object.assign({}, headers); if (this._connectedVersion === versions_1.Versions.V1_2) { headers.id = messageId; } else { headers['message-id'] = messageId; } headers.subscription = subscriptionId; return this._transmit({ command: 'NACK', headers: headers }); }; return StompHandler; }()); exports.StompHandler = StompHandler; //# sourceMappingURL=stomp-handler.js.map