841 lines
33 KiB
JavaScript
841 lines
33 KiB
JavaScript
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
(global.webstomp = factory());
|
|
}(this, (function () { 'use strict';
|
|
|
|
var classCallCheck = function (instance, Constructor) {
|
|
if (!(instance instanceof Constructor)) {
|
|
throw new TypeError("Cannot call a class as a function");
|
|
}
|
|
};
|
|
|
|
var createClass = function () {
|
|
function defineProperties(target, props) {
|
|
for (var i = 0; i < props.length; i++) {
|
|
var descriptor = props[i];
|
|
descriptor.enumerable = descriptor.enumerable || false;
|
|
descriptor.configurable = true;
|
|
if ("value" in descriptor) descriptor.writable = true;
|
|
Object.defineProperty(target, descriptor.key, descriptor);
|
|
}
|
|
}
|
|
|
|
return function (Constructor, protoProps, staticProps) {
|
|
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
|
if (staticProps) defineProperties(Constructor, staticProps);
|
|
return Constructor;
|
|
};
|
|
}();
|
|
|
|
var slicedToArray = function () {
|
|
function sliceIterator(arr, i) {
|
|
var _arr = [];
|
|
var _n = true;
|
|
var _d = false;
|
|
var _e = undefined;
|
|
|
|
try {
|
|
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
|
|
_arr.push(_s.value);
|
|
|
|
if (i && _arr.length === i) break;
|
|
}
|
|
} catch (err) {
|
|
_d = true;
|
|
_e = err;
|
|
} finally {
|
|
try {
|
|
if (!_n && _i["return"]) _i["return"]();
|
|
} finally {
|
|
if (_d) throw _e;
|
|
}
|
|
}
|
|
|
|
return _arr;
|
|
}
|
|
|
|
return function (arr, i) {
|
|
if (Array.isArray(arr)) {
|
|
return arr;
|
|
} else if (Symbol.iterator in Object(arr)) {
|
|
return sliceIterator(arr, i);
|
|
} else {
|
|
throw new TypeError("Invalid attempt to destructure non-iterable instance");
|
|
}
|
|
};
|
|
}();
|
|
|
|
var toConsumableArray = function (arr) {
|
|
if (Array.isArray(arr)) {
|
|
for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
|
|
|
|
return arr2;
|
|
} else {
|
|
return Array.from(arr);
|
|
}
|
|
};
|
|
|
|
var VERSIONS = {
|
|
V1_0: '1.0',
|
|
V1_1: '1.1',
|
|
V1_2: '1.2',
|
|
// Versions of STOMP specifications supported
|
|
supportedVersions: function supportedVersions() {
|
|
return '1.2,1.1,1.0';
|
|
},
|
|
supportedProtocols: function supportedProtocols() {
|
|
return ['v10.stomp', 'v11.stomp', 'v12.stomp'];
|
|
}
|
|
};
|
|
|
|
var PROTOCOLS_VERSIONS = {
|
|
'v10.stomp': VERSIONS.V1_0,
|
|
'v11.stomp': VERSIONS.V1_1,
|
|
'v12.stomp': VERSIONS.V1_2
|
|
};
|
|
|
|
function getSupportedVersion(protocol, debug) {
|
|
var knownVersion = PROTOCOLS_VERSIONS[protocol];
|
|
if (!knownVersion && debug) {
|
|
debug('DEPRECATED: ' + protocol + ' is not a recognized STOMP version. In next major client version, this will close the connection.');
|
|
}
|
|
// 2nd temporary fallback if the protocol
|
|
// does not match a supported STOMP version
|
|
// This fallback will be removed in next major version
|
|
return knownVersion || VERSIONS.V1_2;
|
|
}
|
|
|
|
// Define constants for bytes used throughout the code.
|
|
var BYTES = {
|
|
// LINEFEED byte (octet 10)
|
|
LF: '\x0A',
|
|
// NULL byte (octet 0)
|
|
NULL: '\x00'
|
|
};
|
|
|
|
// utility function to trim any whitespace before and after a string
|
|
var trim = function trim(str) {
|
|
return str.replace(/^\s+|\s+$/g, '');
|
|
};
|
|
|
|
// from https://coolaj86.com/articles/unicode-string-to-a-utf-8-typed-array-buffer-in-javascript/
|
|
function unicodeStringToTypedArray(s) {
|
|
var escstr = encodeURIComponent(s);
|
|
var binstr = escstr.replace(/%([0-9A-F]{2})/g, function (match, p1) {
|
|
return String.fromCharCode('0x' + p1);
|
|
});
|
|
var arr = Array.prototype.map.call(binstr, function (c) {
|
|
return c.charCodeAt(0);
|
|
});
|
|
return new Uint8Array(arr);
|
|
}
|
|
|
|
// from https://coolaj86.com/articles/unicode-string-to-a-utf-8-typed-array-buffer-in-javascript/
|
|
function typedArrayToUnicodeString(ua) {
|
|
var binstr = String.fromCharCode.apply(String, toConsumableArray(ua));
|
|
var escstr = binstr.replace(/(.)/g, function (m, p) {
|
|
var code = p.charCodeAt(0).toString(16).toUpperCase();
|
|
if (code.length < 2) {
|
|
code = '0' + code;
|
|
}
|
|
return '%' + code;
|
|
});
|
|
return decodeURIComponent(escstr);
|
|
}
|
|
|
|
// Compute the size of a UTF-8 string by counting its number of bytes
|
|
// (and not the number of characters composing the string)
|
|
function sizeOfUTF8(s) {
|
|
if (!s) return 0;
|
|
return encodeURIComponent(s).match(/%..|./g).length;
|
|
}
|
|
|
|
function createId() {
|
|
var ts = new Date().getTime();
|
|
var rand = Math.floor(Math.random() * 1000);
|
|
return ts + '-' + rand;
|
|
}
|
|
|
|
// [STOMP Frame](http://stomp.github.com/stomp-specification-1.1.html#STOMP_Frames) Class
|
|
|
|
var Frame = function () {
|
|
|
|
// Frame constructor
|
|
function Frame(command) {
|
|
var headers = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
var body = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
|
classCallCheck(this, Frame);
|
|
|
|
this.command = command;
|
|
this.headers = headers;
|
|
this.body = body;
|
|
}
|
|
|
|
// Provides a textual representation of the frame
|
|
// suitable to be sent to the server
|
|
|
|
|
|
createClass(Frame, [{
|
|
key: 'toString',
|
|
value: function toString() {
|
|
var _this = this;
|
|
|
|
var lines = [this.command],
|
|
skipContentLength = this.headers['content-length'] === false;
|
|
if (skipContentLength) delete this.headers['content-length'];
|
|
|
|
Object.keys(this.headers).forEach(function (name) {
|
|
var value = _this.headers[name];
|
|
lines.push(name + ':' + value);
|
|
});
|
|
|
|
if (this.body && !skipContentLength) {
|
|
lines.push('content-length:' + sizeOfUTF8(this.body));
|
|
}
|
|
|
|
lines.push(BYTES.LF + this.body);
|
|
|
|
return lines.join(BYTES.LF);
|
|
}
|
|
|
|
// Unmarshall a single STOMP frame from a `data` string
|
|
|
|
}], [{
|
|
key: 'unmarshallSingle',
|
|
value: function unmarshallSingle(data) {
|
|
// search for 2 consecutives LF byte to split the command
|
|
// and headers from the body
|
|
var divider = data.search(new RegExp(BYTES.LF + BYTES.LF)),
|
|
headerLines = data.substring(0, divider).split(BYTES.LF),
|
|
command = headerLines.shift(),
|
|
headers = {},
|
|
body = '',
|
|
|
|
// skip the 2 LF bytes that divides the headers from the body
|
|
bodyIndex = divider + 2;
|
|
|
|
// Parse headers in reverse order so that for repeated headers, the 1st
|
|
// value is used
|
|
var _iteratorNormalCompletion = true;
|
|
var _didIteratorError = false;
|
|
var _iteratorError = undefined;
|
|
|
|
try {
|
|
for (var _iterator = headerLines.reverse()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
|
|
var line = _step.value;
|
|
|
|
var idx = line.indexOf(':');
|
|
headers[trim(line.substring(0, idx))] = trim(line.substring(idx + 1));
|
|
}
|
|
// Parse body
|
|
// check for content-length or topping at the first NULL byte found.
|
|
} catch (err) {
|
|
_didIteratorError = true;
|
|
_iteratorError = err;
|
|
} finally {
|
|
try {
|
|
if (!_iteratorNormalCompletion && _iterator.return) {
|
|
_iterator.return();
|
|
}
|
|
} finally {
|
|
if (_didIteratorError) {
|
|
throw _iteratorError;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (headers['content-length']) {
|
|
var len = parseInt(headers['content-length'], 10);
|
|
body = ('' + data).substring(bodyIndex, bodyIndex + len);
|
|
} else {
|
|
var chr = null;
|
|
for (var i = bodyIndex; i < data.length; i++) {
|
|
chr = data.charAt(i);
|
|
if (chr === BYTES.NULL) break;
|
|
body += chr;
|
|
}
|
|
}
|
|
|
|
return new Frame(command, headers, body);
|
|
}
|
|
|
|
// Split the data before unmarshalling every single STOMP frame.
|
|
// Web socket servers can send multiple frames in a single websocket message.
|
|
// If the message size exceeds the websocket message size, then a single
|
|
// frame can be fragmented across multiple messages.
|
|
//
|
|
// `datas` is a string.
|
|
//
|
|
// returns an *array* of Frame objects
|
|
|
|
}, {
|
|
key: 'unmarshall',
|
|
value: function unmarshall(datas) {
|
|
// split and unmarshall *multiple STOMP frames* contained in a *single WebSocket frame*.
|
|
// The data is split when a NULL byte (followed by zero or many LF bytes) is found
|
|
var frames = datas.split(new RegExp(BYTES.NULL + BYTES.LF + '*')),
|
|
firstFrames = frames.slice(0, -1),
|
|
lastFrame = frames.slice(-1)[0],
|
|
r = {
|
|
frames: firstFrames.map(function (f) {
|
|
return Frame.unmarshallSingle(f);
|
|
}),
|
|
partial: ''
|
|
};
|
|
|
|
// If this contains a final full message or just a acknowledgement of a PING
|
|
// without any other content, process this frame, otherwise return the
|
|
// contents of the buffer to the caller.
|
|
if (lastFrame === BYTES.LF || lastFrame.search(RegExp(BYTES.NULL + BYTES.LF + '*$')) !== -1) {
|
|
r.frames.push(Frame.unmarshallSingle(lastFrame));
|
|
} else {
|
|
r.partial = lastFrame;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
// Marshall a Stomp frame
|
|
|
|
}, {
|
|
key: 'marshall',
|
|
value: function marshall(command, headers, body) {
|
|
var frame = new Frame(command, headers, body);
|
|
return frame.toString() + BYTES.NULL;
|
|
}
|
|
}]);
|
|
return Frame;
|
|
}();
|
|
|
|
// STOMP Client Class
|
|
//
|
|
// All STOMP protocol is exposed as methods of this class (`connect()`,
|
|
// `send()`, etc.)
|
|
|
|
var Client = function () {
|
|
function Client(ws) {
|
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
classCallCheck(this, Client);
|
|
|
|
// cannot have default options object + destructuring in the same time in method signature
|
|
var _options$binary = options.binary,
|
|
binary = _options$binary === undefined ? false : _options$binary,
|
|
_options$heartbeat = options.heartbeat,
|
|
heartbeat = _options$heartbeat === undefined ? { outgoing: 10000, incoming: 10000 } : _options$heartbeat,
|
|
_options$debug = options.debug,
|
|
debug = _options$debug === undefined ? true : _options$debug,
|
|
_options$protocols = options.protocols,
|
|
protocols = _options$protocols === undefined ? [] : _options$protocols;
|
|
|
|
|
|
this.ws = ws;
|
|
this.ws.binaryType = 'arraybuffer';
|
|
this.isBinary = !!binary;
|
|
this.hasDebug = !!debug;
|
|
this.connected = false;
|
|
// Heartbeat properties of the client
|
|
// outgoing: send heartbeat every 10s by default (value is in ms)
|
|
// incoming: expect to receive server heartbeat at least every 10s by default
|
|
// falsy value means no heartbeat hence 0,0
|
|
this.heartbeat = heartbeat || { outgoing: 0, incoming: 0 };
|
|
// maximum *WebSocket* frame size sent by the client. If the STOMP frame
|
|
// is bigger than this value, the STOMP frame will be sent using multiple
|
|
// WebSocket frames (default is 16KiB)
|
|
this.maxWebSocketFrameSize = 16 * 1024;
|
|
// subscription callbacks indexed by subscriber's ID
|
|
this.subscriptions = {};
|
|
this.partialData = '';
|
|
this.protocols = protocols;
|
|
}
|
|
|
|
// //// Debugging
|
|
//
|
|
// By default, debug messages are logged in the window's console if it is defined.
|
|
// This method is called for every actual transmission of the STOMP frames over the
|
|
// WebSocket.
|
|
//
|
|
// It is possible to set a `debug(message, data)` method
|
|
// on a client instance to handle differently the debug messages:
|
|
//
|
|
// client.debug = function(str) {
|
|
// // append the debug log to a #debug div
|
|
// $("#debug").append(str + "\n");
|
|
// };
|
|
|
|
|
|
createClass(Client, [{
|
|
key: 'debug',
|
|
value: function debug() {
|
|
var _console;
|
|
|
|
if (this.hasDebug) (_console = console).log.apply(_console, arguments);
|
|
}
|
|
|
|
// [CONNECT Frame](http://stomp.github.com/stomp-specification-1.1.html#CONNECT_or_STOMP_Frame)
|
|
//
|
|
// The `connect` method accepts different number of arguments and types:
|
|
//
|
|
// * `connect(headers, connectCallback)`
|
|
// * `connect(headers, connectCallback, errorCallback)`
|
|
// * `connect(login, passcode, connectCallback)`
|
|
// * `connect(login, passcode, connectCallback, errorCallback)`
|
|
// * `connect(login, passcode, connectCallback, errorCallback, host)`
|
|
//
|
|
// The errorCallback is optional and the 2 first forms allow to pass other
|
|
// headers in addition to `client`, `passcode` and `host`.
|
|
|
|
}, {
|
|
key: 'connect',
|
|
value: function connect() {
|
|
var _this = this;
|
|
|
|
var _parseConnect2 = this._parseConnect.apply(this, arguments),
|
|
_parseConnect3 = slicedToArray(_parseConnect2, 3),
|
|
headers = _parseConnect3[0],
|
|
connectCallback = _parseConnect3[1],
|
|
errorCallback = _parseConnect3[2];
|
|
|
|
this.connectCallback = connectCallback;
|
|
this.debug('Opening Web Socket...');
|
|
this.ws.onmessage = function (evt) {
|
|
var data = evt.data;
|
|
if (evt.data instanceof ArrayBuffer) {
|
|
data = typedArrayToUnicodeString(new Uint8Array(evt.data));
|
|
}
|
|
_this.serverActivity = Date.now();
|
|
// heartbeat
|
|
if (data === BYTES.LF) {
|
|
_this.debug('<<< PONG');
|
|
return;
|
|
}
|
|
_this.debug('<<< ' + data);
|
|
// Handle STOMP frames received from the server
|
|
// The unmarshall function returns the frames parsed and any remaining
|
|
// data from partial frames.
|
|
var unmarshalledData = Frame.unmarshall(_this.partialData + data);
|
|
_this.partialData = unmarshalledData.partial;
|
|
unmarshalledData.frames.forEach(function (frame) {
|
|
switch (frame.command) {
|
|
// [CONNECTED Frame](http://stomp.github.com/stomp-specification-1.1.html#CONNECTED_Frame)
|
|
case 'CONNECTED':
|
|
_this.debug('connected to server ' + frame.headers.server);
|
|
_this.connected = true;
|
|
_this.version = frame.headers.version;
|
|
_this._setupHeartbeat(frame.headers);
|
|
if (connectCallback) connectCallback(frame);
|
|
break;
|
|
// [MESSAGE Frame](http://stomp.github.com/stomp-specification-1.1.html#MESSAGE)
|
|
case 'MESSAGE':
|
|
// the `onreceive` callback is registered when the client calls
|
|
// `subscribe()`.
|
|
// If there is registered subscription for the received message,
|
|
// we used the default `onreceive` method 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.onreceive;
|
|
if (onreceive) {
|
|
// 1.2 define ack header if ack is set to client
|
|
// and this header must be used for ack/nack
|
|
var messageID = _this.version === VERSIONS.V1_2 && frame.headers.ack || frame.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.
|
|
frame.ack = _this.ack.bind(_this, messageID, subscription);
|
|
frame.nack = _this.nack.bind(_this, messageID, subscription);
|
|
onreceive(frame);
|
|
} else {
|
|
_this.debug('Unhandled received MESSAGE: ' + frame);
|
|
}
|
|
break;
|
|
// [RECEIPT Frame](http://stomp.github.com/stomp-specification-1.1.html#RECEIPT)
|
|
//
|
|
// The client instance can set its `onreceipt` field to a function taking
|
|
// a frame argument that will be called when a receipt is received from
|
|
// the server:
|
|
//
|
|
// client.onreceipt = function(frame) {
|
|
// receiptID = frame.headers['receipt-id'];
|
|
// ...
|
|
// }
|
|
case 'RECEIPT':
|
|
if (_this.onreceipt) _this.onreceipt(frame);
|
|
break;
|
|
// [ERROR Frame](http://stomp.github.com/stomp-specification-1.1.html#ERROR)
|
|
case 'ERROR':
|
|
if (errorCallback) errorCallback(frame);
|
|
break;
|
|
default:
|
|
_this.debug('Unhandled frame: ' + frame);
|
|
}
|
|
});
|
|
};
|
|
this.ws.onclose = function (event) {
|
|
_this.debug('Whoops! Lost connection to ' + _this.ws.url + ':', { event: event });
|
|
_this._cleanUp();
|
|
if (errorCallback) errorCallback(event);
|
|
};
|
|
this.ws.onopen = function () {
|
|
_this.debug('Web Socket Opened...');
|
|
// 1st protocol fallback on user 1st protocols options
|
|
// to prevent edge case where server does not comply and respond with a choosen protocol
|
|
// or when ws client does not handle protocol property very well
|
|
headers['accept-version'] = getSupportedVersion(_this.ws.protocol || _this.protocols[0], _this.debug.bind(_this));
|
|
// Check if we already have heart-beat in headers before adding them
|
|
if (!headers['heart-beat']) {
|
|
headers['heart-beat'] = [_this.heartbeat.outgoing, _this.heartbeat.incoming].join(',');
|
|
}
|
|
_this._transmit('CONNECT', headers);
|
|
};
|
|
if (this.ws.readyState === this.ws.OPEN) {
|
|
this.ws.onopen();
|
|
}
|
|
}
|
|
|
|
// [DISCONNECT Frame](http://stomp.github.com/stomp-specification-1.1.html#DISCONNECT)
|
|
|
|
}, {
|
|
key: 'disconnect',
|
|
value: function disconnect(disconnectCallback) {
|
|
var headers = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
|
|
this._transmit('DISCONNECT', headers);
|
|
// Discard the onclose callback to avoid calling the errorCallback when
|
|
// the client is properly disconnected.
|
|
this.ws.onclose = null;
|
|
this.ws.close();
|
|
this._cleanUp();
|
|
// TODO: what's the point of this callback disconnect is not async
|
|
if (disconnectCallback) disconnectCallback();
|
|
}
|
|
|
|
// [SEND Frame](http://stomp.github.com/stomp-specification-1.1.html#SEND)
|
|
//
|
|
// * `destination` is MANDATORY.
|
|
|
|
}, {
|
|
key: 'send',
|
|
value: function send(destination) {
|
|
var body = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
|
var headers = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
|
|
var hdrs = Object.assign({}, headers);
|
|
hdrs.destination = destination;
|
|
this._transmit('SEND', hdrs, body);
|
|
}
|
|
|
|
// [BEGIN Frame](http://stomp.github.com/stomp-specification-1.1.html#BEGIN)
|
|
//
|
|
// If no transaction ID is passed, one will be created automatically
|
|
|
|
}, {
|
|
key: 'begin',
|
|
value: function begin() {
|
|
var transaction = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'tx-' + createId();
|
|
|
|
this._transmit('BEGIN', { transaction: transaction });
|
|
return {
|
|
id: transaction,
|
|
commit: this.commit.bind(this, transaction),
|
|
abort: this.abort.bind(this, transaction)
|
|
};
|
|
}
|
|
|
|
// [COMMIT Frame](http://stomp.github.com/stomp-specification-1.1.html#COMMIT)
|
|
//
|
|
// * `transaction` is MANDATORY.
|
|
//
|
|
// It is preferable to commit a transaction by calling `commit()` directly on
|
|
// the object returned by `client.begin()`:
|
|
//
|
|
// var tx = client.begin(txid);
|
|
// ...
|
|
// tx.commit();
|
|
|
|
}, {
|
|
key: 'commit',
|
|
value: function commit(transaction) {
|
|
this._transmit('COMMIT', { transaction: transaction });
|
|
}
|
|
|
|
// [ABORT Frame](http://stomp.github.com/stomp-specification-1.1.html#ABORT)
|
|
//
|
|
// * `transaction` is MANDATORY.
|
|
//
|
|
// It is preferable to abort a transaction by calling `abort()` directly on
|
|
// the object returned by `client.begin()`:
|
|
//
|
|
// var tx = client.begin(txid);
|
|
// ...
|
|
// tx.abort();
|
|
|
|
}, {
|
|
key: 'abort',
|
|
value: function abort(transaction) {
|
|
this._transmit('ABORT', { transaction: transaction });
|
|
}
|
|
|
|
// [ACK Frame](http://stomp.github.com/stomp-specification-1.1.html#ACK)
|
|
//
|
|
// * `messageID` & `subscription` are MANDATORY.
|
|
//
|
|
// It is preferable to acknowledge a message by calling `ack()` directly
|
|
// on the message handled by a subscription callback:
|
|
//
|
|
// client.subscribe(destination,
|
|
// function(message) {
|
|
// // process the message
|
|
// // acknowledge it
|
|
// message.ack();
|
|
// },
|
|
// {'ack': 'client'}
|
|
// );
|
|
|
|
}, {
|
|
key: 'ack',
|
|
value: function ack(messageID, subscription) {
|
|
var headers = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
|
|
var hdrs = Object.assign({}, headers);
|
|
// 1.2 change id header name from message-id to id
|
|
var idAttr = this.version === VERSIONS.V1_2 ? 'id' : 'message-id';
|
|
hdrs[idAttr] = messageID;
|
|
hdrs.subscription = subscription;
|
|
this._transmit('ACK', hdrs);
|
|
}
|
|
|
|
// [NACK Frame](http://stomp.github.com/stomp-specification-1.1.html#NACK)
|
|
//
|
|
// * `messageID` & `subscription` are MANDATORY.
|
|
//
|
|
// It is preferable to nack a message by calling `nack()` directly on the
|
|
// message handled by a subscription callback:
|
|
//
|
|
// client.subscribe(destination,
|
|
// function(message) {
|
|
// // process the message
|
|
// // an error occurs, nack it
|
|
// message.nack();
|
|
// },
|
|
// {'ack': 'client'}
|
|
// );
|
|
|
|
}, {
|
|
key: 'nack',
|
|
value: function nack(messageID, subscription) {
|
|
var headers = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
|
|
var hdrs = Object.assign({}, headers);
|
|
// 1.2 change id header name from message-id to id
|
|
var idAttr = this.version === VERSIONS.V1_2 ? 'id' : 'message-id';
|
|
hdrs[idAttr] = messageID;
|
|
hdrs.subscription = subscription;
|
|
this._transmit('NACK', hdrs);
|
|
}
|
|
|
|
// [SUBSCRIBE Frame](http://stomp.github.com/stomp-specification-1.1.html#SUBSCRIBE)
|
|
|
|
}, {
|
|
key: 'subscribe',
|
|
value: function subscribe(destination, callback) {
|
|
var headers = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
|
|
var hdrs = Object.assign({}, headers);
|
|
// for convenience if the `id` header is not set, we create a new one for this client
|
|
// that will be returned to be able to unsubscribe this subscription
|
|
if (!hdrs.id) hdrs.id = 'sub-' + createId();
|
|
hdrs.destination = destination;
|
|
this.subscriptions[hdrs.id] = callback;
|
|
this._transmit('SUBSCRIBE', hdrs);
|
|
return {
|
|
id: hdrs.id,
|
|
unsubscribe: this.unsubscribe.bind(this, hdrs.id)
|
|
};
|
|
}
|
|
|
|
// [UNSUBSCRIBE Frame](http://stomp.github.com/stomp-specification-1.1.html#UNSUBSCRIBE)
|
|
//
|
|
// * `id` is MANDATORY.
|
|
//
|
|
// It is preferable to unsubscribe from a subscription by calling
|
|
// `unsubscribe()` directly on the object returned by `client.subscribe()`:
|
|
//
|
|
// var subscription = client.subscribe(destination, onmessage);
|
|
// ...
|
|
// subscription.unsubscribe(headers);
|
|
|
|
}, {
|
|
key: 'unsubscribe',
|
|
value: function unsubscribe(id) {
|
|
var headers = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
|
|
var hdrs = Object.assign({}, headers);
|
|
delete this.subscriptions[id];
|
|
hdrs.id = id;
|
|
this._transmit('UNSUBSCRIBE', hdrs);
|
|
}
|
|
|
|
// Clean up client resources when it is disconnected or the server did not
|
|
// send heart beats in a timely fashion
|
|
|
|
}, {
|
|
key: '_cleanUp',
|
|
value: function _cleanUp() {
|
|
this.connected = false;
|
|
clearInterval(this.pinger);
|
|
clearInterval(this.ponger);
|
|
}
|
|
|
|
// Base method to transmit any stomp frame
|
|
|
|
}, {
|
|
key: '_transmit',
|
|
value: function _transmit(command, headers, body) {
|
|
var out = Frame.marshall(command, headers, body);
|
|
this.debug('>>> ' + out, { frame: { command: command, headers: headers, body: body } });
|
|
this._wsSend(out);
|
|
}
|
|
}, {
|
|
key: '_wsSend',
|
|
value: function _wsSend(data) {
|
|
if (this.isBinary) data = unicodeStringToTypedArray(data);
|
|
this.debug('>>> length ' + data.length);
|
|
// if necessary, split the *STOMP* frame to send it on many smaller
|
|
// *WebSocket* frames
|
|
while (true) {
|
|
if (data.length > this.maxWebSocketFrameSize) {
|
|
this.ws.send(data.slice(0, this.maxWebSocketFrameSize));
|
|
data = data.slice(this.maxWebSocketFrameSize);
|
|
this.debug('remaining = ' + data.length);
|
|
} else {
|
|
return this.ws.send(data);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Heart-beat negotiation
|
|
|
|
}, {
|
|
key: '_setupHeartbeat',
|
|
value: function _setupHeartbeat(headers) {
|
|
var _this2 = this;
|
|
|
|
if (this.version !== VERSIONS.V1_1 && this.version !== VERSIONS.V1_2) return;
|
|
|
|
// heart-beat header received from the server looks like:
|
|
//
|
|
// heart-beat: sx, sy
|
|
|
|
var _split$map = (headers['heart-beat'] || '0,0').split(',').map(function (v) {
|
|
return parseInt(v, 10);
|
|
}),
|
|
_split$map2 = slicedToArray(_split$map, 2),
|
|
serverOutgoing = _split$map2[0],
|
|
serverIncoming = _split$map2[1];
|
|
|
|
if (!(this.heartbeat.outgoing === 0 || serverIncoming === 0)) {
|
|
var ttl = Math.max(this.heartbeat.outgoing, serverIncoming);
|
|
this.debug('send PING every ' + ttl + 'ms');
|
|
this.pinger = setInterval(function () {
|
|
_this2._wsSend(BYTES.LF);
|
|
_this2.debug('>>> PING');
|
|
}, ttl);
|
|
}
|
|
|
|
if (!(this.heartbeat.incoming === 0 || serverOutgoing === 0)) {
|
|
var _ttl = Math.max(this.heartbeat.incoming, serverOutgoing);
|
|
this.debug('check PONG every ' + _ttl + 'ms');
|
|
this.ponger = setInterval(function () {
|
|
var delta = Date.now() - _this2.serverActivity;
|
|
// We wait twice the TTL to be flexible on window's setInterval calls
|
|
if (delta > _ttl * 2) {
|
|
_this2.debug('did not receive server activity for the last ' + delta + 'ms');
|
|
_this2.ws.close();
|
|
}
|
|
}, _ttl);
|
|
}
|
|
}
|
|
|
|
// parse the arguments number and type to find the headers, connectCallback and
|
|
// (eventually undefined) errorCallback
|
|
|
|
}, {
|
|
key: '_parseConnect',
|
|
value: function _parseConnect() {
|
|
var headers = {},
|
|
connectCallback = void 0,
|
|
errorCallback = void 0;
|
|
|
|
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
|
|
args[_key] = arguments[_key];
|
|
}
|
|
|
|
switch (args.length) {
|
|
case 2:
|
|
headers = args[0];
|
|
connectCallback = args[1];
|
|
|
|
break;
|
|
case 3:
|
|
if (args[1] instanceof Function) {
|
|
headers = args[0];
|
|
connectCallback = args[1];
|
|
errorCallback = args[2];
|
|
} else {
|
|
headers.login = args[0];
|
|
headers.passcode = args[1];
|
|
connectCallback = args[2];
|
|
}
|
|
break;
|
|
case 4:
|
|
headers.login = args[0];
|
|
headers.passcode = args[1];
|
|
connectCallback = args[2];
|
|
errorCallback = args[3];
|
|
|
|
break;
|
|
default:
|
|
headers.login = args[0];
|
|
headers.passcode = args[1];
|
|
connectCallback = args[2];
|
|
errorCallback = args[3];
|
|
headers.host = args[4];
|
|
|
|
}
|
|
|
|
return [headers, connectCallback, errorCallback];
|
|
}
|
|
}]);
|
|
return Client;
|
|
}();
|
|
|
|
// The `webstomp` Object
|
|
var webstomp = {
|
|
Frame: Frame,
|
|
VERSIONS: VERSIONS,
|
|
// This method creates a WebSocket client that is connected to
|
|
// the STOMP server located at the url.
|
|
client: function client(url) {
|
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
|
|
var ws = new WebSocket(url, options.protocols || VERSIONS.supportedProtocols());
|
|
return new Client(ws, options);
|
|
},
|
|
|
|
// This method is an alternative to `webstomp.client()` to let the user
|
|
// specify the WebSocket to use (either a standard HTML5 WebSocket or
|
|
// a similar object).
|
|
over: function over() {
|
|
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
|
|
args[_key] = arguments[_key];
|
|
}
|
|
|
|
return new (Function.prototype.bind.apply(Client, [null].concat(args)))();
|
|
}
|
|
};
|
|
|
|
return webstomp;
|
|
|
|
})));
|