node-red-contrib-influxdb3 1.0.1 → 1.0.3

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
@@ -52,6 +52,8 @@ A configuration node that stores connection details for your InfluxDB v3 instanc
52
52
  - **Host**: Your InfluxDB v3 host URL (e.g., `https://us-east-1-1.aws.cloud2.influxdata.com`)
53
53
  - **Token**: Your InfluxDB v3 authentication token
54
54
  - **Database**: The default database (bucket) name
55
+ - **Verify TLS**: Toggle TLS certificate verification (unchecked sets `NODE_TLS_REJECT_UNAUTHORIZED=0`)
56
+ - **CA Cert Path**: Optional filesystem path for a custom root CA (`NODE_EXTRA_CA_CERTS`)
55
57
 
56
58
  ### InfluxDB v3 Write Node
57
59
 
@@ -344,6 +346,13 @@ Simply reference them in the Node-RED UI using `${INFLUX_HOST}` syntax (if using
344
346
  - Check that your token has write permissions for the database
345
347
  - Ensure the database name exists in your InfluxDB v3 instance
346
348
 
349
+ ### TLS / Custom Certificates
350
+
351
+ If you are connecting to a local InfluxDB v3 instance with a custom certificate:
352
+
353
+ - Set **CA Cert Path** in the config node to the PEM file containing your root CA. This sets `NODE_EXTRA_CA_CERTS` for the Node-RED process.
354
+ - As a last resort, disable **Verify TLS** to set `NODE_TLS_REJECT_UNAUTHORIZED=0` (this disables TLS verification globally for the process).
355
+
347
356
  ### Data Not Appearing
348
357
 
349
358
  - Check the node status - it should show "written" briefly after successful writes
@@ -0,0 +1,240 @@
1
+ const path = require('path');
2
+
3
+ let mockLastClientOptions;
4
+ let mockLastClientInstance;
5
+ let mockLastPoint;
6
+
7
+ jest.mock('@influxdata/influxdb3-client', () => {
8
+ class MockInfluxDBClient {
9
+ constructor(options) {
10
+ mockLastClientOptions = options;
11
+ mockLastClientInstance = this;
12
+ this.write = jest.fn().mockResolvedValue(undefined);
13
+ this.close = jest.fn();
14
+ }
15
+ }
16
+
17
+ class MockPoint {
18
+ constructor(measurement) {
19
+ this.measurement = measurement;
20
+ this.tags = {};
21
+ this.integerFields = {};
22
+ this.floatFields = {};
23
+ this.stringFields = {};
24
+ this.booleanFields = {};
25
+ this.timestamp = null;
26
+ mockLastPoint = this;
27
+ }
28
+
29
+ setTag(key, value) {
30
+ this.tags[key] = value;
31
+ }
32
+
33
+ setIntegerField(key, value) {
34
+ this.integerFields[key] = value;
35
+ }
36
+
37
+ setFloatField(key, value) {
38
+ this.floatFields[key] = value;
39
+ }
40
+
41
+ setStringField(key, value) {
42
+ this.stringFields[key] = value;
43
+ }
44
+
45
+ setBooleanField(key, value) {
46
+ this.booleanFields[key] = value;
47
+ }
48
+
49
+ setTimestamp(ts) {
50
+ this.timestamp = ts;
51
+ }
52
+
53
+ toLineProtocol() {
54
+ return `lp:${this.measurement}`;
55
+ }
56
+ }
57
+
58
+ return {
59
+ InfluxDBClient: MockInfluxDBClient,
60
+ Point: MockPoint,
61
+ __getLastClientOptions: () => mockLastClientOptions,
62
+ __getLastClientInstance: () => mockLastClientInstance,
63
+ __getLastPoint: () => mockLastPoint
64
+ };
65
+ });
66
+
67
+ function buildRED() {
68
+ const types = {};
69
+ return {
70
+ log: {
71
+ info: jest.fn(),
72
+ warn: jest.fn(),
73
+ error: jest.fn()
74
+ },
75
+ nodes: {
76
+ createNode(node, config) {
77
+ node.credentials = config.credentials || {};
78
+ node.status = jest.fn();
79
+ node.error = jest.fn();
80
+ node.warn = jest.fn();
81
+ node.send = jest.fn();
82
+ node.on = jest.fn((event, handler) => {
83
+ node._handlers = node._handlers || {};
84
+ node._handlers[event] = handler;
85
+ });
86
+ },
87
+ registerType(name, ctor) {
88
+ types[name] = ctor;
89
+ },
90
+ getNode(id) {
91
+ return id;
92
+ }
93
+ },
94
+ _types: types
95
+ };
96
+ }
97
+
98
+ function setup() {
99
+ jest.resetModules();
100
+ const RED = buildRED();
101
+ require('../influxdb3.js')(RED);
102
+ const influxModule = require('@influxdata/influxdb3-client');
103
+ return { RED, influxModule };
104
+ }
105
+
106
+ beforeEach(() => {
107
+ mockLastClientOptions = undefined;
108
+ mockLastClientInstance = undefined;
109
+ mockLastPoint = undefined;
110
+ });
111
+
112
+ describe('InfluxDB v3 config node', () => {
113
+ test('normalizes host and uses provided config', () => {
114
+ const { RED, influxModule } = setup();
115
+ const ConfigCtor = RED._types['influxdb3-config'];
116
+
117
+ const configNode = new ConfigCtor({
118
+ host: 'https://example.com',
119
+ database: 'metrics',
120
+ name: 'Test',
121
+ credentials: { token: 'token' }
122
+ });
123
+
124
+ configNode.getClient();
125
+
126
+ const options = influxModule.__getLastClientOptions();
127
+ expect(options.host).toBe('https://example.com/');
128
+ expect(options.database).toBe('metrics');
129
+ expect(options.token).toBe('token');
130
+ });
131
+
132
+ test('sets extra CA certificate path when configured', () => {
133
+ const original = process.env.NODE_EXTRA_CA_CERTS;
134
+ const { RED } = setup();
135
+ const ConfigCtor = RED._types['influxdb3-config'];
136
+
137
+ const configNode = new ConfigCtor({
138
+ host: 'https://example.com',
139
+ database: 'metrics',
140
+ name: 'Test',
141
+ caCertPath: path.join('C:', 'certs', 'root.pem'),
142
+ credentials: { token: 'token' }
143
+ });
144
+
145
+ configNode.getClient();
146
+
147
+ expect(process.env.NODE_EXTRA_CA_CERTS).toBe(path.join('C:', 'certs', 'root.pem'));
148
+ process.env.NODE_EXTRA_CA_CERTS = original;
149
+ });
150
+ });
151
+
152
+ describe('InfluxDB v3 write node', () => {
153
+ test('writes line protocol from object payload', async () => {
154
+ const { RED, influxModule } = setup();
155
+ const ConfigCtor = RED._types['influxdb3-config'];
156
+ const WriteCtor = RED._types['influxdb3-write'];
157
+
158
+ const configNode = new ConfigCtor({
159
+ host: 'https://example.com',
160
+ database: 'metrics',
161
+ name: 'Test',
162
+ credentials: { token: 'token' }
163
+ });
164
+
165
+ const writeNode = new WriteCtor({
166
+ influxdb: configNode,
167
+ measurement: 'cpu',
168
+ database: ''
169
+ });
170
+
171
+ const msg = {
172
+ measurement: 'cpu',
173
+ payload: {
174
+ fields: {
175
+ temperature: 21.5,
176
+ count: 5
177
+ },
178
+ tags: { location: 'lab' },
179
+ integers: ['count'],
180
+ timestamp: 1700000000000
181
+ }
182
+ };
183
+
184
+ const send = jest.fn();
185
+ const done = jest.fn();
186
+ await writeNode._handlers.input(msg, send, done);
187
+
188
+ const point = influxModule.__getLastPoint();
189
+ const client = influxModule.__getLastClientInstance();
190
+
191
+ expect(point.floatFields.temperature).toBe(21.5);
192
+ expect(point.integerFields.count).toBe(5);
193
+ expect(point.tags.location).toBe('lab');
194
+
195
+ expect(client.write).toHaveBeenCalledWith('lp:cpu', 'metrics');
196
+ expect(send).toHaveBeenCalledWith(msg);
197
+ expect(done).toHaveBeenCalled();
198
+
199
+ if (writeNode._handlers.close) {
200
+ writeNode._handlers.close();
201
+ }
202
+ });
203
+
204
+ test('writes raw line protocol string with database override', async () => {
205
+ const { RED, influxModule } = setup();
206
+ const ConfigCtor = RED._types['influxdb3-config'];
207
+ const WriteCtor = RED._types['influxdb3-write'];
208
+
209
+ const configNode = new ConfigCtor({
210
+ host: 'https://example.com',
211
+ database: 'metrics',
212
+ name: 'Test',
213
+ credentials: { token: 'token' }
214
+ });
215
+
216
+ const writeNode = new WriteCtor({
217
+ influxdb: configNode,
218
+ measurement: '',
219
+ database: ''
220
+ });
221
+
222
+ const msg = {
223
+ payload: ' weather,location=lab temperature=18.5 ',
224
+ database: 'override-db'
225
+ };
226
+
227
+ const send = jest.fn();
228
+ const done = jest.fn();
229
+ await writeNode._handlers.input(msg, send, done);
230
+
231
+ const client = influxModule.__getLastClientInstance();
232
+ expect(client.write).toHaveBeenCalledWith('weather,location=lab temperature=18.5', 'override-db');
233
+ expect(send).toHaveBeenCalledWith(msg);
234
+ expect(done).toHaveBeenCalled();
235
+
236
+ if (writeNode._handlers.close) {
237
+ writeNode._handlers.close();
238
+ }
239
+ });
240
+ });
package/influxdb3.html CHANGED
@@ -7,7 +7,9 @@
7
7
  defaults: {
8
8
  name: { value: '' },
9
9
  host: { value: '', required: true },
10
- database: { value: '', required: true }
10
+ database: { value: '', required: true },
11
+ tlsRejectUnauthorized: { value: true },
12
+ caCertPath: { value: '' }
11
13
  },
12
14
  credentials: {
13
15
  token: { type: 'password' }
@@ -35,6 +37,15 @@
35
37
  <label for="node-config-input-database"><i class="fa fa-database"></i> Database</label>
36
38
  <input type="text" id="node-config-input-database" placeholder="my-database">
37
39
  </div>
40
+ <div class="form-row">
41
+ <label for="node-config-input-tlsRejectUnauthorized"><i class="fa fa-lock"></i> Verify TLS</label>
42
+ <input type="checkbox" id="node-config-input-tlsRejectUnauthorized" style="width:auto;">
43
+ <span>Reject unauthorized certificates</span>
44
+ </div>
45
+ <div class="form-row">
46
+ <label for="node-config-input-caCertPath"><i class="fa fa-certificate"></i> CA Cert Path</label>
47
+ <input type="text" id="node-config-input-caCertPath" placeholder="C:\\path\\to\\root-ca.pem">
48
+ </div>
38
49
  </script>
39
50
 
40
51
  <script type="text/html" data-help-name="influxdb3-config">
@@ -50,6 +61,10 @@
50
61
  <dd>The authentication token for accessing InfluxDB v3</dd>
51
62
  <dt>Database <span class="property-type">string</span></dt>
52
63
  <dd>The default database (bucket) name to write to</dd>
64
+ <dt>Verify TLS <span class="property-type">boolean</span></dt>
65
+ <dd>When unchecked, sets <code>NODE_TLS_REJECT_UNAUTHORIZED=0</code> for the Node-RED process</dd>
66
+ <dt>CA Cert Path <span class="property-type">string</span></dt>
67
+ <dd>Optional filesystem path used to set <code>NODE_EXTRA_CA_CERTS</code> for custom root CAs</dd>
53
68
  </dl>
54
69
  </script>
55
70
 
package/influxdb3.js CHANGED
@@ -74,6 +74,8 @@ module.exports = function(RED) {
74
74
  this.host = config.host;
75
75
  this.database = config.database;
76
76
  this.name = config.name;
77
+ this.tlsRejectUnauthorized = config.tlsRejectUnauthorized !== false;
78
+ this.caCertPath = config.caCertPath;
77
79
 
78
80
  // Store token as a credential
79
81
  this.token = this.credentials.token;
@@ -97,6 +99,16 @@ module.exports = function(RED) {
97
99
 
98
100
  try {
99
101
  const normalizedHost = normalizeHost(this.host);
102
+
103
+ if (!this.tlsRejectUnauthorized) {
104
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
105
+ RED.log.warn('InfluxDB v3: TLS certificate verification is disabled for this process.');
106
+ }
107
+
108
+ if (this.caCertPath) {
109
+ process.env.NODE_EXTRA_CA_CERTS = this.caCertPath;
110
+ RED.log.info(`InfluxDB v3: Using extra CA certificates from ${this.caCertPath}`);
111
+ }
100
112
 
101
113
  RED.log.info(`InfluxDB v3: Connecting to ${normalizedHost} with database ${this.database}`);
102
114
 
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "node-red-contrib-influxdb3",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Node-RED nodes for InfluxDB v3 integration",
5
5
  "main": "influxdb3.js",
6
6
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
7
+ "test": "jest"
8
8
  },
9
9
  "keywords": [
10
10
  "node-red",
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "repository": {
22
22
  "type": "git",
23
- "url": "https://github.com/stuartb55/nodered-influxdb3.git"
23
+ "url": "git+https://github.com/stuartb55/nodered-influxdb3.git"
24
24
  },
25
25
  "homepage": "https://github.com/stuartb55/nodered-influxdb3#readme",
26
26
  "bugs": {
@@ -33,10 +33,12 @@
33
33
  }
34
34
  },
35
35
  "dependencies": {
36
- "@influxdata/influxdb3-client": "^1.4.0"
36
+ "@influxdata/influxdb3-client": "^2.0.0"
37
+ },
38
+ "devDependencies": {
39
+ "jest": "^29.7.0"
37
40
  },
38
41
  "peerDependencies": {
39
42
  "node-red": ">=3.0.0"
40
43
  }
41
44
  }
42
-