stompit
Version:
STOMP client library for node.js
346 lines (233 loc) • 7.36 kB
JavaScript
/*jslint node: true, indent: 2, unused: true, maxlen: 80, camelcase: true, esversion: 9 */
const Transaction = require('./channel/Transaction');
const { createReadableStream } = require('./util');
const { EventEmitter } = require('events');
class Channel extends EventEmitter {
constructor(connectFailover, options = {}) {
super();
options = {
alwaysConnected: false,
recoverAfterApplicationError: false,
...options
};
this._client = null;
this._failover = connectFailover;
this._connecting = false;
this._closed = false;
this._transmissions = [];
this._idle = false;
this._blockIdle = false;
this._alwaysConnected = options.alwaysConnected;
this._recoverAfterApplicationError = options.recoverAfterApplicationError;
if (this._alwaysConnected) {
this._connect();
}
this._checkIdle = Channel.prototype._checkIdle.bind(this);
}
_connect() {
if (this._connecting) {
return;
}
this._connecting = true;
this._connector = this._failover.connect((error, client, reconnect) => {
this._connecting = false;
if (error) {
this._abort(error);
return;
}
client.on('error', (error) => {
if ( !this._recoverAfterApplicationError &&
error.isApplicationError && error.isApplicationError()) {
this._abort(error);
return;
}
this._client = null;
this._connecting = true;
reconnect();
});
this._client = client;
this._retransmit();
});
}
_disconnect() {
const shouldRemainConnected = this._transmissions.length > 0 ||
(this._alwaysConnected && !this._closed);
if (this._connecting && this._connector && !shouldRemainConnected) {
this._connector.abort();
}
if (this._client === null || shouldRemainConnected) {
// Ignore disconnect request
return;
}
this._client.disconnect();
this._client = null;
}
_retransmit() {
if (this._closed) {
if (this._transmissions.length > 0) {
this._abort(new Error('channel is closed'));
}
this._disconnect();
return;
}
const client = this._client;
if (client === null) {
this._connect();
return;
}
for (let i = 0; i < this._transmissions.length; i++) {
const transmit = this._transmissions[i];
transmit(null, client, this._createCompletionCallback(transmit));
}
}
_transmit(transmit) {
if (this._closed) {
transmit(new Error('channel is closed'));
return;
}
this._transmissions.push(transmit);
this._idle = false;
if (this._client === null) {
this._connect();
// The transmit function will be called from the _retransmit method
// once the client is connected
return;
}
transmit(null, this._client, this._createCompletionCallback(transmit));
}
_createCompletionCallback(transmit) {
return this._removeTransmission.bind(this, transmit);
}
_checkIdle() {
if (this._blockIdle || this._transmissions.length > 0 || this._idle) {
return;
}
this._idle = true;
this.emit('idle');
this._disconnect();
}
_removeTransmission(transmission) {
for (let i = 0; i < this._transmissions.length; i++) {
if (this._transmissions[i] === transmission) {
this._transmissions.splice(i, 1);
process.nextTick(this._checkIdle);
return;
}
}
}
_abort(error) {
if (error) {
for (let i = 0; i < this._transmissions.length; i++) {
this._transmissions[i](error, null, function noop(){});
}
}
this._transmissions = [];
}
close(error) {
this._abort(error);
this._closed = true;
this._disconnect();
}
send(headers, body, callback) {
if(typeof callback !== 'function') {
callback = function noop() {};
}
this._transmit(function(error, client, complete) {
if (error) {
callback(error);
return;
}
const onReceipt = function() {
complete();
callback(null);
};
const output = client.send(headers, {onReceipt: onReceipt});
createReadableStream(body).pipe(output);
});
return this;
}
_subscribe(constructor, onMessageCallback) {
const noop = function() {};
let completionCallback = noop;
let unsubscribe = noop;
let cancelled = false;
const cancel = function() {
if (cancelled) {
return;
}
cancelled = true;
onMessageCallback = noop;
unsubscribe();
completionCallback();
};
const channelSubscription = {
cancel: cancel,
unsubscribe: cancel
};
this._transmit(function(error, client, complete) {
if (cancelled) {
complete();
return;
}
if (error) {
onMessageCallback(error);
return;
}
completionCallback = complete;
const subscription = constructor(client, function(subError, message) {
if (subError) {
// Do nothing here and let the channel reconnect
return;
}
onMessageCallback(null, message, channelSubscription);
});
unsubscribe = subscription.unsubscribe.bind(subscription);
});
return channelSubscription;
}
subscribe(headers, onMessageCallback) {
return this._subscribe(function(client, onMessageCallback) {
return client.subscribe(headers, onMessageCallback);
}, onMessageCallback);
}
setImplicitSubscription(id, ack, msgListener) {
return this._subscribe(function(client, msgListener) {
return client.setImplicitSubscription(id, ack, msgListener);
}, msgListener);
}
ack() {
if (this._client === null) {
return;
}
return this._client.ack.apply(this._client, arguments);
}
nack() {
if (this._client === null) {
return;
}
return this._client.nack.apply(this._client, arguments);
}
begin(headers) {
const transaction = new Transaction(this);
this._transmit(function(error, client, complete) {
if(error) {
return;
}
transaction._completes = [complete];
transaction._transaction = client.begin(headers);
});
return transaction;
}
isEmpty() {
return this._transmissions.length === 0;
}
lock() {
this._idle = false;
this._blockIdle = true;
}
unlock() {
this._blockIdle = false;
this._checkIdle();
}
}
module.exports = Channel;