@frankvdb/node-red-contrib-amqp
Version:
RabbitMQ nodes for node-red
189 lines (188 loc) • 8.36 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const constants_1 = require("../constants");
const types_1 = require("../types");
const Amqp_1 = require("../Amqp");
module.exports = function (RED) {
function AmqpOut(config) {
let reconnectTimeout;
let reconnect = null;
let connection = null;
let channel = null;
let me = this;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
RED.nodes.createNode(this, config);
this.status(constants_1.NODE_STATUS.Disconnected);
const configAmqp = config;
const amqp = new Amqp_1.default(RED, this, configAmqp);
const reconnectOnError = configAmqp.reconnectOnError;
// handle input event;
const inputListener = async (msg, _, done) => {
var _a;
const { payload, routingKey, properties: msgProperties } = msg;
const { exchangeRoutingKey, exchangeRoutingKeyType, amqpProperties, } = config;
// message properties override config properties
let properties;
try {
properties = Object.assign(Object.assign({}, JSON.parse(amqpProperties)), msgProperties);
}
catch (e) {
properties = msgProperties;
}
switch (exchangeRoutingKeyType) {
case 'msg':
case 'flow':
case 'global':
amqp.setRoutingKey(RED.util.evaluateNodeProperty(exchangeRoutingKey, exchangeRoutingKeyType, this, msg));
break;
case 'jsonata': {
const expr = RED.util.prepareJSONataExpression(exchangeRoutingKey, this);
try {
const result = await new Promise((resolve, reject) => {
RED.util.evaluateJSONataExpression(expr, msg, (err, value) => {
if (err) {
reject(err);
}
else {
resolve(value);
}
});
});
if (typeof result !== 'string') {
this.warn(`Routing key JSONata expression returned ${typeof result}; coercing to string`);
}
amqp.setRoutingKey(String(result));
}
catch (err) {
this.error(`Failed to evaluate JSONata expression: ${err}`);
}
break;
}
case 'str':
default:
if (routingKey) {
// if incoming payload contains a routingKey value
// override our string value with it.
// Superfluous (and possibly confusing) at this point
// but keeping it to retain backwards compatibility
amqp.setRoutingKey(routingKey);
}
break;
}
if (!!((_a = properties === null || properties === void 0 ? void 0 : properties.headers) === null || _a === void 0 ? void 0 : _a.doNotStringifyPayload)) {
amqp.publish(payload, properties);
}
else {
amqp.publish(JSON.stringify(payload), properties);
}
done && done();
};
this.on('input', inputListener);
// When the node is re-deployed
this.on('close', async (done) => {
clearTimeout(reconnectTimeout);
await amqp.close();
done && done();
});
async function initializeNode(nodeIns) {
reconnect = async () => {
// check the channel and clear all the event listener
try {
if (channel && channel.removeAllListeners) {
channel.removeAllListeners();
channel.close()
.catch(err => {
// produce very lengthy messages...
//me.error(`Error closing channel: ${JSON.stringify(err)}`);
});
//channel = null;
}
}
catch (error) {
// catch and suppress error
me.error('Error occurred:', error);
}
channel = null;
try {
// check the connection and clear all the event listener
if (connection && connection.removeAllListeners) {
connection.removeAllListeners();
connection.close()
.catch(err => {
me.error('Error closing connection:', err);
});
//connection = null;
}
}
catch (error) {
// catch and suppress error
me.error('Error occurred:', error);
}
connection = null;
// always clear timer before set it;
clearTimeout(reconnectTimeout);
reconnectTimeout = setTimeout(() => {
try {
initializeNode(nodeIns);
}
catch (e) {
reconnect();
}
}, 2000);
};
try {
connection = await amqp.connect();
// istanbul ignore else
if (connection) {
channel = await amqp.initialize();
// When the server goes down
connection.on('close', async (e) => {
e && (await reconnect());
});
// When the connection goes down
connection.on('error', async (e) => {
reconnectOnError && (await reconnect());
nodeIns.error(`Connection error ${e}`, { payload: { error: e, location: types_1.ErrorLocationEnum.ConnectionErrorEvent } });
});
// When the channel goes down
channel.on('close', async () => {
await reconnect();
});
// When the channel error occur
channel.on('error', async (e) => {
reconnectOnError && (await reconnect());
nodeIns.error(`Channel error ${e}`, { payload: { error: e, location: types_1.ErrorLocationEnum.ChannelErrorEvent } });
});
nodeIns.status(constants_1.NODE_STATUS.Connected);
}
}
catch (e) {
if (e.code === types_1.ErrorType.InvalidLogin ||
/ACCESS_REFUSED/i.test(e.message || '')) {
nodeIns.status(constants_1.NODE_STATUS.Invalid);
nodeIns.error(`AmqpOut() Could not connect to broker ${e}`, {
payload: { error: e, location: types_1.ErrorLocationEnum.ConnectError },
});
}
else if (e.code === types_1.ErrorType.ConnectionRefused ||
e.code === types_1.ErrorType.DnsResolve ||
e.code === types_1.ErrorType.HostNotFound ||
e.isOperational) {
reconnectOnError && (await reconnect());
}
else {
nodeIns.status(constants_1.NODE_STATUS.Error);
nodeIns.error(`AmqpOut() ${e}`, {
payload: { error: e, location: types_1.ErrorLocationEnum.ConnectError },
});
}
}
}
// call
initializeNode(this);
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
RED.nodes.registerType(types_1.NodeType.AmqpOut, AmqpOut);
};