node-red-contrib-redis-variable 1.3.1 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -0
- package/examples/example.json +0 -0
- package/package.json +1 -1
- package/redis-variable-config.html +0 -0
- package/redis-variable-config.js +48 -4
- package/redis-variable.html +8 -1
- package/redis-variable.js +91 -45
package/README.md
CHANGED
|
File without changes
|
package/examples/example.json
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-redis-variable",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "A comprehensive Node-RED node for Redis operations with universal payload-based configuration, automatic JSON handling, SSL/TLS support, and advanced pattern matching with pagination",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node-red",
|
|
File without changes
|
package/redis-variable-config.js
CHANGED
|
@@ -382,14 +382,58 @@ module.exports = function (RED) {
|
|
|
382
382
|
maxRetriesPerRequest: 0
|
|
383
383
|
};
|
|
384
384
|
|
|
385
|
+
// Create Redis client
|
|
385
386
|
// Create Redis client
|
|
386
387
|
let client;
|
|
387
388
|
if (this.cluster) {
|
|
388
|
-
//
|
|
389
|
-
const
|
|
390
|
-
|
|
389
|
+
// Prepare cluster node(s)
|
|
390
|
+
const host = options.host;
|
|
391
|
+
const port = parseInt(options.port);
|
|
392
|
+
|
|
393
|
+
// AWS ElastiCache cluster connection (TLS-safe)
|
|
394
|
+
const clusterNodes = [{ host, port }];
|
|
395
|
+
|
|
396
|
+
const clusterOptions = {
|
|
397
|
+
scaleReads: "slave",
|
|
398
|
+
enableReadyCheck: false,
|
|
399
|
+
slotsRefreshTimeout: 5000,
|
|
400
|
+
redisOptions: {
|
|
401
|
+
username: options.username || undefined,
|
|
402
|
+
password: options.password || undefined,
|
|
403
|
+
db: options.db || 0,
|
|
404
|
+
connectTimeout: 5000,
|
|
405
|
+
maxRetriesPerRequest: 3,
|
|
406
|
+
retryStrategy: (times) => Math.min(times * 200, 2000),
|
|
407
|
+
tls: this.enableTLS
|
|
408
|
+
? {
|
|
409
|
+
servername: host,
|
|
410
|
+
rejectUnauthorized: this.tlsRejectUnauthorized,
|
|
411
|
+
ca: this.credentials?.tlsCa || undefined,
|
|
412
|
+
cert: this.credentials?.tlsCert || undefined,
|
|
413
|
+
key: this.credentials?.tlsKey || undefined,
|
|
414
|
+
}
|
|
415
|
+
: undefined,
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
client = new Redis.Cluster(clusterNodes, clusterOptions);
|
|
391
420
|
} else {
|
|
392
|
-
|
|
421
|
+
// Non-cluster (single node)
|
|
422
|
+
client = new Redis({
|
|
423
|
+
...options,
|
|
424
|
+
enableReadyCheck: false,
|
|
425
|
+
connectTimeout: 5000,
|
|
426
|
+
maxRetriesPerRequest: 3,
|
|
427
|
+
tls: this.enableTLS
|
|
428
|
+
? {
|
|
429
|
+
servername: options.host,
|
|
430
|
+
rejectUnauthorized: this.tlsRejectUnauthorized,
|
|
431
|
+
ca: this.credentials?.tlsCa || undefined,
|
|
432
|
+
cert: this.credentials?.tlsCert || undefined,
|
|
433
|
+
key: this.credentials?.tlsKey || undefined,
|
|
434
|
+
}
|
|
435
|
+
: undefined,
|
|
436
|
+
});
|
|
393
437
|
}
|
|
394
438
|
|
|
395
439
|
// Track error state to prevent spam
|
package/redis-variable.html
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
defaults: {
|
|
6
6
|
name: { value: "" },
|
|
7
7
|
redisConfig: { value: "", type: "redis-variable-config" },
|
|
8
|
-
operation: { value: "get" }
|
|
8
|
+
operation: { value: "get" },
|
|
9
|
+
topic: { value: "" }
|
|
9
10
|
},
|
|
10
11
|
inputs: 1,
|
|
11
12
|
outputs: 1,
|
|
@@ -62,9 +63,15 @@
|
|
|
62
63
|
</optgroup>
|
|
63
64
|
<optgroup label="Pub/Sub Operations">
|
|
64
65
|
<option value="publish">PUBLISH - Publish message</option>
|
|
66
|
+
<option value="subscribe">SUBSCRIBE - Subscribe to channel</option>
|
|
67
|
+
<option value="psubscribe">PSUBSCRIBE - Pattern subscribe</option>
|
|
65
68
|
</optgroup>
|
|
66
69
|
</select>
|
|
67
70
|
</div>
|
|
71
|
+
<div class="form-row">
|
|
72
|
+
<label for="node-input-topic"><i class="fa fa-hashtag"></i> Channel/Pattern</label>
|
|
73
|
+
<input type="text" id="node-input-topic" placeholder="notifications or news.*">
|
|
74
|
+
</div>
|
|
68
75
|
</script>
|
|
69
76
|
|
|
70
77
|
<script type="text/x-red" data-help-name="redis-variable">
|
package/redis-variable.js
CHANGED
|
@@ -14,6 +14,7 @@ module.exports = function (RED) {
|
|
|
14
14
|
this.stored = config.stored || false;
|
|
15
15
|
this.params = config.params;
|
|
16
16
|
this.location = config.location || 'flow';
|
|
17
|
+
this.topic = config.topic || "";
|
|
17
18
|
this.sha1 = "";
|
|
18
19
|
|
|
19
20
|
// Save redisConfig once in the constructor
|
|
@@ -75,56 +76,89 @@ module.exports = function (RED) {
|
|
|
75
76
|
// Subscription operations (subscribe, psubscribe)
|
|
76
77
|
function handleSubscription() {
|
|
77
78
|
try {
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
79
|
+
if (!redisConfig) {
|
|
80
|
+
node.status({ fill: "yellow", shape: "ring", text: "no config" });
|
|
81
|
+
node.warn("Redis configuration not found for subscription.");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!node.topic) {
|
|
86
|
+
node.status({ fill: "red", shape: "dot", text: "missing channel" });
|
|
87
|
+
node.error("Missing channel/pattern for subscription (topic)");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
client = redisConfig.getClient(null, node, node.id);
|
|
92
|
+
if (!client) {
|
|
93
|
+
node.status({ fill: "yellow", shape: "ring", text: "no config" });
|
|
94
|
+
node.warn("Redis configuration not available. Subscription skipped.");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const setup = async () => {
|
|
99
|
+
try {
|
|
100
|
+
if (client.status !== 'ready' && client.status !== 'connect') {
|
|
101
|
+
await client.connect();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const topics = (node.topic || "").split(/[\s,]+/).filter(Boolean);
|
|
105
|
+
|
|
106
|
+
if (node.operation === "psubscribe") {
|
|
107
|
+
client.on("pmessage", function (pattern, channel, message) {
|
|
108
|
+
var payload = smartParse(message);
|
|
109
|
+
node.send({
|
|
110
|
+
pattern: pattern,
|
|
111
|
+
topic: channel,
|
|
112
|
+
payload: payload,
|
|
113
|
+
});
|
|
94
114
|
});
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
115
|
+
client.psubscribe(...topics, (err) => {
|
|
116
|
+
if (err) {
|
|
117
|
+
// Fallback: if server doesn't support PSUBSCRIBE and no wildcard is used, try SUBSCRIBE
|
|
118
|
+
if (err.message && err.message.toLowerCase().includes('unknown command')) {
|
|
119
|
+
const hasWildcard = topics.some(t => t.includes('*'));
|
|
120
|
+
if (!hasWildcard) {
|
|
121
|
+
client.subscribe(...topics, (subErr) => {
|
|
122
|
+
if (subErr) {
|
|
123
|
+
node.error(subErr.message);
|
|
124
|
+
node.status({ fill: "red", shape: "dot", text: "error" });
|
|
125
|
+
} else {
|
|
126
|
+
node.status({ fill: "green", shape: "dot", text: "connected" });
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
node.error(err.message);
|
|
133
|
+
node.status({ fill: "red", shape: "dot", text: "error" });
|
|
134
|
+
} else {
|
|
135
|
+
node.status({ fill: "green", shape: "dot", text: "connected" });
|
|
136
|
+
}
|
|
100
137
|
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
payload: payload,
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
client[node.operation](node.topic, (err, count) => {
|
|
112
|
-
if (err) {
|
|
113
|
-
node.error(err.message);
|
|
114
|
-
node.status({
|
|
115
|
-
fill: "red",
|
|
116
|
-
shape: "dot",
|
|
117
|
-
text: "error",
|
|
138
|
+
} else if (node.operation === "subscribe") {
|
|
139
|
+
client.on("message", function (channel, message) {
|
|
140
|
+
var payload = smartParse(message);
|
|
141
|
+
node.send({
|
|
142
|
+
topic: channel,
|
|
143
|
+
payload: payload,
|
|
144
|
+
});
|
|
118
145
|
});
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
146
|
+
client.subscribe(...topics, (err) => {
|
|
147
|
+
if (err) {
|
|
148
|
+
node.error(err.message);
|
|
149
|
+
node.status({ fill: "red", shape: "dot", text: "error" });
|
|
150
|
+
} else {
|
|
151
|
+
node.status({ fill: "green", shape: "dot", text: "connected" });
|
|
152
|
+
}
|
|
124
153
|
});
|
|
125
154
|
}
|
|
126
|
-
})
|
|
127
|
-
|
|
155
|
+
} catch (e) {
|
|
156
|
+
node.error(`Subscription setup failed: ${e.message}`);
|
|
157
|
+
node.status({ fill: "red", shape: "dot", text: "error" });
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
setup();
|
|
128
162
|
} catch (error) {
|
|
129
163
|
node.error(`Subscription error: ${error.message}`);
|
|
130
164
|
node.status({
|
|
@@ -738,6 +772,18 @@ module.exports = function (RED) {
|
|
|
738
772
|
}
|
|
739
773
|
}
|
|
740
774
|
|
|
775
|
+
if (client) {
|
|
776
|
+
try {
|
|
777
|
+
if (node.operation === 'subscribe' && node.topic) {
|
|
778
|
+
await client.unsubscribe(node.topic);
|
|
779
|
+
} else if (node.operation === 'psubscribe' && node.topic) {
|
|
780
|
+
await client.punsubscribe(node.topic);
|
|
781
|
+
}
|
|
782
|
+
} catch (e) {
|
|
783
|
+
// ignore
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
741
787
|
if (redisConfig) {
|
|
742
788
|
const nodeId = node.block ? node.id : redisConfig.id;
|
|
743
789
|
redisConfig.disconnect(nodeId);
|