use-stomp
Version:
react provider, class decorator, and a hook for websockets using the stomp protocol
587 lines (525 loc) • 19.2 kB
text/typescript
import {w3cwebsocket as WebSocketClass} from 'websocket';
const __hasProp = {}.hasOwnProperty;
// const __slice = [].slice;
const Byte = {
LF: '\x0A',
NULL: '\x00'
};
const Frame = (function () {
function Frame(command, headers, body) {
this.command = command;
this.headers = headers != null ? headers : {};
this.body = body != null ? body : '';
}
Frame.prototype.toString = function () {
const lines = [this.command];
const skipContentLength = this.headers['content-length'] === false;
if (skipContentLength) {
delete this.headers['content-length'];
}
const _ref = this.headers;
for (let name in _ref) {
if (!__hasProp.call(_ref, name)) continue;
const value = _ref[name];
lines.push('' + name + ':' + value);
}
if (this.body && !skipContentLength) {
lines.push('content-length:' + Frame.sizeOfUTF8(this.body));
}
lines.push(Byte.LF + this.body);
return lines.join(Byte.LF);
};
Frame.sizeOfUTF8 = function (s) {
if (s) {
return encodeURI(s).match(/%..|./g).length;
} else {
return 0;
}
};
const unmarshallSingle = function (data) {
let i;
let _j;
let _ref1;
let chr;
let idx;
let len;
let line;
const divider = data.search(RegExp('' + Byte.LF + Byte.LF));
const headerLines = data.substring(0, divider).split(Byte.LF);
const command = headerLines.shift();
const headers = {};
const trim = function (str) {
return str.replace(/^\s+|\s+$/g, '');
};
const _ref = headerLines.reverse();
for (let _i = 0, _len = _ref.length; _i < _len; _i++) {
line = _ref[_i];
idx = line.indexOf(':');
headers[trim(line.substring(0, idx))] = trim(
line.substring(idx + 1)
);
}
let body = '';
const start = divider + 2;
if (headers['content-length']) {
len = parseInt(headers['content-length']);
body = ('' + data).substring(start, start + len);
} else {
chr = null;
for (
i = _j = start, _ref1 = data.length;
start <= _ref1 ? _j < _ref1 : _j > _ref1;
i = start <= _ref1 ? ++_j : --_j
) {
chr = data.charAt(i);
if (chr === Byte.NULL) {
break;
}
body += chr;
}
}
return new Frame(command, headers, body);
};
Frame.unmarshall = function (datas) {
return (function () {
const _ref = datas.split(RegExp('' + Byte.NULL + Byte.LF + '*'));
const _results = [];
for (let _i = 0, _len = _ref.length; _i < _len; _i++) {
const data = _ref[_i];
if ((data != null ? data.length : void 0) > 0) {
_results.push(unmarshallSingle(data));
}
}
return _results;
})();
};
Frame.marshall = function (command, headers, body) {
const frame = new Frame(command, headers, body);
return frame.toString() + Byte.NULL;
};
return Frame;
})();
const Client = (function () {
var now;
function Client(ws, debug = false) {
this.ws = ws;
this.ws.binaryType = 'arraybuffer';
this.counter = 0;
this.debugEnabled = debug || false;
this.connected = false;
this.heartbeat = {
outgoing: 10000,
incoming: 10000
};
this.maxWebSocketFrameSize = 16 * 1024;
this.subscriptions = {};
}
Client.prototype.debug = function (message) {
let _ref;
if (!this.debugEnabled) {
return;
}
return typeof (window || self) !== 'undefined' &&
(window || self) !== null
? (_ref = (window || self).console) != null
? _ref.log(message)
: void 0
: void 0;
};
now = function () {
if (Date.now) {
return Date.now();
} else {
return new Date().valueOf;
}
};
Client.prototype._transmit = function (command, headers, body) {
let out = Frame.marshall(command, headers, body);
if (typeof this.debug === 'function') {
this.debug('>>> ' + out);
}
while (true) {
if (out.length > this.maxWebSocketFrameSize) {
this.ws.send(out.substring(0, this.maxWebSocketFrameSize));
out = out.substring(this.maxWebSocketFrameSize);
if (typeof this.debug === 'function') {
this.debug('remaining = ' + out.length);
}
} else {
return this.ws.send(out);
}
}
};
Client.prototype._setupHeartbeat = function (headers) {
var serverIncoming, serverOutgoing, ttl, v, _ref, _ref1;
if (
(_ref = headers.version) !== Stomp.VERSIONS.V1_1 &&
_ref !== Stomp.VERSIONS.V1_2
) {
return;
}
(_ref1 = (function () {
let _i, _len, _ref1, _results;
_ref1 = headers['heart-beat'].split(',');
_results = [];
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
v = _ref1[_i];
_results.push(parseInt(v));
}
return _results;
})()),
(serverOutgoing = _ref1[0]),
(serverIncoming = _ref1[1]);
if (!(this.heartbeat.outgoing === 0 || serverIncoming === 0)) {
ttl = Math.max(this.heartbeat.outgoing, serverIncoming);
if (typeof this.debug === 'function') {
this.debug('send PING every ' + ttl + 'ms');
}
this.pinger = Stomp.setInterval(
ttl,
(function (_this) {
return function () {
_this.ws.send(Byte.LF);
return typeof _this.debug === 'function'
? _this.debug('>>> PING')
: void 0;
};
})(this)
);
}
if (!(this.heartbeat.incoming === 0 || serverOutgoing === 0)) {
ttl = Math.max(this.heartbeat.incoming, serverOutgoing);
if (typeof this.debug === 'function') {
this.debug('check PONG every ' + ttl + 'ms');
}
return (this.ponger = Stomp.setInterval(
ttl,
(function (_this) {
return function () {
var delta;
delta = now() - _this.serverActivity;
if (delta > ttl * 2) {
if (typeof _this.debug === 'function') {
_this.debug(
'did not receive server activity for the last ' +
delta +
'ms'
);
}
return _this.ws.close();
}
};
})(this)
));
}
};
Client.prototype.headers = null;
Client.prototype.connectCallback = () => {};
Client.prototype.disconnectCallback = () => {};
Client.prototype.errorCallback = () => {};
Client.prototype.connect = function (
headers,
connectCallback,
disconnectCallback,
errorCallback
) {
if (typeof this.debug === 'function') {
this.debug('Opening Web Socket...');
}
this.headers = headers;
this.errorCallback = errorCallback;
this.connectCallback = connectCallback;
this.disconnectCallback = disconnectCallback;
this.ws.onmessage = (function (_this) {
return function (evt) {
var arr,
c,
client,
data,
frame,
messageID,
onreceive,
subscription,
_i,
_len,
_ref,
_results;
data =
typeof ArrayBuffer !== 'undefined' &&
evt.data instanceof ArrayBuffer
? ((arr = new Uint8Array(evt.data)),
typeof _this.debug === 'function'
? _this.debug(
'--- got data length: ' + arr.length
)
: void 0,
(function () {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = arr.length; _i < _len; _i++) {
c = arr[_i];
_results.push(String.fromCharCode(c));
}
return _results;
})().join(''))
: evt.data;
_this.serverActivity = now();
if (data === Byte.LF) {
if (typeof _this.debug === 'function') {
_this.debug('<<< PONG');
}
return;
}
if (typeof _this.debug === 'function') {
_this.debug('<<< ' + data);
}
_ref = Frame.unmarshall(data);
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
frame = _ref[_i];
switch (frame.command) {
case 'CONNECTED':
if (typeof _this.debug === 'function') {
_this.debug(
'connected to server ' +
frame.headers.server
);
}
_this.connected = true;
_this._setupHeartbeat(frame.headers);
_results.push(
typeof _this.connectCallback === 'function'
? _this.connectCallback(frame)
: void 0
);
break;
case 'MESSAGE':
subscription = frame.headers.subscription;
onreceive =
_this.subscriptions[subscription] ||
_this.onreceive;
if (onreceive) {
client = _this;
messageID = frame.headers['message-id'];
frame.ack = function (headers) {
if (headers == null) {
headers = {};
}
return client.ack(
messageID,
subscription,
headers
);
};
frame.nack = function (headers) {
if (headers == null) {
headers = {};
}
return client.nack(
messageID,
subscription,
headers
);
};
_results.push(onreceive(frame));
} else {
_results.push(
typeof _this.debug === 'function'
? _this.debug(
'Unhandled received MESSAGE: ' +
frame
)
: void 0
);
}
break;
case 'RECEIPT':
_results.push(
typeof _this.onreceipt === 'function'
? _this.onreceipt(frame)
: void 0
);
break;
case 'ERROR':
_results.push(
typeof _this.errorCallback === 'function'
? _this.errorCallback(frame)
: void 0
);
break;
default:
_results.push(
typeof _this.debug === 'function'
? _this.debug('Unhandled frame: ' + frame)
: void 0
);
}
}
return _results;
};
})(this);
this.ws.onclose = (function (_this) {
return function () {
const msg = 'Whoops! Lost connection to ' + _this.ws.url;
if (typeof _this.debug === 'function') {
_this.debug(msg);
}
_this._cleanUp();
return typeof disconnectCallback === 'function'
? disconnectCallback()
: void 0;
};
})(this);
return (this.ws.onopen = (function (_this) {
return function () {
if (typeof _this.debug === 'function') {
_this.debug('Web Socket Opened...');
}
_this.headers[
'accept-version'
] = Stomp.VERSIONS.supportedVersions();
_this.headers['heart-beat'] = [
_this.heartbeat.outgoing,
_this.heartbeat.incoming
].join(',');
return _this._transmit('CONNECT', _this.headers);
};
})(this));
};
Client.prototype.disconnect = function (callback?: () => void) {
if (this.headers == null) {
this.headers = {};
}
this._transmit('DISCONNECT', this.headers);
this.ws.onclose = null;
this.ws.close();
this._cleanUp();
if (callback) {
callback();
}
if (typeof this.disconnectCallback === 'function') {
this.disconnectCallback();
}
this.connectCallback = function () {};
this.disconnectCallback = function () {};
this.errorCallback = function () {};
};
Client.prototype._cleanUp = function () {
this.connected = false;
if (this.pinger) {
Stomp.clearInterval(this.pinger);
}
if (this.ponger) {
return Stomp.clearInterval(this.ponger);
}
};
Client.prototype.send = function (destination, headers, body) {
if (headers == null) {
headers = {};
}
if (body == null) {
body = '';
}
headers.destination = destination;
return this._transmit('SEND', headers, body);
};
Client.prototype.subscribe = function (destination, callback, headers) {
var client;
if (headers == null) {
headers = {};
}
if (!headers.id) {
headers.id = 'sub-' + this.counter++;
}
headers.destination = destination;
this.subscriptions[headers.id] = callback;
this._transmit('SUBSCRIBE', headers);
client = this;
return {
id: headers.id,
unsubscribe: function () {
return client.unsubscribe(headers.id);
}
};
};
Client.prototype.unsubscribe = function (id) {
delete this.subscriptions[id];
return this._transmit('UNSUBSCRIBE', {
id: id
});
};
Client.prototype.begin = function (transaction) {
var client, txid;
txid = transaction || 'tx-' + this.counter++;
this._transmit('BEGIN', {
transaction: txid
});
client = this;
return {
id: txid,
commit: function () {
return client.commit(txid);
},
abort: function () {
return client.abort(txid);
}
};
};
Client.prototype.commit = function (transaction) {
return this._transmit('COMMIT', {
transaction: transaction
});
};
Client.prototype.abort = function (transaction) {
return this._transmit('ABORT', {
transaction: transaction
});
};
Client.prototype.ack = function (messageID, subscription, headers) {
if (headers == null) {
headers = {};
}
headers['message-id'] = messageID;
headers.subscription = subscription;
return this._transmit('ACK', headers);
};
Client.prototype.nack = function (messageID, subscription, headers) {
if (headers == null) {
headers = {};
}
headers['message-id'] = messageID;
headers.subscription = subscription;
return this._transmit('NACK', headers);
};
return Client;
})();
const Stomp = {
debug: false,
VERSIONS: {
V1_0: '1.0',
V1_1: '1.1',
V1_2: '1.2',
supportedVersions: function () {
return '1.1,1.0';
}
},
client: function (url, protocols) {
if (protocols == null) {
protocols = ['v10.stomp', 'v11.stomp'];
}
const klass = WebSocketClass || WebSocket;
const ws = new klass(url, protocols);
return new Client(ws);
},
over: function (ws, debug = false) {
return new Client(ws, debug);
},
setInterval: function (interval, f) {
return (self || window).setInterval(f, interval);
},
clearInterval: function (id) {
return (self || window).clearInterval(id);
},
Frame
};
export default Stomp;