node-red-contrib-redis
Version:
Node Red client for Redis with pub/sub, list, lua scripting and other commands support.
426 lines (394 loc) • 11 kB
JavaScript
module.exports = function (RED) {
"use strict";
const Redis = require("ioredis");
const async = require("async");
let connections = {};
let usedConn = {};
function RedisConfig(n) {
RED.nodes.createNode(this, n);
this.name = n.name;
this.cluster = n.cluster;
if (this.optionsType === "") {
this.options = n.options;
} else {
RED.util.evaluateNodeProperty(n.options, n.optionsType,this,undefined,(err,value) => {
if(!err) {
// Check if value is a string and optionsType is "env"
if (typeof value === 'string' && n.optionsType === "env") {
try {
this.options = JSON.parse(value); // Attempt to parse JSON
} catch (e) {
console.warn("Failed to parse env as JSON string in redis-config node, use plain value:", e);
this.options = value; // Keep the value as is if it's not valid JSON
}
} else {
this.options = value;
}
}
});
}
}
RED.nodes.registerType("redis-config", RedisConfig);
function RedisIn(n) {
RED.nodes.createNode(this, n);
this.server = RED.nodes.getNode(n.server);
this.command = n.command;
this.name = n.name;
this.topic = n.topic;
this.obj = n.obj;
this.timeout = n.timeout;
let node = this;
let client = getConn(this.server, n.id);
let running = true;
node.on("close", async (undeploy, done) => {
node.status({});
disconnect(node.id);
client = null;
running = false;
done();
});
if (node.command === "psubscribe") {
client.on("pmessage", function (pattern, channel, message) {
var payload = null;
try {
if(node.obj){
payload = JSON.parse(message);
}else{
payload = message;
}
} catch (err) {
payload = message;
} finally {
node.send({
pattern: pattern,
topic: channel,
payload: payload,
});
}
});
client[node.command](node.topic, (err, count) => {
node.status({
fill: "green",
shape: "dot",
text: "connected",
});
});
} else if (node.command === "subscribe") {
client.on("message", function (channel, message) {
var payload = null;
try {
if(node.obj){
payload = JSON.parse(message);
}else{
payload = message;
}
} catch (err) {
payload = message;
} finally {
node.send({
topic: channel,
payload: payload,
});
}
});
client[node.command](node.topic, (err, count) => {
node.status({
fill: "green",
shape: "dot",
text: "connected",
});
});
} else {
async.whilst(
(cb) => {
cb(null, running);
},
(cb) => {
client[node.command](node.topic, Number(node.timeout))
.then((data) => {
if (data !== null && data.length == 2) {
var payload = null;
try {
if(node.obj){
payload = JSON.parse(data[1]);
}else{
payload = data[1];
}
} catch (err) {
payload = data[1];
} finally {
node.send({
topic: node.topic,
payload: payload,
});
}
}
cb(null);
})
.catch((e) => {
RED.log.info(e.message);
running = false;
});
},
() => {}
);
}
node.status({
fill: "green",
shape: "dot",
text: "connected",
});
}
RED.nodes.registerType("redis-in", RedisIn);
function RedisOut(n) {
RED.nodes.createNode(this, n);
this.server = RED.nodes.getNode(n.server);
this.command = n.command;
this.name = n.name;
this.topic = n.topic;
this.obj = n.obj;
var node = this;
let client = getConn(this.server, node.server.name);
node.on("close", function (done) {
node.status({});
disconnect( node.server.name);
client = null;
done();
});
node.on("input", function (msg, send, done) {
var topic;
send = send || function() { node.send.apply(node,arguments) }
done = done || function(err) { if(err)node.error(err, msg); }
if (msg.topic !== undefined && msg.topic !== "") {
topic = msg.topic;
} else {
topic = node.topic;
}
if (topic === "") {
done(new Error("Missing topic, please send topic on msg or set Topic on node."));
} else {
try {
if(node.obj){
client[node.command](topic, JSON.stringify(msg.payload));
}else{
client[node.command](topic, msg.payload);
}
done();
} catch (err) {
done(err);
}
}
});
}
RED.nodes.registerType("redis-out", RedisOut);
function RedisCmd(n) {
RED.nodes.createNode(this, n);
this.server = RED.nodes.getNode(n.server);
this.command = n.command;
this.name = n.name;
this.topic = n.topic;
this.params = n.params;
var node = this;
this.block = n.block || false;
let id = this.block ? n.id : this.server.name;
let client = getConn(this.server, id);
node.on("close", function (done) {
node.status({});
disconnect(id);
client = null;
done();
});
node.on("input", function (msg, send, done) {
let topic = undefined;
send = send || function() { node.send.apply(node,arguments) }
done = done || function(err) { if(err)node.error(err, msg); }
if (msg.topic !== undefined && msg.topic !== "") {
topic = msg.topic;
} else if (node.topic && node.topic !== "") {
try {
topic = node.topic;
} catch (e) {
topic = undefined;
}
}
let payload = undefined;
if (msg.payload) {
let type = typeof msg.payload;
switch (type) {
case "string":
if (msg.payload.length > 0) {
payload = msg.payload;
}
break;
case "object":
if (Array.isArray(msg.payload)) {
if (msg.payload.length > 0) {
payload = msg.payload;
}
break;
}
if (Object.keys(msg.payload).length > 0) {
payload = msg.payload;
}
break;
}
} else if (
node.params &&
node.params !== "" &&
node.params !== "[]" &&
node.params !== "{}"
) {
try {
payload = JSON.parse(node.params);
} catch (e) {
payload = undefined;
}
}
let response = function (err, res) {
if (err) {
done(err);
} else {
msg.payload = res;
send(msg);
done();
}
};
if (!payload) {
payload = topic;
topic = undefined;
}
if (topic) {
client.call(node.command, topic, payload, response);
} else if (payload) {
client.call(node.command, payload, response);
} else {
client.call(node.command, response);
}
});
}
RED.nodes.registerType("redis-command", RedisCmd);
function RedisLua(n) {
RED.nodes.createNode(this, n);
this.server = RED.nodes.getNode(n.server);
this.func = n.func;
this.name = n.name;
this.keyval = n.keyval;
this.stored = n.stored;
this.sha1 = "";
this.command = "eval";
var node = this;
this.block = n.block || false;
let id = this.block ? n.id : n.server.name;
let client = getConn(this.server, id);
node.on("close", function (done) {
node.status({});
disconnect(id);
client = null;
done();
});
if (node.stored) {
client.script("load", node.func, function (err, res) {
if (err) {
node.status({
fill: "red",
shape: "dot",
text: "script not loaded",
});
} else {
node.status({
fill: "green",
shape: "dot",
text: "script loaded",
});
node.sha1 = res;
}
});
}
node.on("input", function (msg, send, done) {
send = send || function() { node.send.apply(node,arguments) }
done = done || function(err) { if(err)node.error(err, msg); }
if (node.keyval > 0 && !Array.isArray(msg.payload)) {
throw Error("Payload is not Array");
}
var args = null;
if (node.stored) {
node.command = "evalsha";
args = [node.sha1, node.keyval].concat(msg.payload);
} else {
args = [node.func, node.keyval].concat(msg.payload);
}
client[node.command](args, function (err, res) {
if (err) {
done(err);
} else {
msg.payload = res;
send(msg);
done();
}
});
});
}
RED.nodes.registerType("redis-lua-script", RedisLua);
function RedisInstance(n) {
RED.nodes.createNode(this, n);
this.server = RED.nodes.getNode(n.server);
this.location = n.location;
this.name = n.name;
this.topic = n.topic;
let id = n.id;
var node = this;
let client = getConn(this.server, id);
this.context()[node.location].set(node.topic, client);
node.status({
fill: "green",
shape: "dot",
text: "ready",
});
node.on("close", function (done) {
node.status({});
this.context()[node.location].set(node.topic, null);
disconnect(id);
client = null;
done();
});
}
RED.nodes.registerType("redis-instance", RedisInstance);
function getConn(config, id) {
if (connections[id]) {
usedConn[id]++;
return connections[id];
}
let options = config.options;
if (!options) {
return config.error(
"Missing options in the redis config - Are you upgrading from old version?",
null
);
}
try {
if (config.cluster) {
connections[id] = new Redis.Cluster(options);
} else {
connections[id] = new Redis(options);
}
connections[id].on("error", (e) => {
config.error(e, null);
});
if (usedConn[id] === undefined) {
usedConn[id] = 1;
}
return connections[id];
} catch (e) {
config.error(e.message, null);
}
}
function disconnect(id) {
if (usedConn[id] !== undefined) {
usedConn[id]--;
}
if (connections[id] && usedConn[id] <= 0) {
connections[id].disconnect();
delete connections[id];
}
}
};