node-red-contrib-redis-variable 1.5.1 → 1.5.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-redis-variable",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
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",
@@ -385,9 +385,21 @@ module.exports = function (RED) {
385
385
  // Create Redis client
386
386
  let client;
387
387
  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);
388
+ // ioredis Cluster: first arg = startup nodes [{ host, port }], second = options with redisOptions
389
+ const clusterNodes = [{ host: options.host, port: parseInt(options.port, 10) }];
390
+ const clusterOptions = {
391
+ enableReadyCheck: false,
392
+ lazyConnect: true,
393
+ maxRetriesPerRequest: 3,
394
+ redisOptions: {
395
+ username: options.username || undefined,
396
+ password: options.password || undefined,
397
+ db: 0,
398
+ connectTimeout: connectionOptions.connectTimeout || 5000,
399
+ tls: connectionOptions.tls || undefined,
400
+ },
401
+ };
402
+ client = new Redis.Cluster(clusterNodes, clusterOptions);
391
403
  } else {
392
404
  client = new Redis(connectionOptions);
393
405
  }
package/redis-variable.js CHANGED
@@ -309,6 +309,21 @@ module.exports = function (RED) {
309
309
  send = send || function() { node.send.apply(node, arguments) };
310
310
  done = done || function(err) { if(err) node.error(err, msg); };
311
311
 
312
+ // Ensure we only send and call done once per message
313
+ let completed = false;
314
+ const finish = (err) => {
315
+ if (completed) return;
316
+ completed = true;
317
+ if (err) node.error(err.message || err, msg);
318
+ done(err);
319
+ };
320
+ const sendAndDone = (m) => {
321
+ if (completed) return;
322
+ completed = true;
323
+ send(m);
324
+ done();
325
+ };
326
+
312
327
  if (!running) {
313
328
  running = true;
314
329
  }
@@ -317,8 +332,7 @@ module.exports = function (RED) {
317
332
  if (!redisConfig) {
318
333
  node.error("Redis configuration not found", msg);
319
334
  msg.payload = { error: "Redis configuration not found" };
320
- send(msg);
321
- done();
335
+ sendAndDone(msg);
322
336
  return;
323
337
  }
324
338
 
@@ -337,8 +351,7 @@ module.exports = function (RED) {
337
351
  } catch (configError) {
338
352
  node.error(configError.message, msg);
339
353
  msg.payload = { error: configError.message };
340
- send(msg);
341
- done();
354
+ sendAndDone(msg);
342
355
  return;
343
356
  }
344
357
 
@@ -350,43 +363,50 @@ module.exports = function (RED) {
350
363
  node.warn("Redis configuration not available. Operation skipped.");
351
364
  node.status({ fill: "yellow", shape: "ring", text: "no config" });
352
365
  msg.payload = { error: "Redis configuration not available" };
353
- send(msg);
354
- done();
366
+ sendAndDone(msg);
355
367
  return;
356
368
  }
357
369
  }
358
370
 
359
- // Check if client is connected before proceeding
371
+ // Ensure client is connected before running commands
360
372
  if (client.status !== 'ready' && client.status !== 'connect') {
361
- // Try to connect if not connected
362
- if (client.status === 'wait') {
363
- await client.connect();
364
- } else if (client.status === 'disconnect' || client.status === 'end') {
365
- await client.connect();
366
- } else {
367
- // Force disconnect and recreate client for other error states
368
- try {
373
+ const CONNECT_WAIT_MS = 15000;
374
+ const waitForReady = () => new Promise((resolve, reject) => {
375
+ const timeout = setTimeout(() => reject(new Error('Connection timeout')), CONNECT_WAIT_MS);
376
+ const onReady = () => { clearTimeout(timeout); cleanup(); resolve(); };
377
+ const onError = (e) => { clearTimeout(timeout); cleanup(); reject(e); };
378
+ const cleanup = () => {
379
+ client.removeListener('ready', onReady);
380
+ client.removeListener('connect', onReady);
381
+ client.removeListener('error', onError);
382
+ };
383
+ client.once('ready', onReady);
384
+ client.once('connect', onReady);
385
+ client.once('error', onError);
386
+ });
387
+ try {
388
+ if (client.status === 'connecting') {
389
+ // Already connecting: wait for ready, do not call connect() again
390
+ await waitForReady();
391
+ } else if (typeof client.connect === 'function') {
392
+ await client.connect();
393
+ }
394
+ } catch (connectErr) {
395
+ const msg = connectErr.message || String(connectErr);
396
+ if (msg.includes('already connecting') || msg.includes('already connected')) {
397
+ // Race: another message triggered connect; wait for ready
398
+ await waitForReady();
399
+ } else {
369
400
  redisConfig.forceDisconnect(node.id);
370
401
  client = null;
371
- client = redisConfig.getClient(msg, node, node.id);
372
- if (!client) {
373
- node.warn("Redis configuration not available during reconnection. Operation skipped.");
374
- node.status({ fill: "yellow", shape: "ring", text: "no config" });
375
- msg.payload = { error: "Redis configuration not available" };
376
- send(msg);
377
- done();
378
- return;
379
- }
380
- } catch (reconnectError) {
381
- throw new Error(`Failed to reconnect: ${reconnectError.message}`);
402
+ throw new Error(`Failed to connect: ${msg}`);
382
403
  }
383
404
  }
384
405
  }
385
406
  } catch (err) {
386
407
  node.error(err.message, msg);
387
408
  msg.payload = { error: err.message };
388
- send(msg);
389
- done();
409
+ sendAndDone(msg);
390
410
  return;
391
411
  }
392
412
 
@@ -770,22 +790,19 @@ module.exports = function (RED) {
770
790
  retryable: false
771
791
  };
772
792
  }
773
- send(msg);
774
- done();
793
+ sendAndDone(msg);
775
794
  return;
776
795
  }
777
796
 
778
797
  // Update node status on success
779
798
  node.status({ fill: "green", shape: "dot", text: node.operation });
780
- send(msg);
781
- done();
799
+ sendAndDone(msg);
782
800
 
783
801
  } catch (error) {
784
802
  // Handle general errors (validation, etc.)
785
803
  node.error(error.message, msg);
786
804
  msg.payload = { error: error.message };
787
- send(msg);
788
- done();
805
+ sendAndDone(msg);
789
806
  }
790
807
  });
791
808
  }