node-opcua-client-dynamic-extension-object 2.51.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.
Files changed (49) hide show
  1. package/LICENSE +20 -0
  2. package/dist/convert_data_type_definition_to_structuretype_schema.d.ts +12 -0
  3. package/dist/convert_data_type_definition_to_structuretype_schema.js +284 -0
  4. package/dist/convert_data_type_definition_to_structuretype_schema.js.map +1 -0
  5. package/dist/extra_data_type_manager.d.ts +16 -0
  6. package/dist/extra_data_type_manager.js +76 -0
  7. package/dist/extra_data_type_manager.js.map +1 -0
  8. package/dist/get_extension_object_constructor.d.ts +7 -0
  9. package/dist/get_extension_object_constructor.js +38 -0
  10. package/dist/get_extension_object_constructor.js.map +1 -0
  11. package/dist/get_extra_data_type_manager.d.ts +3 -0
  12. package/dist/get_extra_data_type_manager.js +52 -0
  13. package/dist/get_extra_data_type_manager.js.map +1 -0
  14. package/dist/index.d.ts +11 -0
  15. package/dist/index.js +24 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/populate_data_type_manager.d.ts +3 -0
  18. package/dist/populate_data_type_manager.js +26 -0
  19. package/dist/populate_data_type_manager.js.map +1 -0
  20. package/dist/private/convert_data_type_definition_to_structuretype_schema.d.ts +12 -0
  21. package/dist/private/convert_data_type_definition_to_structuretype_schema.js +284 -0
  22. package/dist/private/convert_data_type_definition_to_structuretype_schema.js.map +1 -0
  23. package/dist/private/find_encodings.d.ts +4 -0
  24. package/dist/private/find_encodings.js +56 -0
  25. package/dist/private/find_encodings.js.map +1 -0
  26. package/dist/private/populate_data_type_manager_103.d.ts +9 -0
  27. package/dist/private/populate_data_type_manager_103.js +599 -0
  28. package/dist/private/populate_data_type_manager_103.js.map +1 -0
  29. package/dist/private/populate_data_type_manager_104.d.ts +9 -0
  30. package/dist/private/populate_data_type_manager_104.js +146 -0
  31. package/dist/private/populate_data_type_manager_104.js.map +1 -0
  32. package/dist/promote_opaque_structure.d.ts +6 -0
  33. package/dist/promote_opaque_structure.js +42 -0
  34. package/dist/promote_opaque_structure.js.map +1 -0
  35. package/dist/resolve_dynamic_extension_object.d.ts +4 -0
  36. package/dist/resolve_dynamic_extension_object.js +106 -0
  37. package/dist/resolve_dynamic_extension_object.js.map +1 -0
  38. package/package.json +47 -0
  39. package/source/convert_data_type_definition_to_structuretype_schema.ts +326 -0
  40. package/source/extra_data_type_manager.ts +89 -0
  41. package/source/get_extension_object_constructor.ts +28 -0
  42. package/source/get_extra_data_type_manager.ts +43 -0
  43. package/source/index.ts +11 -0
  44. package/source/populate_data_type_manager.ts +14 -0
  45. package/source/private/find_encodings.ts +44 -0
  46. package/source/private/populate_data_type_manager_103.ts +715 -0
  47. package/source/private/populate_data_type_manager_104.ts +153 -0
  48. package/source/promote_opaque_structure.ts +42 -0
  49. package/source/resolve_dynamic_extension_object.ts +104 -0
@@ -0,0 +1,715 @@
1
+ // tslint:disable: no-console
2
+ /**
3
+ * @module node-opcua-client-dynamic-extension-object
4
+ */
5
+ import * as chalk from "chalk";
6
+ import * as PrettyError from "pretty-error";
7
+ const pe = new PrettyError();
8
+
9
+ import { assert } from "node-opcua-assert";
10
+ import { AttributeIds, makeNodeClassMask, makeResultMask, QualifiedName } from "node-opcua-data-model";
11
+ import { checkDebugFlag, make_debugLog, make_errorLog } from "node-opcua-debug";
12
+ import { ConstructorFuncWithSchema, DataTypeFactory, getStandardDataTypeFactory } from "node-opcua-factory";
13
+ import { ExpandedNodeId, NodeId, resolveNodeId, sameNodeId } from "node-opcua-nodeid";
14
+ import { browseAll, BrowseDescriptionLike, IBasicSession } from "node-opcua-pseudo-session";
15
+ import {
16
+ createDynamicObjectConstructor,
17
+ DataTypeAndEncodingId,
18
+ MapDataTypeAndEncodingIdProvider,
19
+ parseBinaryXSDAsync
20
+ } from "node-opcua-schemas";
21
+ import { BrowseDescriptionOptions, BrowseDirection, BrowseResult, ReferenceDescription } from "node-opcua-service-browse";
22
+ import { makeBrowsePath } from "node-opcua-service-translate-browse-path";
23
+ import { StatusCodes } from "node-opcua-status-code";
24
+ import { ReadValueIdOptions, StructureDefinition } from "node-opcua-types";
25
+
26
+ import { ExtraDataTypeManager } from "../extra_data_type_manager";
27
+ import {
28
+ CacheForFieldResolution,
29
+ convertDataTypeDefinitionToStructureTypeSchema
30
+ } from "../convert_data_type_definition_to_structuretype_schema";
31
+
32
+ const doDebug = checkDebugFlag(__filename);
33
+ const debugLog = make_debugLog(__filename);
34
+ const errorLog = make_errorLog(__filename);
35
+
36
+ // DataType
37
+ // | 1
38
+ // | n
39
+ // +- HasEncoding-> "Default Binary" (O)[DataTypeEncodingType]
40
+ // |
41
+ // +-- HasDescription -> "MyItemType" (V)[DataTypeDescriptionType]
42
+ // |
43
+ // +- ComponentOf -> Schema(V) []
44
+ // |
45
+ // +- ComponentOf -> OPC Binary(V)[DataTypeSystemType]
46
+ //
47
+ // Note that in 1.04 compliant server, DataType definition might be available
48
+ // in a DataTypeDefinition attributes of the DataType object
49
+ // However this is a brand new aspect of the specification and is not widely implemented
50
+ // it is also optional
51
+ // It will takes time for old opcua server to be refurbished and we may have to
52
+ // keep the current method to access type definition from embedded xsd.
53
+ //
54
+
55
+ async function _readDeprecatedFlag(session: IBasicSession, dataTypeDictionary: NodeId): Promise<boolean> {
56
+ const browsePath = makeBrowsePath(dataTypeDictionary, ".Deprecated");
57
+ const a = await session.translateBrowsePath(browsePath);
58
+ /* istanbul ignore next */
59
+ if (!a.targets || a.targets.length === 0) {
60
+ // the server is probably version < 1.04.
61
+ debugLog("Cannot find Deprecated property for dataTypeDictionary " + dataTypeDictionary.toString());
62
+ return false;
63
+ }
64
+ const deprecatedFlagNodeId = a.targets[0].targetId;
65
+ const dataValue = await session.read({ nodeId: deprecatedFlagNodeId, attributeId: AttributeIds.Value });
66
+ return dataValue.value.value;
67
+ }
68
+
69
+ async function _readNamespaceUriProperty(session: IBasicSession, dataTypeDictionary: NodeId): Promise<string> {
70
+ const a = await session.translateBrowsePath(makeBrowsePath(dataTypeDictionary, ".NamespaceUri"));
71
+ /* istanbul ignore next */
72
+ if (!a.targets || a.targets.length === 0) {
73
+ return "??dataTypeDictionary doesn't expose NamespaceUri property??";
74
+ }
75
+ const namespaceUriProp = a.targets[0].targetId;
76
+ const dataValue = await session.read({ nodeId: namespaceUriProp, attributeId: AttributeIds.Value });
77
+ return dataValue.value.value || "<not set>";
78
+ }
79
+
80
+ interface IDataTypeDescription {
81
+ browseName: QualifiedName;
82
+ nodeId: NodeId;
83
+ encodings?: DataTypeAndEncodingId;
84
+ symbolicName?: string;
85
+ }
86
+
87
+ async function _getDataTypeDescriptions(session: IBasicSession, dataTypeDictionaryNodeId: NodeId): Promise<IDataTypeDescription[]> {
88
+ const nodeToBrowse2: BrowseDescriptionLike = {
89
+ browseDirection: BrowseDirection.Forward,
90
+ includeSubtypes: false,
91
+ nodeClassMask: makeNodeClassMask("Variable"),
92
+ nodeId: dataTypeDictionaryNodeId,
93
+ referenceTypeId: resolveNodeId("HasComponent"),
94
+ // resultMask: makeResultMask("NodeId | ReferenceType | BrowseName | NodeClass | TypeDefinition")
95
+ resultMask: makeResultMask("NodeId | BrowseName")
96
+ };
97
+ const result2 = await browseAll(session, nodeToBrowse2);
98
+ result2.references = result2.references || [];
99
+ return result2.references.map((r) => ({ nodeId: r.nodeId, browseName: r.browseName }));
100
+ }
101
+
102
+ async function _enrichWithDescriptionOf(session: IBasicSession, dataTypeDescriptions: IDataTypeDescription[]): Promise<NodeId[]> {
103
+ const nodesToBrowse3: BrowseDescriptionOptions[] = [];
104
+ for (const ref of dataTypeDescriptions) {
105
+ ref.browseName.toString();
106
+ nodesToBrowse3.push({
107
+ browseDirection: BrowseDirection.Inverse,
108
+ includeSubtypes: false,
109
+ nodeClassMask: makeNodeClassMask("Object"),
110
+ nodeId: ref.nodeId.toString(),
111
+ referenceTypeId: resolveNodeId("HasDescription"),
112
+ // resultMask: makeResultMask("NodeId | ReferenceType | BrowseName | NodeClass | TypeDefinition")
113
+ resultMask: makeResultMask("NodeId")
114
+ });
115
+ }
116
+ /* istanbul ignore next */
117
+ if (nodesToBrowse3.length === 0) {
118
+ return [];
119
+ }
120
+ const results3 = await browseAll(session, nodesToBrowse3);
121
+
122
+ const binaryEncodings = [];
123
+ const nodesToBrowseDataType: BrowseDescriptionOptions[] = [];
124
+
125
+ let i = 0;
126
+ for (const result3 of results3) {
127
+ const dataTypeDescription = dataTypeDescriptions[i++];
128
+
129
+ result3.references = result3.references || [];
130
+ assert(result3.references.length === 1);
131
+ for (const ref of result3.references) {
132
+ const binaryEncodingNodeId = ref.nodeId;
133
+ dataTypeDescription.encodings = dataTypeDescription.encodings || {
134
+ binaryEncodingNodeId: NodeId.nullNodeId,
135
+ dataTypeNodeId: NodeId.nullNodeId,
136
+ jsonEncodingNodeId: NodeId.nullNodeId,
137
+ xmlEncodingNodeId: NodeId.nullNodeId
138
+ };
139
+ dataTypeDescription.encodings.binaryEncodingNodeId = binaryEncodingNodeId;
140
+ binaryEncodings.push(binaryEncodingNodeId);
141
+ nodesToBrowseDataType.push({
142
+ browseDirection: BrowseDirection.Inverse,
143
+ includeSubtypes: false,
144
+ nodeClassMask: makeNodeClassMask("DataType"),
145
+ nodeId: ref.nodeId.toString(),
146
+ referenceTypeId: resolveNodeId("HasEncoding"),
147
+ // resultMask: makeResultMask("NodeId | ReferenceType | BrowseName | NodeClass | TypeDefinition")
148
+ resultMask: makeResultMask("NodeId | BrowseName")
149
+ });
150
+ }
151
+ }
152
+ const dataTypeNodeIds: NodeId[] = [];
153
+ if (nodesToBrowseDataType.length > 0) {
154
+ const results4 = await browseAll(session, nodesToBrowseDataType);
155
+ i = 0;
156
+ for (const result4 of results4) {
157
+ result4.references = result4.references || [];
158
+
159
+ /* istanbul ignore next */
160
+ if (result4.references.length !== 1) {
161
+ console.log("What's going on ?", result4.toString());
162
+ }
163
+
164
+ for (const ref of result4.references) {
165
+ const dataTypeNodeId = ref.nodeId;
166
+
167
+ dataTypeNodeIds.push(dataTypeNodeId);
168
+
169
+ const dataTypeDescription = dataTypeDescriptions[i++];
170
+ dataTypeDescription.encodings!.dataTypeNodeId = dataTypeNodeId;
171
+ }
172
+ }
173
+ }
174
+ return dataTypeNodeIds;
175
+ }
176
+
177
+ interface IDataTypeDefInfo {
178
+ className: string;
179
+ dataTypeNodeId: NodeId;
180
+ dataTypeDefinition: StructureDefinition;
181
+ }
182
+ type DataTypeDefinitions = IDataTypeDefInfo[];
183
+
184
+ function sortStructure(dataTypeDefinitions: DataTypeDefinitions) {
185
+ const dataTypeDefinitionsSorted: IDataTypeDefInfo[] = [];
186
+ const _visited: { [key: string]: IDataTypeDefInfo } = {};
187
+ const _map: { [key: string]: IDataTypeDefInfo } = {};
188
+
189
+ for (const d of dataTypeDefinitions) {
190
+ _map[d.dataTypeNodeId.toString()] = d;
191
+ }
192
+
193
+ function _visit(d: IDataTypeDefInfo) {
194
+ const hash = d.dataTypeNodeId.toString();
195
+ if (_visited[hash]) {
196
+ return;
197
+ }
198
+ const bbb = _map[d.dataTypeDefinition.baseDataType.toString()];
199
+ if (bbb) {
200
+ _visit(bbb);
201
+ }
202
+
203
+ for (const f of d.dataTypeDefinition.fields || []) {
204
+ const ddd = _map[f.dataType.toString()];
205
+ if (!ddd) {
206
+ continue;
207
+ }
208
+ _visit(ddd);
209
+ }
210
+ _visited[hash] = d;
211
+ dataTypeDefinitionsSorted.push(d);
212
+ }
213
+ for (const d of dataTypeDefinitions) {
214
+ _visit(d);
215
+ }
216
+ return dataTypeDefinitionsSorted;
217
+ }
218
+
219
+ async function _extractDataTypeDictionaryFromDefinition(
220
+ session: IBasicSession,
221
+ dataTypeDictionaryNodeId: NodeId,
222
+ dataTypeFactory: DataTypeFactory
223
+ ) {
224
+ assert(dataTypeFactory, "expecting a dataTypeFactory");
225
+
226
+ const dataTypeDescriptions = await _getDataTypeDescriptions(session, dataTypeDictionaryNodeId);
227
+ const dataTypeNodeIds = await _enrichWithDescriptionOf(session, dataTypeDescriptions);
228
+
229
+ // now read DataTypeDefinition attributes of all the dataTypeNodeIds, this will only contains concrete structure
230
+ const nodesToRead: ReadValueIdOptions[] = dataTypeNodeIds.map((nodeId: NodeId) => ({
231
+ attributeId: AttributeIds.DataTypeDefinition,
232
+ nodeId
233
+ }));
234
+
235
+ const cache: { [key: string]: CacheForFieldResolution } = {};
236
+ const dataValuesWithDataTypeDefinition = nodesToRead.length > 0 ? await session.read(nodesToRead) : [];
237
+
238
+ // in some circumstances like Euromap, this assert fails:
239
+ // assert(dataValuesWithDataTypeDefinition.length === dataTypeDescriptions.length);
240
+
241
+ const dataTypeDefinitions: DataTypeDefinitions = [];
242
+
243
+ let index = 0;
244
+ for (const dataValue of dataValuesWithDataTypeDefinition) {
245
+ const dataTypeNodeId = dataTypeNodeIds[index];
246
+ const dataTypeDescription = dataTypeDescriptions[index];
247
+
248
+ /* istanbul ignore else */
249
+ if (dataValue.statusCode === StatusCodes.Good) {
250
+ const dataTypeDefinition = dataValue.value.value;
251
+
252
+ if (dataTypeDefinition && dataTypeDefinition instanceof StructureDefinition) {
253
+ const className = dataTypeDescription.browseName.name!;
254
+ dataTypeDefinitions.push({ className, dataTypeNodeId, dataTypeDefinition });
255
+ }
256
+ } else {
257
+ debugLog(
258
+ "dataTypeNodeId ",
259
+ dataTypeNodeId.toString(),
260
+ " has no DataTypeDescription attribute",
261
+ dataValue.statusCode.toString()
262
+ );
263
+ }
264
+ index++;
265
+ }
266
+ // to do put in logical order
267
+ const dataTypeDefinitionsSorted = sortStructure(dataTypeDefinitions);
268
+ if (doDebug) {
269
+ debugLog("order ", dataTypeDefinitionsSorted.map((a) => a.className + " " + a.dataTypeNodeId).join(" -> "));
270
+ }
271
+ for (const { className, dataTypeNodeId, dataTypeDefinition } of dataTypeDefinitionsSorted) {
272
+ // istanbul ignore next
273
+ if (doDebug) {
274
+ debugLog(chalk.yellow("--------------------------------------- "), className, dataTypeNodeId.toString());
275
+ }
276
+ if (dataTypeFactory.hasStructuredType(className)) {
277
+ continue; // this structure has already been seen
278
+ }
279
+ // now fill typeDictionary
280
+ try {
281
+ const schema = await convertDataTypeDefinitionToStructureTypeSchema(
282
+ session,
283
+ dataTypeNodeId,
284
+ className,
285
+ dataTypeDefinition,
286
+ dataTypeFactory,
287
+ cache
288
+ );
289
+
290
+ // istanbul ignore next
291
+ if (doDebug) {
292
+ debugLog(chalk.red("Registering "), chalk.cyan(className.padEnd(30, " ")), schema.dataTypeNodeId.toString());
293
+ }
294
+ const Constructor = createDynamicObjectConstructor(schema, dataTypeFactory) as ConstructorFuncWithSchema;
295
+ assert(Constructor.schema === schema);
296
+ } catch (err) {
297
+ console.log("Constructor verification err: ", (<Error>err).message);
298
+ console.log("For this reason class " + className + " has not been registered");
299
+ console.log(err);
300
+ }
301
+ }
302
+ }
303
+
304
+ async function _extractNodeIds(
305
+ session: IBasicSession,
306
+ dataTypeDictionaryNodeId: NodeId
307
+ ): Promise<MapDataTypeAndEncodingIdProvider> {
308
+ const map: { [key: string]: DataTypeAndEncodingId } = {};
309
+
310
+ const dataTypeDescriptions = await _getDataTypeDescriptions(session, dataTypeDictionaryNodeId);
311
+
312
+ /* const dataTypeNodeIds = */
313
+ await _enrichWithDescriptionOf(session, dataTypeDescriptions);
314
+
315
+ for (const dataTypeDescription of dataTypeDescriptions) {
316
+ map[dataTypeDescription.browseName.name!.toString()] = dataTypeDescription.encodings!;
317
+ }
318
+
319
+ return {
320
+ getDataTypeAndEncodingId(key: string): DataTypeAndEncodingId | null {
321
+ return map[key] || null;
322
+ }
323
+ };
324
+ }
325
+
326
+ interface TypeDictionaryInfo {
327
+ reference: ReferenceDescription;
328
+ dataTypeDictionaryNodeId: NodeId;
329
+ isDictionaryDeprecated: boolean;
330
+ rawSchema: string;
331
+ dependencies: { [key: string]: string };
332
+ targetNamespace: string;
333
+ }
334
+
335
+ async function _extractDataTypeDictionary(
336
+ session: IBasicSession,
337
+ d: TypeDictionaryInfo,
338
+ dataTypeManager: ExtraDataTypeManager
339
+ ): Promise<void> {
340
+ const dataTypeDictionaryNodeId = d.reference.nodeId;
341
+
342
+ const isDictionaryDeprecated = d.isDictionaryDeprecated; // await _readDeprecatedFlag(session, dataTypeDictionaryNodeId);
343
+ const rawSchema = d.rawSchema; // DataValue = await session.read({ nodeId: dataTypeDictionaryNodeId, attributeId: AttributeIds.Value });
344
+
345
+ const name = await session.read({ nodeId: dataTypeDictionaryNodeId, attributeId: AttributeIds.BrowseName });
346
+ const namespace = await _readNamespaceUriProperty(session, dataTypeDictionaryNodeId);
347
+
348
+ if (isDictionaryDeprecated || rawSchema.length === 0) {
349
+ debugLog(
350
+ "DataTypeDictionary is deprecated or BSD schema stored in dataValue is null !",
351
+ chalk.cyan(name.value.value.toString()),
352
+ "namespace =",
353
+ namespace
354
+ );
355
+ debugLog("let's use the new way (1.04) and let's crawl all dataTypes exposed by this name space");
356
+
357
+ // dataType definition in store directly in UADataType under the $definition property
358
+ const dataTypeFactory2 = dataTypeManager.getDataTypeFactory(dataTypeDictionaryNodeId.namespace);
359
+ if (!dataTypeFactory2) {
360
+ throw new Error("cannot find dataTypeFactory for namespace " + dataTypeDictionaryNodeId.namespace);
361
+ }
362
+ await _extractDataTypeDictionaryFromDefinition(session, dataTypeDictionaryNodeId, dataTypeFactory2);
363
+ return;
364
+ } else {
365
+ debugLog(" ----- Using old method for extracting schema => with BSD files");
366
+ // old method ( until 1.03 )
367
+ // one need to read the schema file store in the dataTypeDictionary node and parse it !
368
+ /* istanbul ignore next */
369
+ if (doDebug) {
370
+ debugLog("---------------------------------------------");
371
+ debugLog(rawSchema.toString());
372
+ debugLog("---------------------------------------------");
373
+ }
374
+ const idProvider = await _extractNodeIds(session, dataTypeDictionaryNodeId);
375
+ const dataTypeFactory1 = dataTypeManager.getDataTypeFactory(dataTypeDictionaryNodeId.namespace);
376
+ await parseBinaryXSDAsync(rawSchema, idProvider, dataTypeFactory1);
377
+ }
378
+ }
379
+
380
+ async function _exploreDataTypeDefinition(
381
+ session: IBasicSession,
382
+ dataTypeDictionaryTypeNode: NodeId,
383
+ dataTypeFactory: DataTypeFactory,
384
+ namespaces: string[]
385
+ ) {
386
+ const nodeToBrowse: BrowseDescriptionLike = {
387
+ browseDirection: BrowseDirection.Forward,
388
+ includeSubtypes: false,
389
+ nodeClassMask: makeNodeClassMask("Variable"),
390
+ nodeId: dataTypeDictionaryTypeNode,
391
+ referenceTypeId: resolveNodeId("HasComponent"),
392
+ resultMask: makeResultMask("ReferenceType | IsForward | BrowseName | NodeClass | TypeDefinition")
393
+ };
394
+ const result = await browseAll(session, nodeToBrowse);
395
+ const references = result.references || [];
396
+
397
+ /* istanbul ignore next */
398
+ if (references.length === 0) {
399
+ return;
400
+ }
401
+
402
+ // request the Definition of each nodes
403
+ const nodesToBrowse2 = references.map((ref: ReferenceDescription) => {
404
+ return {
405
+ browseDirection: BrowseDirection.Inverse,
406
+ includeSubtypes: false,
407
+ nodeClassMask: makeNodeClassMask("Object | Variable"),
408
+ nodeId: ref.nodeId,
409
+ referenceTypeId: resolveNodeId("HasDescription"),
410
+ resultMask: makeResultMask("NodeId | ReferenceType | BrowseName | NodeClass | TypeDefinition")
411
+ };
412
+ });
413
+ const results2 = await browseAll(session, nodesToBrowse2);
414
+
415
+ const binaryEncodingNodeIds = results2.map((br: BrowseResult) => {
416
+ const defaultBin = br.references!.filter((r: ReferenceDescription) => r.browseName.toString() === "Default Binary");
417
+
418
+ /* istanbul ignore next */
419
+ if (defaultBin.length < 1) {
420
+ return ExpandedNodeId;
421
+ }
422
+ return ExpandedNodeId.fromNodeId(defaultBin[0].nodeId, namespaces[defaultBin[0].nodeId.namespace]);
423
+ });
424
+
425
+ // follow now Default Binary <= [Has Encoding] = [DataType]
426
+
427
+ /* istanbul ignore next */
428
+ if (doDebug) {
429
+ console.log(chalk.bgWhite.red("testing new constructors"));
430
+ for (let i = 0; i < references.length; i++) {
431
+ const ref = references[i];
432
+ const binaryEncoding = binaryEncodingNodeIds[i];
433
+ const name = ref.browseName!.name!.toString();
434
+ if (doDebug) {
435
+ debugLog(" type ", name.padEnd(30, " "), binaryEncoding.toString());
436
+ }
437
+ // let's verify that constructor is operational
438
+ try {
439
+ const constructor = dataTypeFactory.getStructureTypeConstructor(name);
440
+ // xx const constructor = getOrCreateConstructor(name, dataTypeFactory, defaultBinary);
441
+ const testObject = new constructor();
442
+ debugLog(testObject.toString());
443
+ } catch (err) {
444
+ debugLog(" Error cannot construct Extension Object " + name);
445
+ debugLog(" " + (<Error>err).message);
446
+ }
447
+ }
448
+ }
449
+ }
450
+
451
+ const regexTargetNamespaceAttribute = /TargetNamespace="([^\"]+)"|TargetNamespace='([^\"]+)'/;
452
+ function extractTargetNamespaceAttribute(xmlElement: string): string {
453
+ // warning TargetNamespace could have ' or " , Wago PLC for instance uses simple quotes
454
+ const c2 = xmlElement.match(regexTargetNamespaceAttribute);
455
+ if (c2) {
456
+ return c2[1] || c2[2];
457
+ }
458
+ return "";
459
+ }
460
+ const regexNamespaceRef = /xmlns:(.*)=(("([^"]+)")|('([^']+)'))/;
461
+ function extraNamespaceRef(attribute: string): { xmlns: string; namespace: string } | null {
462
+ const c = attribute.match(regexNamespaceRef);
463
+ if (c) {
464
+ const xmlns = c[1] as string;
465
+ const namespace: string = c[3] || c[4];
466
+ return { xmlns, namespace };
467
+ }
468
+ return null;
469
+ }
470
+
471
+ /**
472
+ * Extract all custom dataType
473
+ * @param session
474
+ * @param dataTypeManager
475
+ * @async
476
+ */
477
+ export async function populateDataTypeManager103(session: IBasicSession, dataTypeManager: ExtraDataTypeManager): Promise<void> {
478
+ debugLog("in ... populateDataTypeManager");
479
+
480
+ // read namespace array
481
+ const dataValueNamespaceArray = await session.read({
482
+ attributeId: AttributeIds.Value,
483
+ nodeId: resolveNodeId("Server_NamespaceArray")
484
+ });
485
+
486
+ const namespaceArray: string[] = dataValueNamespaceArray.value.value;
487
+
488
+ // istanbul ignore next
489
+ if (!namespaceArray) {
490
+ debugLog("session: cannot read Server_NamespaceArray");
491
+ // throw new Error("Cannot get Server_NamespaceArray as a array of string");
492
+ return;
493
+ }
494
+
495
+ // istanbul ignore next
496
+ if (doDebug) {
497
+ debugLog("namespaceArray ", namespaceArray.map((a, index) => " " + index.toString().padEnd(3) + ":" + a).join(" "));
498
+ }
499
+
500
+ if (dataValueNamespaceArray.statusCode === StatusCodes.Good && namespaceArray && namespaceArray.length > 0) {
501
+ dataTypeManager.setNamespaceArray(namespaceArray);
502
+
503
+ for (let namespaceIndex = 1; namespaceIndex < namespaceArray.length; namespaceIndex++) {
504
+ if (!dataTypeManager.hasDataTypeFactory(namespaceIndex)) {
505
+ const dataTypeFactory1 = new DataTypeFactory([getStandardDataTypeFactory()]);
506
+ dataTypeManager.registerDataTypeFactory(namespaceIndex, dataTypeFactory1);
507
+ }
508
+ }
509
+ }
510
+
511
+ /// to do :: may be not useful
512
+ if (!dataValueNamespaceArray.value.value && dataTypeManager.namespaceArray.length === 0) {
513
+ dataTypeManager.setNamespaceArray([]);
514
+ }
515
+
516
+ const dataTypeDictionaryType = resolveNodeId("DataTypeDictionaryType");
517
+ // DataType/OPCBinary => i=93 [OPCBinarySchema_TypeSystem]
518
+
519
+ // "OPC Binary"[DataSystemType]
520
+ const opcBinaryNodeId = resolveNodeId("OPCBinarySchema_TypeSystem");
521
+
522
+ // let find all DataType dictionary node corresponding to a given namespace
523
+ // (have DataTypeDictionaryType)
524
+ const nodeToBrowse: BrowseDescriptionLike = {
525
+ browseDirection: BrowseDirection.Forward,
526
+ includeSubtypes: false,
527
+ nodeClassMask: makeNodeClassMask("Variable"),
528
+ nodeId: opcBinaryNodeId,
529
+ referenceTypeId: resolveNodeId("HasComponent"),
530
+ resultMask: makeResultMask("ReferenceType | IsForward | BrowseName | NodeClass | TypeDefinition")
531
+ };
532
+ const result = await browseAll(session, nodeToBrowse);
533
+
534
+ if (doDebug) {
535
+ debugLog(result.statusCode.toString());
536
+ debugLog(result.references?.map((r: any) => r.browseName?.toString()).join(" "));
537
+ }
538
+
539
+ // filter nodes that have the expected namespace Index
540
+ // ( more specifically we want to filter out DataStructure from namespace 0)
541
+ // we also want to keep only object of type DataTypeDictionaryType
542
+ const references = result.references!.filter(
543
+ (e: ReferenceDescription) => e.nodeId.namespace !== 0 && sameNodeId(e.typeDefinition, dataTypeDictionaryType)
544
+ );
545
+
546
+ debugLog(`found ${references.length} dictionary`);
547
+
548
+ async function putInCorrectOrder(): Promise<TypeDictionaryInfo[]> {
549
+ const infos: TypeDictionaryInfo[] = [];
550
+ const innerMap: { [key: string]: TypeDictionaryInfo } = {};
551
+
552
+ for (const reference of references) {
553
+ const dataTypeDictionaryNodeId = reference.nodeId;
554
+ const isDictionaryDeprecated = await _readDeprecatedFlag(session, dataTypeDictionaryNodeId);
555
+ const rawSchemaDataValue = await session.read({
556
+ attributeId: AttributeIds.Value,
557
+ nodeId: dataTypeDictionaryNodeId
558
+ });
559
+ const rawSchema = rawSchemaDataValue.value.value ? rawSchemaDataValue.value.value.toString() : "";
560
+
561
+ const info: TypeDictionaryInfo = {
562
+ dataTypeDictionaryNodeId,
563
+ dependencies: {},
564
+ isDictionaryDeprecated,
565
+ rawSchema,
566
+ reference,
567
+ targetNamespace: ""
568
+ };
569
+
570
+ infos.push(info);
571
+
572
+ if (!isDictionaryDeprecated && rawSchema.length > 0) {
573
+ if (doDebug) {
574
+ console.log("schema", rawSchema);
575
+ }
576
+ const matches = rawSchema.match(/<opc:TypeDictionary([^\>]+)>/);
577
+ if (matches) {
578
+ // extract xml:NS="namespace" from attribute list
579
+ // for instance:
580
+ // <opc:TypeDictionary
581
+ // xmlns:opc="http://opcfoundation.org/BinarySchema/"
582
+ // xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
583
+ // xmlns:ua="http://opcfoundation.org/UA/"
584
+ // xmlns:tns="urn:SomeName:Ua:Types:GlobalTypes"
585
+ // DefaultByteOrder="LittleEndian"
586
+ // TargetNamespace="urn:SomeName:Ua:Types:GlobalTypes">
587
+ const typeDictionaryElementAttributes = matches[1];
588
+
589
+ info.targetNamespace = extractTargetNamespaceAttribute(typeDictionaryElementAttributes);
590
+
591
+ const nsKeyNamespace: { [key: string]: string } = {};
592
+ for (const attribute of typeDictionaryElementAttributes.split(" ")) {
593
+ const r = extraNamespaceRef(attribute);
594
+ if (r) {
595
+ const { xmlns, namespace } = r;
596
+ nsKeyNamespace[xmlns] = namespace;
597
+ debugLog("xxxx ns= ", xmlns, "=>", namespace);
598
+ }
599
+ }
600
+ info.dependencies = nsKeyNamespace;
601
+ debugLog("xxx targetNamespace = ", info.targetNamespace);
602
+ innerMap[info.targetNamespace] = info;
603
+ }
604
+ } else {
605
+ // may be 1.04 => the rawSchema is no more needed in new version
606
+ info.targetNamespace = namespaceArray[dataTypeDictionaryNodeId.namespace];
607
+ debugLog("xxx targetNamespace = ", info.targetNamespace);
608
+ innerMap[info.targetNamespace] = info;
609
+ }
610
+ // assert(info.targetNamespace.length !== 0);
611
+ }
612
+ // ----------------------------------
613
+ const orderedList: TypeDictionaryInfo[] = [];
614
+ const visited: any = {};
615
+ function explore(d: TypeDictionaryInfo): void {
616
+ if (visited[d.targetNamespace]) {
617
+ return;
618
+ }
619
+ visited[d.targetNamespace] = 1;
620
+ for (const [xmlns, namespace] of Object.entries(d.dependencies)) {
621
+ if (!innerMap[namespace] || namespace === d.targetNamespace) {
622
+ continue;
623
+ }
624
+ explore(innerMap[namespace]);
625
+ }
626
+ orderedList.push(d);
627
+ }
628
+ for (const d of infos) {
629
+ explore(d);
630
+ }
631
+
632
+ debugLog(" Ordered List = ", orderedList.map((a) => a.targetNamespace).join(" "));
633
+
634
+ return orderedList;
635
+ }
636
+ const dataTypeDictionaryInfo = await putInCorrectOrder();
637
+
638
+ // setup dependencies
639
+ const map: { [key: string]: TypeDictionaryInfo } = {};
640
+ for (const d of dataTypeDictionaryInfo) {
641
+ map[d.targetNamespace] = d;
642
+
643
+ debugLog(
644
+ " fixing based dataTypeFactory dependencies for ",
645
+ d.targetNamespace,
646
+ "index = ",
647
+ d.dataTypeDictionaryNodeId.namespace
648
+ );
649
+
650
+ const baseDataFactories: DataTypeFactory[] = [getStandardDataTypeFactory()];
651
+ for (const namespace of Object.values(d.dependencies)) {
652
+ if (namespace === d.targetNamespace) {
653
+ continue;
654
+ }
655
+ const baseDataFactory = map[namespace];
656
+ if (!baseDataFactory) {
657
+ // xx console.log("xxxxx baseDataFactory = ", namespace);
658
+ continue;
659
+ }
660
+ const namespaceIndex = baseDataFactory.dataTypeDictionaryNodeId.namespace;
661
+ if (dataTypeManager.hasDataTypeFactory(namespaceIndex)) {
662
+ const dep = dataTypeManager.getDataTypeFactory(namespaceIndex);
663
+ baseDataFactories.push(dep);
664
+ debugLog(
665
+ " considering , ",
666
+ baseDataFactory.targetNamespace,
667
+ "index = ",
668
+ baseDataFactory.dataTypeDictionaryNodeId.namespace
669
+ );
670
+ }
671
+ }
672
+ const dataTypeFactory = dataTypeManager.getDataTypeFactory(d.dataTypeDictionaryNodeId.namespace);
673
+ if (dataTypeFactory) {
674
+ dataTypeFactory.repairBaseDataFactories(baseDataFactories);
675
+ }
676
+ }
677
+ // --------------------
678
+
679
+ // now investigate DataTypeDescriptionType
680
+
681
+ async function processReferenceOnDataTypeDictionaryType(d: TypeDictionaryInfo): Promise<void> {
682
+ debugLog(chalk.cyan("processReferenceOnDataTypeDictionaryType on "), d.targetNamespace);
683
+
684
+ const ref = d.reference;
685
+ const dataTypeDictionaryNodeId = d.reference.nodeId;
686
+
687
+ await _extractDataTypeDictionary(session, d, dataTypeManager);
688
+ /* istanbul ignore next */
689
+ if (doDebug) {
690
+ debugLog(
691
+ chalk.bgWhite(" => "),
692
+ ref.browseName.toString(),
693
+ ref.nodeId.toString()
694
+ );
695
+ }
696
+ const dataTypeFactory = dataTypeManager.getDataTypeFactoryForNamespace(dataTypeDictionaryNodeId.namespace);
697
+ await _exploreDataTypeDefinition(session, dataTypeDictionaryNodeId, dataTypeFactory, dataTypeManager.namespaceArray);
698
+ }
699
+
700
+ // https://medium.com/swlh/dealing-with-multiple-promises-in-javascript-41d6c21f20ff
701
+ for (const d of dataTypeDictionaryInfo) {
702
+ try {
703
+ await processReferenceOnDataTypeDictionaryType(d).catch((e) => {
704
+ console.log(e);
705
+ debugLog("processReferenceOnDataTypeDictionaryType has failed ");
706
+ debugLog("Error", e.message);
707
+ debugLog(e);
708
+ return e;
709
+ });
710
+ } catch (err) {
711
+ debugLog(chalk.red("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx "), err);
712
+ }
713
+ }
714
+ debugLog("out ... populateDataTypeManager");
715
+ }