@vitormnm/node-red-simple-opcua 1.4.3 → 1.6.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/client/icons/opcua.svg +132 -132
- package/client/lib/opcua-client-browser.js +368 -330
- package/client/lib/opcua-client-method-service.js +88 -88
- package/client/lib/opcua-client-read-service.js +27 -15
- package/client/lib/opcua-client-subscription-id-service.js +24 -24
- package/client/lib/opcua-client-subscription-service.js +175 -170
- package/client/lib/opcua-client-write-service.js +146 -146
- package/client/opcua-client-config.html +80 -80
- package/client/opcua-client-utils.js +217 -22
- package/client/opcua-client.html +140 -140
- package/client/view/opcua-client.js +1144 -1140
- package/examples/flows_simple_opc.json +1 -2851
- package/icons/opcua.svg +132 -132
- package/icons/opcua2.svg +132 -132
- package/package.json +3 -3
- package/server/icons/opcua.svg +132 -132
- package/server/lib/opcua-address-space-alarm.js +341 -341
- package/server/lib/opcua-address-space-builder.js +1797 -1485
- package/server/lib/opcua-config.js +800 -546
- package/server/lib/opcua-constants.js +120 -109
- package/server/lib/opcua-server-events-child.js +139 -139
- package/server/lib/opcua-server-methods.js +2 -0
- package/server/lib/opcua-server-runtime-child.js +874 -819
- package/server/lib/opcua-server-runtime.js +385 -311
- package/server/lib/opcua-server-status-child.js +187 -187
- package/server/lib/server-node-utils.js +16 -16
- package/server/opcua-server-io.html +346 -346
- package/server/opcua-server-io.js +497 -496
- package/server/opcua-server-registry.js +270 -270
- package/server/opcua-server.css +265 -265
- package/server/opcua-server.html +158 -1643
- package/server/opcua-server.js +24 -13
- package/server/view/opcua-server.css +496 -0
- package/server/view/opcua-server.js +1585 -0
|
@@ -1,546 +1,800 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const {
|
|
4
|
-
DEFAULT_NAMESPACE_URI,
|
|
5
|
-
DEFAULT_RESOURCE_PATH,
|
|
6
|
-
DEFAULT_SERVER_NAME,
|
|
7
|
-
MessageSecurityMode,
|
|
8
|
-
SecurityPolicy,
|
|
9
|
-
SECURITY_MODE_MAP,
|
|
10
|
-
SECURITY_POLICY_MAP,
|
|
11
|
-
DATA_TYPE_MAP,
|
|
12
|
-
normalizePort
|
|
13
|
-
} = require("./opcua-constants");
|
|
14
|
-
|
|
15
|
-
class OpcUaServerConfigParser {
|
|
16
|
-
constructor(node) {
|
|
17
|
-
this.node = node;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
parseNodeConfig(config, credentials) {
|
|
21
|
-
const security = this.applySecuritySettings(config.securityPolicy, config.securityMode);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
this.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
return
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if (!
|
|
306
|
-
throw new Error("
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
if (!
|
|
361
|
-
throw new Error("Each
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if (
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
if (typeof
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
DEFAULT_NAMESPACE_URI,
|
|
5
|
+
DEFAULT_RESOURCE_PATH,
|
|
6
|
+
DEFAULT_SERVER_NAME,
|
|
7
|
+
MessageSecurityMode,
|
|
8
|
+
SecurityPolicy,
|
|
9
|
+
SECURITY_MODE_MAP,
|
|
10
|
+
SECURITY_POLICY_MAP,
|
|
11
|
+
DATA_TYPE_MAP,
|
|
12
|
+
normalizePort
|
|
13
|
+
} = require("./opcua-constants");
|
|
14
|
+
|
|
15
|
+
class OpcUaServerConfigParser {
|
|
16
|
+
constructor(node) {
|
|
17
|
+
this.node = node;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
parseNodeConfig(config, credentials) {
|
|
21
|
+
const security = this.applySecuritySettings(config.securityPolicy, config.securityMode);
|
|
22
|
+
const auth = this.parseAuthConfig(config, credentials);
|
|
23
|
+
return {
|
|
24
|
+
id: this.node.id,
|
|
25
|
+
name: config.name,
|
|
26
|
+
serverName: config.serverName || DEFAULT_SERVER_NAME,
|
|
27
|
+
port: normalizePort(config.port),
|
|
28
|
+
maxConnections: this.normalizeMaxConnections(config.maxConnections),
|
|
29
|
+
namespaceUri: config.namespaceUri || DEFAULT_NAMESPACE_URI,
|
|
30
|
+
resourcePath: config.resourcePath || DEFAULT_RESOURCE_PATH,
|
|
31
|
+
treeConfig: this.parseTreeConfig(config.tree),
|
|
32
|
+
allowAnonymous: this.normalizeAllowAnonymous(config.allowAnonymous),
|
|
33
|
+
groups: auth.groups,
|
|
34
|
+
users: auth.users,
|
|
35
|
+
securityPolicy: security.securityPolicy,
|
|
36
|
+
securityMode: security.securityMode
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
parseTreeConfig(rawTree) {
|
|
41
|
+
try {
|
|
42
|
+
return this.normalizeTreeConfig(rawTree);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
this.node.warn("Invalid tree configuration in editor, using empty tree: " + error.message);
|
|
45
|
+
return { objects: [], folders: [], objectsTypes: [], nameSpaces: [] };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
parseAuthConfig(config, credentials) {
|
|
50
|
+
const safeConfig = config || {};
|
|
51
|
+
let groups = [];
|
|
52
|
+
let users = [];
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
groups = this.normalizeGroupsConfig(
|
|
56
|
+
credentials && credentials.groups !== undefined ? credentials.groups : safeConfig.groups
|
|
57
|
+
);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
this.node.warn("Invalid groups configuration in editor, using derived groups only: " + error.message);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
users = this.normalizeUsersConfig(
|
|
64
|
+
credentials && credentials.users !== undefined ? credentials.users : safeConfig.users
|
|
65
|
+
);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
this.node.warn("Invalid users configuration in editor, using only legacy credential user: " + error.message);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const credentialUser = this.normalizeCredentialUser(credentials);
|
|
71
|
+
if (credentialUser) {
|
|
72
|
+
users.unshift(credentialUser);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
groups: this.buildResolvedGroups(groups, users),
|
|
77
|
+
users
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
buildResolvedGroups(groups, users) {
|
|
82
|
+
const resolved = [];
|
|
83
|
+
const seen = new Set();
|
|
84
|
+
|
|
85
|
+
const addGroup = (groupName) => {
|
|
86
|
+
const normalized = typeof groupName === "string" ? groupName.trim() : "";
|
|
87
|
+
if (!normalized || seen.has(normalized)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
seen.add(normalized);
|
|
91
|
+
resolved.push(normalized);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
(Array.isArray(groups) ? groups : []).forEach(addGroup);
|
|
95
|
+
(Array.isArray(users) ? users : []).forEach((user) => {
|
|
96
|
+
if (user && user.group) {
|
|
97
|
+
if (typeof user.group === "string") {
|
|
98
|
+
user.group.split(",").forEach(addGroup);
|
|
99
|
+
} else if (Array.isArray(user.group)) {
|
|
100
|
+
user.group.forEach(addGroup);
|
|
101
|
+
} else {
|
|
102
|
+
addGroup(user.group);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return resolved;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
normalizeTreeConfig(rawTree) {
|
|
111
|
+
let parsed = rawTree;
|
|
112
|
+
|
|
113
|
+
if (parsed === undefined || parsed === null || parsed === "") {
|
|
114
|
+
parsed = { objects: [], folders: [], objectsTypes: [], nameSpaces: [] };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (typeof parsed === "string") {
|
|
118
|
+
parsed = JSON.parse(parsed);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
122
|
+
throw new Error("Tree configuration must be an object");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Normalize root-level type definitions first so instances can reference them
|
|
126
|
+
const objectsTypes = this.normalizeObjectTypes(parsed.objectsTypes || parsed.objectTypes || []);
|
|
127
|
+
|
|
128
|
+
// Build a lookup map by type name for fast resolution
|
|
129
|
+
this._objectsTypesMap = {};
|
|
130
|
+
for (const typeDef of objectsTypes) {
|
|
131
|
+
this._objectsTypesMap[typeDef.name] = typeDef;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const enumerations = this.normalizeEnumerations(parsed.enumerations || parsed.enumeration || []);
|
|
135
|
+
this._enumerationsMap = {};
|
|
136
|
+
for (const enumDef of enumerations) {
|
|
137
|
+
this._enumerationsMap[enumDef.name] = enumDef;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
objects: this.normalizeObjects(parsed.objects || []),
|
|
142
|
+
folders: this.normalizeFolders(parsed.folders || []),
|
|
143
|
+
objectsTypes,
|
|
144
|
+
enumerations,
|
|
145
|
+
nameSpaces: this.normalizeNamespaces(parsed.nameSpaces || parsed.namespaces || [])
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
normalizeNamespaces(rawNamespaces) {
|
|
150
|
+
if (!Array.isArray(rawNamespaces)) {
|
|
151
|
+
throw new Error("'nameSpaces' must be an array");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const seenIds = new Set();
|
|
155
|
+
return rawNamespaces.map((namespaceConfig) => {
|
|
156
|
+
const normalized = this.normalizeNamespaceDefinition(namespaceConfig);
|
|
157
|
+
if (seenIds.has(normalized.id)) {
|
|
158
|
+
throw new Error("Duplicate namespace id: " + normalized.id);
|
|
159
|
+
}
|
|
160
|
+
seenIds.add(normalized.id);
|
|
161
|
+
return normalized;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
normalizeNamespaceDefinition(namespaceConfig) {
|
|
166
|
+
if (!namespaceConfig || typeof namespaceConfig !== "object" || Array.isArray(namespaceConfig)) {
|
|
167
|
+
throw new Error("Each namespace must be an object");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const id = this.normalizeNamespaceId(namespaceConfig.id);
|
|
171
|
+
const name = typeof namespaceConfig.name === "string" ? namespaceConfig.name.trim() : "";
|
|
172
|
+
|
|
173
|
+
if (!name) {
|
|
174
|
+
throw new Error("Each namespace requires a non-empty name");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
id,
|
|
179
|
+
name
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
normalizeAccessPermissions(rawPermissions) {
|
|
184
|
+
let values = rawPermissions;
|
|
185
|
+
|
|
186
|
+
if (values === undefined || values === null || values === "") {
|
|
187
|
+
values = ["public"];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (typeof values === "string") {
|
|
191
|
+
values = values.indexOf(",") >= 0
|
|
192
|
+
? values.split(",")
|
|
193
|
+
: [values];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!Array.isArray(values)) {
|
|
197
|
+
throw new Error("'accessPermission' must be an array or string");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const seen = new Set();
|
|
201
|
+
const normalized = values.reduce((result, value) => {
|
|
202
|
+
const permission = String(value || "").trim().toLowerCase();
|
|
203
|
+
if (!permission || seen.has(permission)) {
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
seen.add(permission);
|
|
207
|
+
result.push(permission);
|
|
208
|
+
return result;
|
|
209
|
+
}, []);
|
|
210
|
+
|
|
211
|
+
return normalized.length ? normalized : ["public"];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
normalizeObjectTypes(objectTypes) {
|
|
215
|
+
if (!Array.isArray(objectTypes)) {
|
|
216
|
+
throw new Error("'objectsTypes' must be an array");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return objectTypes.map((objectTypeConfig) => this.normalizeBranch(objectTypeConfig, "object type"));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
normalizeGroupsConfig(rawGroups) {
|
|
223
|
+
let parsed = rawGroups;
|
|
224
|
+
|
|
225
|
+
if (parsed === undefined || parsed === null || parsed === "") {
|
|
226
|
+
parsed = [];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (typeof parsed === "string") {
|
|
230
|
+
parsed = JSON.parse(parsed);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (!Array.isArray(parsed)) {
|
|
234
|
+
throw new Error("Groups configuration must be an array");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const seen = new Set();
|
|
238
|
+
return parsed.reduce((groups, groupConfig) => {
|
|
239
|
+
const groupName = this.normalizeGroup(groupConfig);
|
|
240
|
+
if (!seen.has(groupName)) {
|
|
241
|
+
seen.add(groupName);
|
|
242
|
+
groups.push(groupName);
|
|
243
|
+
}
|
|
244
|
+
return groups;
|
|
245
|
+
}, []);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
normalizeGroup(groupConfig) {
|
|
249
|
+
if (typeof groupConfig === "string") {
|
|
250
|
+
const groupName = groupConfig.trim();
|
|
251
|
+
if (!groupName) {
|
|
252
|
+
throw new Error("Each group requires a non-empty name");
|
|
253
|
+
}
|
|
254
|
+
return groupName;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!groupConfig || typeof groupConfig !== "object" || Array.isArray(groupConfig)) {
|
|
258
|
+
throw new Error("Each group must be a string or an object");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const groupName = typeof groupConfig.name === "string" ? groupConfig.name.trim() : "";
|
|
262
|
+
if (!groupName) {
|
|
263
|
+
throw new Error("Each group requires a non-empty name");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return groupName;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
normalizeUsersConfig(rawUsers) {
|
|
270
|
+
let parsed = rawUsers;
|
|
271
|
+
|
|
272
|
+
if (parsed === undefined || parsed === null || parsed === "") {
|
|
273
|
+
parsed = [];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (typeof parsed === "string") {
|
|
277
|
+
parsed = JSON.parse(parsed);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (!Array.isArray(parsed)) {
|
|
281
|
+
throw new Error("Users configuration must be an array");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return parsed.map((userConfig) => this.normalizeUser(userConfig));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
normalizeCredentialUser(credentials) {
|
|
288
|
+
const safeCredentials = credentials || {};
|
|
289
|
+
const username = typeof safeCredentials.username === "string" ? safeCredentials.username.trim() : "";
|
|
290
|
+
const password = typeof safeCredentials.password === "string" ? safeCredentials.password : "";
|
|
291
|
+
|
|
292
|
+
if (!username || !password) {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
username,
|
|
298
|
+
password,
|
|
299
|
+
passwordHash: "",
|
|
300
|
+
group: "default"
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
normalizeFolders(folders) {
|
|
305
|
+
if (!Array.isArray(folders)) {
|
|
306
|
+
throw new Error("'folders' must be an array");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return folders.map((folderConfig) => this.normalizeBranch(folderConfig, "folder"));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
normalizeObjects(objects) {
|
|
313
|
+
if (!Array.isArray(objects)) {
|
|
314
|
+
throw new Error("'objects' must be an array");
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return objects.map((objectConfig) => this.normalizeBranch(objectConfig, "object"));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
normalizeBranch(branchConfig, branchType) {
|
|
321
|
+
if (!branchConfig || typeof branchConfig !== "object" || Array.isArray(branchConfig)) {
|
|
322
|
+
throw new Error("Each " + branchType + " must be an object");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const name = this.requiredName(branchConfig, branchType);
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
name,
|
|
329
|
+
displayName: branchConfig.displayName || name,
|
|
330
|
+
description: branchConfig.description || "",
|
|
331
|
+
nodeId: this.normalizeOptionalNodeId(branchConfig.nodeId),
|
|
332
|
+
namespaceId: this.normalizeNamespaceId(branchConfig.namespaceId),
|
|
333
|
+
accessPermission: this.normalizeAccessPermissions(branchConfig.accessPermission || branchConfig.accessPermissions),
|
|
334
|
+
folders: Array.isArray(branchConfig.folders)
|
|
335
|
+
? branchConfig.folders.map((folderConfig) => this.normalizeBranch(folderConfig, "folder"))
|
|
336
|
+
: [],
|
|
337
|
+
objects: Array.isArray(branchConfig.objects)
|
|
338
|
+
? branchConfig.objects.map((objectConfig) => this.normalizeBranch(objectConfig, "object"))
|
|
339
|
+
: [],
|
|
340
|
+
variables: Array.isArray(branchConfig.variables)
|
|
341
|
+
? branchConfig.variables.map((variableConfig) => this.normalizeVariable(variableConfig))
|
|
342
|
+
: [],
|
|
343
|
+
methods: Array.isArray(branchConfig.methods)
|
|
344
|
+
? branchConfig.methods.map((methodConfig) => this.normalizeMethod(methodConfig))
|
|
345
|
+
: Array.isArray(branchConfig.method)
|
|
346
|
+
? branchConfig.method.map((methodConfig) => this.normalizeMethod(methodConfig))
|
|
347
|
+
: [],
|
|
348
|
+
objectsTypes: Array.isArray(branchConfig.objectsTypes)
|
|
349
|
+
? branchConfig.objectsTypes.map((objectTypeConfig) => this.normalizeObjectTypeInstance(objectTypeConfig))
|
|
350
|
+
: Array.isArray(branchConfig.objectTypes)
|
|
351
|
+
? branchConfig.objectTypes.map((objectTypeConfig) => this.normalizeObjectTypeInstance(objectTypeConfig))
|
|
352
|
+
: [],
|
|
353
|
+
alarms: Array.isArray(branchConfig.alarms)
|
|
354
|
+
? branchConfig.alarms.map((alarmConfig) => this.normalizeAlarm(alarmConfig))
|
|
355
|
+
: []
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
normalizeObjectTypeInstance(objectTypeConfig) {
|
|
360
|
+
if (!objectTypeConfig || typeof objectTypeConfig !== "object" || Array.isArray(objectTypeConfig)) {
|
|
361
|
+
throw new Error("Each object type instance must be an object");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const normalizedBranch = this.normalizeBranch(objectTypeConfig, "object type instance");
|
|
365
|
+
const objectsType = typeof objectTypeConfig.objectsType === "string" && objectTypeConfig.objectsType.trim()
|
|
366
|
+
? objectTypeConfig.objectsType.trim()
|
|
367
|
+
: typeof objectTypeConfig.objectType === "string" && objectTypeConfig.objectType.trim()
|
|
368
|
+
? objectTypeConfig.objectType.trim()
|
|
369
|
+
: "";
|
|
370
|
+
|
|
371
|
+
if (!objectsType) {
|
|
372
|
+
throw new Error("Each object type instance requires a non-empty objectsType");
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
normalizedBranch.objectsType = objectsType;
|
|
376
|
+
|
|
377
|
+
// Children inherited from the type definition are intentionally NOT injected here.
|
|
378
|
+
// The builder (walkInheritedChildren) locates and registers them after node-opcua
|
|
379
|
+
// creates them automatically via addObject({ typeDefinition }). Injecting them here
|
|
380
|
+
// would cause addVariable to be called with the type's nodeId, triggering a
|
|
381
|
+
// "nodeId already registered" error from node-opcua.
|
|
382
|
+
|
|
383
|
+
return normalizedBranch;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
normalizeEnumerations(enumerations) {
|
|
387
|
+
if (!Array.isArray(enumerations)) {
|
|
388
|
+
throw new Error("'enumerations' must be an array");
|
|
389
|
+
}
|
|
390
|
+
return enumerations.map((config) => this.normalizeEnumeration(config));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
normalizeEnumeration(enumerationConfig) {
|
|
394
|
+
if (!enumerationConfig || typeof enumerationConfig !== "object" || Array.isArray(enumerationConfig)) {
|
|
395
|
+
throw new Error("Each enumeration must be an object");
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const name = this.requiredName(enumerationConfig, "enumeration");
|
|
399
|
+
|
|
400
|
+
let enumerationStates = [];
|
|
401
|
+
if (Array.isArray(enumerationConfig.enumeration)) {
|
|
402
|
+
enumerationStates = enumerationConfig.enumeration.map(state => {
|
|
403
|
+
return {
|
|
404
|
+
value: Number.isFinite(Number(state.value)) ? Number(state.value) : 0,
|
|
405
|
+
displayName: typeof state.displayName === "string" ? state.displayName : ""
|
|
406
|
+
};
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
name,
|
|
412
|
+
displayName: enumerationConfig.displayName || name,
|
|
413
|
+
description: enumerationConfig.description || "",
|
|
414
|
+
nodeId: this.normalizeOptionalNodeId(enumerationConfig.nodeId),
|
|
415
|
+
namespaceId: this.normalizeNamespaceId(enumerationConfig.namespaceId),
|
|
416
|
+
accessPermission: this.normalizeAccessPermissions(enumerationConfig.accessPermission || enumerationConfig.accessPermissions),
|
|
417
|
+
enumeration: enumerationStates
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Returns the string value after "s=" in a nodeId like "ns=2;s=Motor_type2"
|
|
422
|
+
_extractNodeIdValue(nodeId) {
|
|
423
|
+
if (!nodeId) return "";
|
|
424
|
+
const m = nodeId.match(/(?:^|;)s=(.+)$/);
|
|
425
|
+
return m ? m[1] : "";
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
normalizeVariable(variableConfig) {
|
|
429
|
+
if (!variableConfig || typeof variableConfig !== "object" || Array.isArray(variableConfig)) {
|
|
430
|
+
throw new Error("Each variable must be an object");
|
|
431
|
+
}
|
|
432
|
+
const name = this.requiredName(variableConfig, "variable");
|
|
433
|
+
const type = this.normalizeType(variableConfig.type);
|
|
434
|
+
const access = this.normalizeAccess(variableConfig.access);
|
|
435
|
+
|
|
436
|
+
return {
|
|
437
|
+
name,
|
|
438
|
+
type,
|
|
439
|
+
access,
|
|
440
|
+
value: this.coerceValue(variableConfig.value, type),
|
|
441
|
+
description: variableConfig.description || "",
|
|
442
|
+
displayName: variableConfig.displayName || name,
|
|
443
|
+
nodeId: this.normalizeOptionalNodeId(variableConfig.nodeId),
|
|
444
|
+
namespaceId: this.normalizeNamespaceId(variableConfig.namespaceId),
|
|
445
|
+
accessPermission: this.normalizeAccessPermissions(variableConfig.accessPermission || variableConfig.accessPermissions)
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
normalizeMethod(methodConfig) {
|
|
450
|
+
if (!methodConfig || typeof methodConfig !== "object" || Array.isArray(methodConfig)) {
|
|
451
|
+
throw new Error("Each method must be an object");
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const name = this.requiredName(methodConfig, "method");
|
|
455
|
+
|
|
456
|
+
return {
|
|
457
|
+
name,
|
|
458
|
+
displayName: methodConfig.displayName || name,
|
|
459
|
+
description: methodConfig.description || "",
|
|
460
|
+
nodeId: this.normalizeOptionalNodeId(methodConfig.nodeId),
|
|
461
|
+
namespaceId: this.normalizeNamespaceId(methodConfig.namespaceId),
|
|
462
|
+
accessPermission: this.normalizeAccessPermissions(methodConfig.accessPermission || methodConfig.accessPermissions),
|
|
463
|
+
inputs: Array.isArray(methodConfig.inputs)
|
|
464
|
+
? methodConfig.inputs.map((arg) => this.normalizeMethodArg(arg))
|
|
465
|
+
: Array.isArray(methodConfig.inputArguments)
|
|
466
|
+
? methodConfig.inputArguments.map((arg) => this.normalizeMethodArg(arg))
|
|
467
|
+
: [],
|
|
468
|
+
outputs: Array.isArray(methodConfig.outputs)
|
|
469
|
+
? methodConfig.outputs.map((arg) => this.normalizeMethodArg(arg))
|
|
470
|
+
: Array.isArray(methodConfig.outputArguments)
|
|
471
|
+
? methodConfig.outputArguments.map((arg) => this.normalizeMethodArg(arg))
|
|
472
|
+
: []
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
normalizeMethodArg(arg) {
|
|
477
|
+
if (!arg || typeof arg !== "object" || Array.isArray(arg)) {
|
|
478
|
+
throw new Error("Each method arg must be an object");
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const name = this.requiredName(arg, "method arg");
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
name,
|
|
485
|
+
type: this.normalizeType(arg.type),
|
|
486
|
+
displayName: arg.displayName || name,
|
|
487
|
+
description: arg.description || ""
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
normalizeAlarm(alarmConfig) {
|
|
492
|
+
if (!alarmConfig || typeof alarmConfig !== "object" || Array.isArray(alarmConfig)) {
|
|
493
|
+
throw new Error("Each alarm must be an object");
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const name = this.requiredName(alarmConfig, "alarm");
|
|
497
|
+
const type = typeof alarmConfig.type === "string" && alarmConfig.type.trim()
|
|
498
|
+
? alarmConfig.type.trim()
|
|
499
|
+
: "levelAlarm";
|
|
500
|
+
|
|
501
|
+
const base = {
|
|
502
|
+
name,
|
|
503
|
+
type,
|
|
504
|
+
sourceName: typeof alarmConfig.sourceName === "string" ? alarmConfig.sourceName : name,
|
|
505
|
+
severity: Number.isFinite(Number(alarmConfig.severity)) ? Number(alarmConfig.severity) : 500,
|
|
506
|
+
variableNodeId: typeof alarmConfig.variableNodeId === "string" ? alarmConfig.variableNodeId : "",
|
|
507
|
+
displayName: typeof alarmConfig.displayName === "string" ? alarmConfig.displayName : "",
|
|
508
|
+
description: typeof alarmConfig.description === "string" ? alarmConfig.description : "",
|
|
509
|
+
nodeId: this.normalizeOptionalNodeId(alarmConfig.nodeId),
|
|
510
|
+
namespaceId: this.normalizeNamespaceId(alarmConfig.namespaceId),
|
|
511
|
+
accessPermission: this.normalizeAccessPermissions(alarmConfig.accessPermission || alarmConfig.accessPermissions),
|
|
512
|
+
enabled: typeof alarmConfig.enabled === "boolean" ? alarmConfig.enabled : true
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
if (type === "levelAlarm") {
|
|
516
|
+
base.highHighLimit = Number.isFinite(Number(alarmConfig.highHighLimit)) ? Number(alarmConfig.highHighLimit) : 100;
|
|
517
|
+
base.highHighMessage = typeof alarmConfig.highHighMessage === "string" ? alarmConfig.highHighMessage : "High High alarm";
|
|
518
|
+
base.highLimit = Number.isFinite(Number(alarmConfig.highLimit)) ? Number(alarmConfig.highLimit) : 80;
|
|
519
|
+
base.highMessage = typeof alarmConfig.highMessage === "string" ? alarmConfig.highMessage : "High alarm";
|
|
520
|
+
base.lowLimit = Number.isFinite(Number(alarmConfig.lowLimit)) ? Number(alarmConfig.lowLimit) : 20;
|
|
521
|
+
base.lowMessage = typeof alarmConfig.lowMessage === "string" ? alarmConfig.lowMessage : "Low alarm";
|
|
522
|
+
base.lowLowLimit = Number.isFinite(Number(alarmConfig.lowLowLimit)) ? Number(alarmConfig.lowLowLimit) : 0;
|
|
523
|
+
base.lowLowMessage = typeof alarmConfig.lowLowMessage === "string" ? alarmConfig.lowLowMessage : "Low Low alarm";
|
|
524
|
+
} else if (type === "digitalAlarm") {
|
|
525
|
+
base.normalStateValue = Number.isFinite(Number(alarmConfig.normalStateValue)) ? Number(alarmConfig.normalStateValue) : 0;
|
|
526
|
+
base.digitalMessage = typeof alarmConfig.digitalMessage === "string" ? alarmConfig.digitalMessage : "Digital alarm";
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return base;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
normalizeUser(userConfig) {
|
|
533
|
+
if (!userConfig || typeof userConfig !== "object" || Array.isArray(userConfig)) {
|
|
534
|
+
throw new Error("Each user must be an object");
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const username = typeof userConfig.username === "string" ? userConfig.username.trim() : "";
|
|
538
|
+
const passwordHash = typeof userConfig.passwordHash === "string" ? userConfig.passwordHash : "";
|
|
539
|
+
const password = typeof userConfig.password === "string" ? userConfig.password : "";
|
|
540
|
+
|
|
541
|
+
let group = "";
|
|
542
|
+
if (userConfig.group !== undefined) {
|
|
543
|
+
if (Array.isArray(userConfig.group)) {
|
|
544
|
+
group = userConfig.group.map(g => typeof g === "string" ? g.trim() : "").filter(Boolean).join(",");
|
|
545
|
+
} else if (typeof userConfig.group === "string") {
|
|
546
|
+
group = userConfig.group.trim();
|
|
547
|
+
}
|
|
548
|
+
} else if (userConfig.role !== undefined) {
|
|
549
|
+
if (Array.isArray(userConfig.role)) {
|
|
550
|
+
group = userConfig.role.map(r => typeof r === "string" ? r.trim() : "").filter(Boolean).join(",");
|
|
551
|
+
} else if (typeof userConfig.role === "string") {
|
|
552
|
+
group = userConfig.role.trim();
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (!username) {
|
|
557
|
+
throw new Error("Each user requires a non-empty username");
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (!passwordHash && !password) {
|
|
561
|
+
throw new Error("Each user requires a password or password hash");
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (!group) {
|
|
565
|
+
throw new Error("Each user requires a non-empty group");
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return {
|
|
569
|
+
username,
|
|
570
|
+
password,
|
|
571
|
+
passwordHash,
|
|
572
|
+
group
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
requiredName(entry, label) {
|
|
577
|
+
if (!entry || typeof entry.name !== "string" || !entry.name.trim()) {
|
|
578
|
+
throw new Error("Each " + label + " requires a non-empty name");
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
return entry.name.trim();
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
normalizeOptionalNodeId(nodeId) {
|
|
585
|
+
return typeof nodeId === "string" ? nodeId.trim() : "";
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
normalizeNamespaceId(namespaceId) {
|
|
589
|
+
if (namespaceId === undefined || namespaceId === null || namespaceId === "") {
|
|
590
|
+
return 2;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const parsed = Number(namespaceId);
|
|
594
|
+
if (!Number.isInteger(parsed) || parsed < 2) {
|
|
595
|
+
throw new Error("Namespace id must be an integer greater than or equal to 2");
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return parsed;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
normalizeType(type) {
|
|
602
|
+
const normalized = typeof type === "string" ? type.trim() : "";
|
|
603
|
+
const aliases = {
|
|
604
|
+
int16: "Int16",
|
|
605
|
+
uint16: "UInt16",
|
|
606
|
+
int32: "Int32",
|
|
607
|
+
uint32: "UInt32",
|
|
608
|
+
int64: "Int64",
|
|
609
|
+
float: "Float",
|
|
610
|
+
boolean: "Boolean",
|
|
611
|
+
string: "String",
|
|
612
|
+
bytestring: "ByteString",
|
|
613
|
+
localizedText: "LocalizedText",
|
|
614
|
+
};
|
|
615
|
+
const canonical = aliases[normalized.toLowerCase()] || normalized;
|
|
616
|
+
if (!DATA_TYPE_MAP[canonical] && (!this._enumerationsMap || !this._enumerationsMap[canonical])) {
|
|
617
|
+
throw new Error("Unsupported variable type: " + type);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
return canonical;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
normalizeAccess(access) {
|
|
624
|
+
const normalized = typeof access === "string" ? access.toLowerCase() : "readonly";
|
|
625
|
+
if (normalized === "rw") {
|
|
626
|
+
return "readwrite";
|
|
627
|
+
}
|
|
628
|
+
if (normalized === "ro") {
|
|
629
|
+
return "readonly";
|
|
630
|
+
}
|
|
631
|
+
if (normalized !== "readonly" && normalized !== "readwrite") {
|
|
632
|
+
throw new Error("Unsupported access mode: " + access);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return normalized;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
normalizeAllowAnonymous(value) {
|
|
639
|
+
if (typeof value === "string") {
|
|
640
|
+
return value !== "false";
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return value !== false;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
normalizeMaxConnections(value) {
|
|
647
|
+
const parsed = Number(value);
|
|
648
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
649
|
+
return 10;
|
|
650
|
+
}
|
|
651
|
+
return parsed;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
applySecuritySettings(policy, mode) {
|
|
655
|
+
const rawPolicy = typeof policy === "string" ? policy.trim() : "None";
|
|
656
|
+
const rawMode = typeof mode === "string" ? mode.trim() : "None";
|
|
657
|
+
|
|
658
|
+
let securityPolicy = Object.prototype.hasOwnProperty.call(SECURITY_POLICY_MAP, rawPolicy)
|
|
659
|
+
? SECURITY_POLICY_MAP[rawPolicy]
|
|
660
|
+
: SECURITY_POLICY_MAP.None;
|
|
661
|
+
let securityMode = Object.prototype.hasOwnProperty.call(SECURITY_MODE_MAP, rawMode)
|
|
662
|
+
? SECURITY_MODE_MAP[rawMode]
|
|
663
|
+
: SECURITY_MODE_MAP.None;
|
|
664
|
+
|
|
665
|
+
if (securityMode === MessageSecurityMode.None) {
|
|
666
|
+
securityPolicy = SecurityPolicy.None;
|
|
667
|
+
if (rawPolicy !== "None") {
|
|
668
|
+
this.node.warn("Security policy adjusted to None because security mode is None");
|
|
669
|
+
}
|
|
670
|
+
} else if (securityPolicy === SecurityPolicy.None) {
|
|
671
|
+
securityPolicy = SecurityPolicy.Basic256Sha256;
|
|
672
|
+
this.node.warn("Security policy adjusted to Basic256Sha256 because signed modes require a policy");
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
return {
|
|
676
|
+
securityPolicy,
|
|
677
|
+
securityMode
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
coerceValue(value, type) {
|
|
682
|
+
if (Array.isArray(value)) {
|
|
683
|
+
return value.map((item) => this.coerceScalarValue(item, type));
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (typeof value === "string") {
|
|
687
|
+
const trimmed = value.trim();
|
|
688
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
689
|
+
try {
|
|
690
|
+
const parsed = JSON.parse(trimmed);
|
|
691
|
+
if (Array.isArray(parsed)) {
|
|
692
|
+
return parsed.map((item) => this.coerceScalarValue(item, type));
|
|
693
|
+
}
|
|
694
|
+
} catch (error) {
|
|
695
|
+
throw new Error("Invalid array value for type " + type + ": " + error.message);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
return this.coerceScalarValue(value, type);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
coerceScalarValue(value, type) {
|
|
704
|
+
if (type === "Int32" || (this._enumerationsMap && this._enumerationsMap[type])) {
|
|
705
|
+
const parsed = Number(value);
|
|
706
|
+
if (!Number.isFinite(parsed)) {
|
|
707
|
+
return 0;
|
|
708
|
+
}
|
|
709
|
+
return Math.trunc(parsed);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (type === "Int64") {
|
|
713
|
+
const minVal = -9223372036854775808n;
|
|
714
|
+
const maxVal = 9223372036854775807n;
|
|
715
|
+
try {
|
|
716
|
+
let bigintVal = BigInt(value);
|
|
717
|
+
if (bigintVal < minVal) bigintVal = minVal;
|
|
718
|
+
else if (bigintVal > maxVal) bigintVal = maxVal;
|
|
719
|
+
return String(bigintVal);
|
|
720
|
+
} catch (error) {
|
|
721
|
+
const parsed = Number(value);
|
|
722
|
+
if (Number.isFinite(parsed)) {
|
|
723
|
+
if (parsed >= 9223372036854775807) {
|
|
724
|
+
return String(maxVal);
|
|
725
|
+
}
|
|
726
|
+
if (parsed <= -9223372036854775808) {
|
|
727
|
+
return String(minVal);
|
|
728
|
+
}
|
|
729
|
+
try {
|
|
730
|
+
let bigintVal = BigInt(Math.trunc(parsed));
|
|
731
|
+
if (bigintVal < minVal) bigintVal = minVal;
|
|
732
|
+
else if (bigintVal > maxVal) bigintVal = maxVal;
|
|
733
|
+
return String(bigintVal);
|
|
734
|
+
} catch (e2) {
|
|
735
|
+
return "0";
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
return "0";
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
if (type === "UInt64") {
|
|
743
|
+
const minVal = 0n;
|
|
744
|
+
const maxVal = 18446744073709551615n;
|
|
745
|
+
try {
|
|
746
|
+
let bigintVal = BigInt(value);
|
|
747
|
+
if (bigintVal < minVal) bigintVal = minVal;
|
|
748
|
+
else if (bigintVal > maxVal) bigintVal = maxVal;
|
|
749
|
+
return String(bigintVal);
|
|
750
|
+
} catch (error) {
|
|
751
|
+
const parsed = Number(value);
|
|
752
|
+
if (Number.isFinite(parsed)) {
|
|
753
|
+
if (parsed >= 18446744073709551615) {
|
|
754
|
+
return String(maxVal);
|
|
755
|
+
}
|
|
756
|
+
if (parsed <= 0) {
|
|
757
|
+
return String(minVal);
|
|
758
|
+
}
|
|
759
|
+
try {
|
|
760
|
+
let bigintVal = BigInt(Math.trunc(parsed));
|
|
761
|
+
if (bigintVal < minVal) bigintVal = minVal;
|
|
762
|
+
else if (bigintVal > maxVal) bigintVal = maxVal;
|
|
763
|
+
return String(bigintVal);
|
|
764
|
+
} catch (e2) {
|
|
765
|
+
return "0";
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return "0";
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if (type === "Float") {
|
|
773
|
+
const parsed = Number(value);
|
|
774
|
+
if (!Number.isFinite(parsed)) {
|
|
775
|
+
return 0;
|
|
776
|
+
}
|
|
777
|
+
return parsed;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
if (type === "Boolean") {
|
|
781
|
+
if (typeof value === "string") {
|
|
782
|
+
const normalized = value.trim().toLowerCase();
|
|
783
|
+
if (normalized === "false" || normalized === "0" || normalized === "") {
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
if (normalized === "true" || normalized === "1") {
|
|
787
|
+
return true;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
return Boolean(value);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
return value === undefined || value === null ? "" : String(value);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
module.exports = {
|
|
799
|
+
OpcUaServerConfigParser
|
|
800
|
+
};
|