@vitormnm/node-red-simple-opcua 1.6.2 → 1.7.0
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 +89 -136
- package/client/lib/opcua-client-browser.js +238 -10
- package/client/lib/opcua-client-method-service.js +1 -1
- package/client/lib/opcua-client-subscription-service.js +0 -2
- package/client/opcua-client-config.html +118 -1
- package/client/opcua-client-config.js +74 -8
- package/client/opcua-client-help.html +6 -0
- package/client/opcua-client-utils.js +34 -10
- package/client/opcua-client.html +7 -0
- package/client/opcua-client.js +97 -1
- package/examples/flows_simple_opc.json +1 -1
- package/package.json +1 -1
- package/server/lib/opcua-address-space-alarm.js +11 -5
- package/server/lib/opcua-address-space-builder.js +65 -15
- package/server/lib/opcua-config.js +81 -23
- package/server/lib/opcua-server-events-child.js +1 -1
- package/server/lib/opcua-server-runtime-child.js +429 -59
- package/server/lib/opcua-server-runtime.js +49 -5
- package/server/lib/opcua-server-status-child.js +14 -14
- package/server/nodered/simple_opcua/server/certificates/mutex +0 -0
- package/server/nodered/simple_opcua/server/certificates/own/certs/server_selfsigned_cert_2048.pem +25 -0
- package/server/nodered/simple_opcua/server/certificates/own/certs/server_selfsigned_cert_2048.pem.mutex +0 -0
- package/server/nodered/simple_opcua/server/certificates/own/openssl.cnf +72 -0
- package/server/nodered/simple_opcua/server/certificates/own/private/private_key.pem +28 -0
- package/server/nodered/simple_opcua/server/certificates/trusted/certs/NodeOPCUA-Client@tuf[c5a9e20a8b680cdff76aaf0165bb3c9318da37a5].pem +25 -0
- package/server/nodered/simple_opcua/server/myServer1/mutex +0 -0
- package/server/nodered/simple_opcua/server/myServer1/own/certs/server_selfsigned_cert_2048.pem +25 -0
- package/server/nodered/simple_opcua/server/myServer1/own/certs/server_selfsigned_cert_2048.pem.mutex +0 -0
- package/server/nodered/simple_opcua/server/myServer1/own/openssl.cnf +72 -0
- package/server/nodered/simple_opcua/server/myServer1/own/private/private_key.pem +28 -0
- package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[91e520c64ff891c67168f08a46dd194071e15dae].pem +25 -0
- package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[98ae95da627cea4c500753c319161a3554ee38d7].pem +25 -0
- package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[aef8d7a1cfba13d84189a0bcf1694208fc51a7f9].pem +25 -0
- package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[c5a9e20a8b680cdff76aaf0165bb3c9318da37a5].pem +25 -0
- package/server/nodered/simple_opcua/server/myServer1/trusted/certs/NodeOPCUA-Client@tuf[ebdf9acf1d02e347917a14108d3144799c638ea3].pem +25 -0
- package/server/opcua-server-io.html +76 -0
- package/server/opcua-server-io.js +135 -23
- package/server/opcua-server.css +52 -0
- package/server/opcua-server.html +166 -44
- package/server/opcua-server.js +142 -7
- package/server/view/opcua-server.css +89 -6
- package/server/view/opcua-server.js +523 -42
|
@@ -193,15 +193,18 @@ class OpcUaServerProcess {
|
|
|
193
193
|
const target = msg && msg.opcuaServerIo ? msg.opcuaServerIo : {};
|
|
194
194
|
const identifierType = this.resolveIdentifierType(target);
|
|
195
195
|
|
|
196
|
-
let result = {}
|
|
196
|
+
let result = {};
|
|
197
|
+
let readArrayResults = null;
|
|
197
198
|
|
|
198
199
|
if (Array.isArray(payload)) {
|
|
199
200
|
if (!payload.length) {
|
|
200
201
|
throw new Error("msg.payload array does not contain any items");
|
|
201
202
|
}
|
|
202
203
|
|
|
204
|
+
readArrayResults = payload.map((item) => this.readPayloadItem(identifierType, item));
|
|
205
|
+
|
|
203
206
|
result = {
|
|
204
|
-
payload:
|
|
207
|
+
payload: readArrayResults,
|
|
205
208
|
identifiers: payload.map((item) => this.resolvePayloadItemIdentifier(item))
|
|
206
209
|
};
|
|
207
210
|
} else if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
@@ -225,9 +228,18 @@ class OpcUaServerProcess {
|
|
|
225
228
|
};
|
|
226
229
|
} else {
|
|
227
230
|
const identifier = this.resolveIdentifier(target);
|
|
231
|
+
let directValue = null;
|
|
232
|
+
let directError = null;
|
|
233
|
+
try {
|
|
234
|
+
directValue = server.readValue(identifierType, identifier);
|
|
235
|
+
} catch (e) {
|
|
236
|
+
directValue = null;
|
|
237
|
+
directError = { identifier, message: e.message || String(e) };
|
|
238
|
+
}
|
|
228
239
|
result = {
|
|
229
|
-
payload:
|
|
230
|
-
identifiers: [identifier]
|
|
240
|
+
payload: directValue,
|
|
241
|
+
identifiers: [identifier],
|
|
242
|
+
directError
|
|
231
243
|
};
|
|
232
244
|
|
|
233
245
|
}
|
|
@@ -245,28 +257,73 @@ class OpcUaServerProcess {
|
|
|
245
257
|
|
|
246
258
|
|
|
247
259
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
260
|
+
let hasFailures = false;
|
|
261
|
+
if (readArrayResults) {
|
|
262
|
+
const failed = readArrayResults.filter(item => item && item.status !== "Good");
|
|
263
|
+
if (failed.length) {
|
|
264
|
+
hasFailures = true;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (result.directError) {
|
|
268
|
+
hasFailures = true;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (!hasFailures) {
|
|
272
|
+
process.send({
|
|
273
|
+
type: "send",
|
|
274
|
+
data: msg,
|
|
275
|
+
nodeId: nodeId
|
|
276
|
+
});
|
|
277
|
+
}
|
|
253
278
|
|
|
254
279
|
process.send({
|
|
255
280
|
type: "status",
|
|
256
281
|
data: {
|
|
257
|
-
fill: "green",
|
|
282
|
+
fill: hasFailures ? (result.directError ? "red" : "yellow") : "green",
|
|
258
283
|
shape: "dot",
|
|
259
|
-
text:
|
|
284
|
+
text: hasFailures
|
|
285
|
+
? (result.directError ? "read failed" : "partial read failed")
|
|
286
|
+
: (result.identifiers.length > 1 ? "read " + result.identifiers.length + " tags" : "read " + result.identifiers[0])
|
|
260
287
|
},
|
|
261
288
|
nodeId: nodeId
|
|
262
289
|
});
|
|
263
290
|
|
|
291
|
+
// Emit a partialError for items that could not be read (array-of-objects mode only)
|
|
292
|
+
if (readArrayResults) {
|
|
293
|
+
const failed = readArrayResults
|
|
294
|
+
.filter(item => item && item.status !== "Good")
|
|
295
|
+
.map(item => ({ name: item.name, path: item.path, status: item.status }));
|
|
296
|
+
|
|
297
|
+
if (failed.length) {
|
|
298
|
+
process.send({
|
|
299
|
+
type: "partialError",
|
|
300
|
+
error: "Some tags could not be read: " + failed.map(f => f.path).join(", "),
|
|
301
|
+
failed: failed,
|
|
302
|
+
originalMsg: msg,
|
|
303
|
+
nodeId: nodeId
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Emit a partialError for a direct single-tag read failure
|
|
309
|
+
if (result.directError) {
|
|
310
|
+
const { identifier, message } = result.directError;
|
|
311
|
+
process.send({
|
|
312
|
+
type: "partialError",
|
|
313
|
+
error: "Some tags could not be read: " + identifier,
|
|
314
|
+
failed: [{ name: "", path: identifier, status: message }],
|
|
315
|
+
originalMsg: msg,
|
|
316
|
+
nodeId: nodeId
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
264
320
|
} catch (error) {
|
|
265
321
|
|
|
266
322
|
process.send({
|
|
267
323
|
type: "error",
|
|
268
324
|
data: { fill: "red", shape: "ring", text: "failed read" },
|
|
269
325
|
error: error.message,
|
|
326
|
+
originalMsg: msg,
|
|
270
327
|
nodeId: nodeId
|
|
271
328
|
});
|
|
272
329
|
}
|
|
@@ -321,6 +378,7 @@ class OpcUaServerProcess {
|
|
|
321
378
|
type: "error",
|
|
322
379
|
data: { fill: "red", shape: "ring", text: "failed write" },
|
|
323
380
|
error: error.message,
|
|
381
|
+
originalMsg: msg,
|
|
324
382
|
nodeId: nodeId
|
|
325
383
|
});
|
|
326
384
|
}
|
|
@@ -335,6 +393,7 @@ class OpcUaServerProcess {
|
|
|
335
393
|
try {
|
|
336
394
|
let writtenPaths = null;
|
|
337
395
|
let payload = msg ? msg.payload : undefined;
|
|
396
|
+
let directError = null;
|
|
338
397
|
|
|
339
398
|
const target = msg && msg.opcuaServerIo ? msg.opcuaServerIo : {};
|
|
340
399
|
const identifierType = this.resolveIdentifierType(target);
|
|
@@ -365,14 +424,18 @@ class OpcUaServerProcess {
|
|
|
365
424
|
if (Buffer.isBuffer(payload) || payload instanceof Uint8Array) {
|
|
366
425
|
|
|
367
426
|
const identifier = this.resolveIdentifier(target);
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
427
|
+
try {
|
|
428
|
+
this.node.writeValue(
|
|
429
|
+
identifierType,
|
|
430
|
+
identifier,
|
|
431
|
+
Buffer.isBuffer(payload)
|
|
432
|
+
? payload
|
|
433
|
+
: Buffer.from(payload)
|
|
434
|
+
);
|
|
435
|
+
} catch (e) {
|
|
436
|
+
directError = { identifier, message: e.message || String(e) };
|
|
437
|
+
msg.payload = null;
|
|
438
|
+
}
|
|
376
439
|
|
|
377
440
|
writtenPaths = [identifier];
|
|
378
441
|
}
|
|
@@ -384,14 +447,18 @@ class OpcUaServerProcess {
|
|
|
384
447
|
) {
|
|
385
448
|
|
|
386
449
|
const identifier = this.resolveIdentifier(target);
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
450
|
+
try {
|
|
451
|
+
this.node.writeValue(
|
|
452
|
+
identifierType,
|
|
453
|
+
identifier,
|
|
454
|
+
isByteString
|
|
455
|
+
? Buffer.from(payload)
|
|
456
|
+
: payload
|
|
457
|
+
);
|
|
458
|
+
} catch (e) {
|
|
459
|
+
directError = { identifier, message: e.message || String(e) };
|
|
460
|
+
msg.payload = null;
|
|
461
|
+
}
|
|
395
462
|
|
|
396
463
|
writtenPaths = [identifier];
|
|
397
464
|
}
|
|
@@ -403,13 +470,13 @@ class OpcUaServerProcess {
|
|
|
403
470
|
throw new Error("msg.payload array does not contain any items");
|
|
404
471
|
}
|
|
405
472
|
|
|
406
|
-
payload.
|
|
407
|
-
this.writePayloadItem(identifierType, item)
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
writtenPaths = payload.map(item =>
|
|
411
|
-
this.resolvePayloadItemIdentifier(item)
|
|
473
|
+
const writeArrayResults = payload.map(item =>
|
|
474
|
+
this.writePayloadItem(identifierType, item)
|
|
412
475
|
);
|
|
476
|
+
|
|
477
|
+
msg.payload = writeArrayResults;
|
|
478
|
+
|
|
479
|
+
writtenPaths = writeArrayResults.map(item => item.path);
|
|
413
480
|
}
|
|
414
481
|
|
|
415
482
|
// Objeto { path: value }
|
|
@@ -429,11 +496,15 @@ class OpcUaServerProcess {
|
|
|
429
496
|
}
|
|
430
497
|
|
|
431
498
|
identifiers.forEach(identifier => {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
499
|
+
try {
|
|
500
|
+
this.node.writeValue(
|
|
501
|
+
identifierType,
|
|
502
|
+
identifier,
|
|
503
|
+
payload[identifier]
|
|
504
|
+
);
|
|
505
|
+
} catch (e) {
|
|
506
|
+
// suppress per-item errors; unknown paths become undefined
|
|
507
|
+
}
|
|
437
508
|
});
|
|
438
509
|
|
|
439
510
|
writtenPaths = identifiers;
|
|
@@ -443,12 +514,16 @@ class OpcUaServerProcess {
|
|
|
443
514
|
else {
|
|
444
515
|
|
|
445
516
|
const identifier = this.resolveIdentifier(target);
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
517
|
+
try {
|
|
518
|
+
this.node.writeValue(
|
|
519
|
+
identifierType,
|
|
520
|
+
identifier,
|
|
521
|
+
payload
|
|
522
|
+
);
|
|
523
|
+
} catch (e) {
|
|
524
|
+
directError = { identifier, message: e.message || String(e) };
|
|
525
|
+
msg.payload = null;
|
|
526
|
+
}
|
|
452
527
|
|
|
453
528
|
writtenPaths = [identifier];
|
|
454
529
|
}
|
|
@@ -466,25 +541,66 @@ class OpcUaServerProcess {
|
|
|
466
541
|
msg.topic = writtenPaths[0];
|
|
467
542
|
}
|
|
468
543
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
544
|
+
let hasFailures = false;
|
|
545
|
+
if (Array.isArray(msg.payload) && msg.payload.length && msg.payload[0] && typeof msg.payload[0].status === "string") {
|
|
546
|
+
const failed = msg.payload.filter(item => item.status !== "Good");
|
|
547
|
+
if (failed.length) {
|
|
548
|
+
hasFailures = true;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (directError) {
|
|
552
|
+
hasFailures = true;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (!hasFailures) {
|
|
556
|
+
process.send({
|
|
557
|
+
type: "send",
|
|
558
|
+
data: msg,
|
|
559
|
+
nodeId
|
|
560
|
+
});
|
|
561
|
+
}
|
|
474
562
|
|
|
475
563
|
process.send({
|
|
476
564
|
type: "status",
|
|
477
565
|
data: {
|
|
478
|
-
fill: "green",
|
|
566
|
+
fill: hasFailures ? (directError ? "red" : "yellow") : "green",
|
|
479
567
|
shape: "dot",
|
|
480
|
-
text:
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
: `write ${writtenPaths[0]}`
|
|
568
|
+
text: hasFailures
|
|
569
|
+
? (directError ? "write failed" : "partial write failed")
|
|
570
|
+
: (writtenPaths.length > 1 ? `write ${writtenPaths.length} tags` : `write ${writtenPaths[0]}`)
|
|
484
571
|
},
|
|
485
572
|
nodeId
|
|
486
573
|
});
|
|
487
574
|
|
|
575
|
+
// Emit a partialError for items that could not be written (array-of-objects mode only)
|
|
576
|
+
if (Array.isArray(msg.payload) && msg.payload.length && msg.payload[0] && typeof msg.payload[0].status === "string") {
|
|
577
|
+
const failed = msg.payload
|
|
578
|
+
.filter(item => item.status !== "Good")
|
|
579
|
+
.map(item => ({ name: item.name, path: item.path, status: item.status }));
|
|
580
|
+
|
|
581
|
+
if (failed.length) {
|
|
582
|
+
process.send({
|
|
583
|
+
type: "partialError",
|
|
584
|
+
error: "Some tags could not be written: " + failed.map(f => f.path).join(", "),
|
|
585
|
+
failed: failed,
|
|
586
|
+
originalMsg: msg,
|
|
587
|
+
nodeId
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Emit a partialError for a direct single-tag write failure
|
|
593
|
+
if (directError) {
|
|
594
|
+
const { identifier, message } = directError;
|
|
595
|
+
process.send({
|
|
596
|
+
type: "partialError",
|
|
597
|
+
error: "Some tags could not be written: " + identifier,
|
|
598
|
+
failed: [{ name: "", path: identifier, status: message }],
|
|
599
|
+
originalMsg: msg,
|
|
600
|
+
nodeId
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
|
|
488
604
|
} catch (error) {
|
|
489
605
|
|
|
490
606
|
process.send({
|
|
@@ -495,6 +611,7 @@ class OpcUaServerProcess {
|
|
|
495
611
|
text: "failed write"
|
|
496
612
|
},
|
|
497
613
|
error: error.message,
|
|
614
|
+
originalMsg: msg,
|
|
498
615
|
nodeId
|
|
499
616
|
});
|
|
500
617
|
}
|
|
@@ -541,20 +658,38 @@ class OpcUaServerProcess {
|
|
|
541
658
|
|
|
542
659
|
readPayloadItem(identifierType, item) {
|
|
543
660
|
const identifier = this.resolvePayloadItemIdentifier(item);
|
|
661
|
+
let value = null;
|
|
662
|
+
let status = "Good";
|
|
663
|
+
try {
|
|
664
|
+
value = this.node.readValue(identifierType, identifier);
|
|
665
|
+
} catch (e) {
|
|
666
|
+
value = null;
|
|
667
|
+
status = e.message || String(e);
|
|
668
|
+
}
|
|
544
669
|
return {
|
|
545
670
|
name: item.name,
|
|
546
671
|
path: identifier,
|
|
547
|
-
value
|
|
672
|
+
value,
|
|
673
|
+
status
|
|
548
674
|
};
|
|
549
675
|
}
|
|
550
676
|
|
|
551
677
|
writePayloadItem(identifierType, item) {
|
|
552
678
|
const identifier = this.resolvePayloadItemIdentifier(item);
|
|
553
|
-
|
|
679
|
+
let writtenValue = null;
|
|
680
|
+
let status = "Good";
|
|
681
|
+
try {
|
|
682
|
+
this.node.writeValue(identifierType, identifier, item.value);
|
|
683
|
+
writtenValue = item.value;
|
|
684
|
+
} catch (e) {
|
|
685
|
+
writtenValue = null;
|
|
686
|
+
status = e.message || String(e);
|
|
687
|
+
}
|
|
554
688
|
return {
|
|
555
689
|
name: item.name,
|
|
556
690
|
path: identifier,
|
|
557
|
-
value:
|
|
691
|
+
value: writtenValue,
|
|
692
|
+
status
|
|
558
693
|
};
|
|
559
694
|
}
|
|
560
695
|
|
|
@@ -775,6 +910,222 @@ class OpcUaServerProcess {
|
|
|
775
910
|
}
|
|
776
911
|
}
|
|
777
912
|
|
|
913
|
+
async readActiveSessions(msg, nodeId) {
|
|
914
|
+
try {
|
|
915
|
+
await this.ensureReady();
|
|
916
|
+
const server = this.node && this.node.server;
|
|
917
|
+
if (!server || !server.engine) {
|
|
918
|
+
throw new Error("OPC UA server is not available");
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
const rawSessions = server.engine._sessions || {};
|
|
922
|
+
const sessions = Object.values(rawSessions).map((session) => buildSessionSnapshot(session));
|
|
923
|
+
|
|
924
|
+
if (msg) {
|
|
925
|
+
const outMsg = Object.assign({}, msg);
|
|
926
|
+
outMsg.payload = sessions;
|
|
927
|
+
|
|
928
|
+
process.send({
|
|
929
|
+
type: "send",
|
|
930
|
+
data: outMsg,
|
|
931
|
+
nodeId: nodeId
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
process.send({
|
|
936
|
+
type: "status",
|
|
937
|
+
data: {
|
|
938
|
+
fill: "green",
|
|
939
|
+
shape: "dot",
|
|
940
|
+
text: sessions.length === 1
|
|
941
|
+
? "1 session"
|
|
942
|
+
: sessions.length + " sessions"
|
|
943
|
+
},
|
|
944
|
+
nodeId: nodeId
|
|
945
|
+
});
|
|
946
|
+
} catch (error) {
|
|
947
|
+
if (msg) {
|
|
948
|
+
process.send({
|
|
949
|
+
type: "error",
|
|
950
|
+
data: { fill: "red", shape: "ring", text: "failed getSessions" },
|
|
951
|
+
error: error.message,
|
|
952
|
+
originalMsg: msg,
|
|
953
|
+
nodeId: nodeId
|
|
954
|
+
});
|
|
955
|
+
} else {
|
|
956
|
+
process.send({
|
|
957
|
+
type: "status",
|
|
958
|
+
data: { fill: "red", shape: "ring", text: "failed getSessions: " + error.message },
|
|
959
|
+
nodeId: nodeId
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
async deleteActiveSessions(msg, nodeId) {
|
|
966
|
+
try {
|
|
967
|
+
await this.ensureReady();
|
|
968
|
+
const server = this.node && this.node.server;
|
|
969
|
+
if (!server || !server.engine) {
|
|
970
|
+
throw new Error("OPC UA server is not available");
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
const payload = msg && Array.isArray(msg.payload) ? msg.payload : [];
|
|
974
|
+
if (!payload.length) {
|
|
975
|
+
throw new Error("msg.payload must be a non-empty array of { sessionId } objects");
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
const engine = server.engine;
|
|
979
|
+
const rawSessions = engine._sessions || {};
|
|
980
|
+
|
|
981
|
+
const results = payload.map((item) => {
|
|
982
|
+
const requestedId = String(item && item.sessionId || "").trim();
|
|
983
|
+
if (!requestedId) {
|
|
984
|
+
return { sessionId: requestedId, status: "error", error: "sessionId is required" };
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// Sessions are keyed by authenticationToken; find by matching nodeId (the GUID sessionId)
|
|
988
|
+
const found = Object.values(rawSessions).find(
|
|
989
|
+
(s) => safeToString(s.nodeId) === requestedId
|
|
990
|
+
);
|
|
991
|
+
|
|
992
|
+
if (!found) {
|
|
993
|
+
return { sessionId: requestedId, status: "not_found" };
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
try {
|
|
997
|
+
engine.closeSession(found.authenticationToken, true, "Forcing");
|
|
998
|
+
return { sessionId: requestedId, status: "deleted" };
|
|
999
|
+
} catch (closeError) {
|
|
1000
|
+
return { sessionId: requestedId, status: "error", error: closeError.message };
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
const deletedCount = results.filter((r) => r.status === "deleted").length;
|
|
1005
|
+
const notFoundCount = results.filter((r) => r.status === "not_found").length;
|
|
1006
|
+
const errorCount = results.filter((r) => r.status === "error").length;
|
|
1007
|
+
|
|
1008
|
+
if (msg) {
|
|
1009
|
+
const outMsg = Object.assign({}, msg);
|
|
1010
|
+
outMsg.payload = results;
|
|
1011
|
+
|
|
1012
|
+
process.send({
|
|
1013
|
+
type: "send",
|
|
1014
|
+
data: outMsg,
|
|
1015
|
+
nodeId: nodeId
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
const statusParts = [];
|
|
1020
|
+
if (deletedCount) statusParts.push("deleted " + deletedCount);
|
|
1021
|
+
if (notFoundCount) statusParts.push("not found " + notFoundCount);
|
|
1022
|
+
if (errorCount) statusParts.push("error " + errorCount);
|
|
1023
|
+
|
|
1024
|
+
process.send({
|
|
1025
|
+
type: "status",
|
|
1026
|
+
data: {
|
|
1027
|
+
fill: errorCount ? "red" : (notFoundCount ? "yellow" : "green"),
|
|
1028
|
+
shape: "dot",
|
|
1029
|
+
text: statusParts.join(", ") || "no sessions"
|
|
1030
|
+
},
|
|
1031
|
+
nodeId: nodeId
|
|
1032
|
+
});
|
|
1033
|
+
} catch (error) {
|
|
1034
|
+
if (msg) {
|
|
1035
|
+
process.send({
|
|
1036
|
+
type: "error",
|
|
1037
|
+
data: { fill: "red", shape: "ring", text: "failed deleteSessions" },
|
|
1038
|
+
error: error.message,
|
|
1039
|
+
originalMsg: msg,
|
|
1040
|
+
nodeId: nodeId
|
|
1041
|
+
});
|
|
1042
|
+
} else {
|
|
1043
|
+
process.send({
|
|
1044
|
+
type: "status",
|
|
1045
|
+
data: { fill: "red", shape: "ring", text: "failed deleteSessions: " + error.message },
|
|
1046
|
+
nodeId: nodeId
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* Serializes a node-opcua ServerSession into a plain, IPC-safe object.
|
|
1056
|
+
*/
|
|
1057
|
+
function buildSessionSnapshot(session) {
|
|
1058
|
+
return {
|
|
1059
|
+
sessionId: safeToString(session.nodeId),
|
|
1060
|
+
sessionName: String(session.sessionName || ""),
|
|
1061
|
+
status: String(session.__status || ""),
|
|
1062
|
+
creationDate: session.creationDate instanceof Date ? session.creationDate.toISOString() : null,
|
|
1063
|
+
sessionTimeout: safeNumber(session.sessionTimeout),
|
|
1064
|
+
clientLastContactTime: safeNumber(session.clientLastContactTime),
|
|
1065
|
+
channelId: session.channelId != null ? session.channelId : null,
|
|
1066
|
+
clientDescription: buildClientDescription(session.clientDescription),
|
|
1067
|
+
userIdentityToken: buildUserIdentityToken(session.userIdentityToken),
|
|
1068
|
+
channel: buildChannelInfo(session.channel),
|
|
1069
|
+
currentSubscriptionCount: safeNumber(session.currentSubscriptionCount),
|
|
1070
|
+
cumulatedSubscriptionCount: safeNumber(session.cumulatedSubscriptionCount),
|
|
1071
|
+
currentMonitoredItemCount: safeNumber(session.currentMonitoredItemCount),
|
|
1072
|
+
aborted: Boolean(session.aborted)
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
function safeToString(value) {
|
|
1077
|
+
try {
|
|
1078
|
+
return value != null ? String(value) : null;
|
|
1079
|
+
} catch (_) {
|
|
1080
|
+
return null;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function safeNumber(value) {
|
|
1085
|
+
const n = Number(value);
|
|
1086
|
+
return Number.isFinite(n) ? n : null;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
function buildClientDescription(desc) {
|
|
1090
|
+
if (!desc || typeof desc !== "object") {
|
|
1091
|
+
return null;
|
|
1092
|
+
}
|
|
1093
|
+
return {
|
|
1094
|
+
applicationUri: safeToString(desc.applicationUri),
|
|
1095
|
+
productUri: safeToString(desc.productUri),
|
|
1096
|
+
applicationName: desc.applicationName && desc.applicationName.text
|
|
1097
|
+
? String(desc.applicationName.text)
|
|
1098
|
+
: safeToString(desc.applicationName),
|
|
1099
|
+
applicationType: safeToString(desc.applicationType)
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
function buildUserIdentityToken(token) {
|
|
1104
|
+
if (!token || typeof token !== "object") {
|
|
1105
|
+
return null;
|
|
1106
|
+
}
|
|
1107
|
+
return {
|
|
1108
|
+
policyId: safeToString(token.policyId),
|
|
1109
|
+
userName: safeToString(token.userName),
|
|
1110
|
+
// Never expose passwords or raw credential bytes
|
|
1111
|
+
tokenType: safeToString(token.schema && token.schema.name)
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
function buildChannelInfo(channel) {
|
|
1116
|
+
if (!channel || typeof channel !== "object") {
|
|
1117
|
+
return null;
|
|
1118
|
+
}
|
|
1119
|
+
return {
|
|
1120
|
+
channelId: channel.channelId != null ? channel.channelId : null,
|
|
1121
|
+
remoteAddress: safeToString(channel.remoteAddress),
|
|
1122
|
+
remotePort: safeNumber(channel.remotePort),
|
|
1123
|
+
bytesRead: safeNumber(channel.bytesRead),
|
|
1124
|
+
bytesWritten: safeNumber(channel.bytesWritten),
|
|
1125
|
+
transactionsCount: safeNumber(channel.transactionsCount),
|
|
1126
|
+
securityMode: safeToString(channel.securityMode),
|
|
1127
|
+
securityPolicy: safeToString(channel.securityPolicy)
|
|
1128
|
+
};
|
|
778
1129
|
}
|
|
779
1130
|
|
|
780
1131
|
/**
|
|
@@ -827,14 +1178,33 @@ process.on("message", async (msg) => {
|
|
|
827
1178
|
break;
|
|
828
1179
|
|
|
829
1180
|
case "buildServerSnapshot":
|
|
830
|
-
|
|
831
|
-
|
|
1181
|
+
try {
|
|
1182
|
+
await serverProcess.ensureReady();
|
|
1183
|
+
OpcUaServerStatusNode(serverProcess.node, msg.msg, msg.nodeId);
|
|
1184
|
+
} catch (error) {
|
|
1185
|
+
process.send({
|
|
1186
|
+
type: "status",
|
|
1187
|
+
data: {
|
|
1188
|
+
fill: msg.msg ? "red" : "yellow",
|
|
1189
|
+
shape: "ring",
|
|
1190
|
+
text: msg.msg ? "Status: " + error.message : "waiting for server"
|
|
1191
|
+
},
|
|
1192
|
+
nodeId: msg.nodeId
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
break;
|
|
832
1196
|
case "eventsServer":
|
|
833
1197
|
eventsServer(serverProcess.node, msg.node, msg.nodeId)
|
|
834
1198
|
break;
|
|
835
1199
|
case "readActiveAlarms":
|
|
836
1200
|
serverProcess.readActiveAlarms(msg.msg, msg.nodeId)
|
|
837
1201
|
break;
|
|
1202
|
+
case "readActiveSessions":
|
|
1203
|
+
await serverProcess.readActiveSessions(msg.msg, msg.nodeId);
|
|
1204
|
+
break;
|
|
1205
|
+
case "deleteActiveSessions":
|
|
1206
|
+
await serverProcess.deleteActiveSessions(msg.msg, msg.nodeId);
|
|
1207
|
+
break;
|
|
838
1208
|
|
|
839
1209
|
|
|
840
1210
|
|