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 CHANGED
File without changes
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.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
@@ -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
- // For cluster mode, options should be an array of nodes
389
- const clusterNodes = Array.isArray(connectionOptions) ? connectionOptions : [connectionOptions];
390
- client = new Redis.Cluster(clusterNodes);
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
- client = new Redis(connectionOptions);
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
@@ -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 (node.operation === "psubscribe") {
79
- client.on("pmessage", function (pattern, channel, message) {
80
- var payload = smartParse(message);
81
- node.send({
82
- pattern: pattern,
83
- topic: channel,
84
- payload: payload,
85
- });
86
- });
87
- client[node.operation](node.topic, (err, count) => {
88
- if (err) {
89
- node.error(err.message);
90
- node.status({
91
- fill: "red",
92
- shape: "dot",
93
- text: "error",
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
- } else {
96
- node.status({
97
- fill: "green",
98
- shape: "dot",
99
- text: "connected",
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
- } else if (node.operation === "subscribe") {
104
- client.on("message", function (channel, message) {
105
- var payload = smartParse(message);
106
- node.send({
107
- topic: channel,
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
- } else {
120
- node.status({
121
- fill: "green",
122
- shape: "dot",
123
- text: "connected",
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);