• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

node-opcua / node-opcua / 14452017930

14 Apr 2025 05:35PM UTC coverage: 90.863% (+0.001%) from 90.862%
14452017930

push

github

erossignon
chore: tidy up imports

10983 of 13932 branches covered (78.83%)

3 of 3 new or added lines in 3 files covered. (100.0%)

245 existing lines in 14 files now uncovered.

29991 of 33007 relevant lines covered (90.86%)

322514.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.38
/packages/node-opcua-client-proxy/source/object_explorer.ts
1
/**
2
 * @module node-opcua-client-proxy
3
 */
4

5
import { assert } from "node-opcua-assert";
1✔
6
import { AttributeIds, BrowseDirection, makeNodeClassMask, makeResultMask } from "node-opcua-data-model";
1✔
7
import { NodeId } from "node-opcua-nodeid";
8
import {
9
    IBasicSessionReadAsyncSimple,
10
    IBasicSessionBrowseAsyncSimple
11
} from "node-opcua-pseudo-session";
12
import { ReferenceDescription } from "node-opcua-service-browse";
13
import { CallMethodRequest, Argument } from "node-opcua-service-call";
1✔
14
import { lowerFirstLetter } from "node-opcua-utils";
1✔
15
import { DataType, Variant, VariantArrayType } from "node-opcua-variant";
1✔
16
import { make_errorLog, make_debugLog } from "node-opcua-debug";
1✔
17
import { DataTypeIds } from "node-opcua-constants";
1✔
18

19
import { makeRefId } from "./proxy";
1✔
20
import { UAProxyManager } from "./proxy_manager";
21
import { ProxyVariable } from "./proxy_variable";
1✔
22
import { MethodDescription, ArgumentEx } from "./proxy_base_node";
23

24
const doDebug = false;
1✔
25
const debugLog = make_debugLog("Proxy");
1✔
26

27
export interface ObjectExplorerOptions {
28
    proxyManager: UAProxyManager;
29
    name: string;
30
    nodeId: NodeId;
31
    parent: any;
32
}
33

34
const resultMask = makeResultMask("ReferenceType | IsForward | BrowseName | NodeClass | TypeDefinition");
1✔
35

36
/**
37

38
 *
39
 * @param session
40
 * @param dataTypeId
41
 * @param callback
42
 * @param callback.err
43
 * @param callback.dataType
44
 *
45
 *  @example
46
 *
47
 *      const dataTypeId  ="ns=0;i=11"; // Double
48
 *      convertNodeIdToDataTypeAsync(session,dataTypeId,function(err,dataType) {
49
 *          assert(!err && dataType === DataType.Double);
50
 *      });
51
 *
52
 *      const dataTypeId  ="ns=0;i=290"; // Duration => SubTypeOf Double
53
 *      convertNodeIdToDataTypeAsync(session,dataTypeId,function(err,dataType) {
54
 *          assert(!err && dataType === DataType.Double);
55
 *      });
56
 *
57
 * see also AddressSpace#findCorrespondingBasicDataType
58
 *
59
 * for an enumeration dataType will be DataType.Int32
60
 */
61
async function convertNodeIdToDataTypeAsync(
62
    session: IBasicSessionReadAsyncSimple & IBasicSessionBrowseAsyncSimple,
63
    dataTypeId: NodeId
64
): Promise<DataType> {
65
    const nodeToRead = {
183✔
66
        attributeId: AttributeIds.BrowseName,
67
        nodeId: dataTypeId
68
    };
69
    const dataValue = await session.read(nodeToRead);
183✔
70
    let dataType: DataType;
71
    // istanbul ignore next
72
    if (dataValue.statusCode.isNotGood()) {
73
        dataType = DataType.Null;
74
        return dataType;
75
    }
76

77
    const dataTypeName = dataValue.value.value;
183✔
78

79
    if (dataTypeId.namespace === 0 && dataTypeId.value === DataTypeIds.Enumeration) {
183✔
80
        dataType = DataType.Int32;
1✔
81
        return dataType;
1✔
82
    }
83

84
    if (dataTypeId.namespace === 0 && DataType[dataTypeId.value as number]) {
182✔
85
        dataType = (DataType as any)[dataTypeId.value as number] as DataType;
164✔
86
        return dataType;
164✔
87
    }
88

89
    /// example => Duration (i=290) => Double (i=11)
90
    // read subTypeOf
91
    const nodeToBrowse = {
18✔
92
        browseDirection: BrowseDirection.Inverse,
93
        includeSubtypes: false,
94
        nodeId: dataTypeId,
95
        // BrowseDescription
96
        referenceTypeId: makeRefId("HasSubtype"),
97
        // xx nodeClassMask: makeNodeClassMask("ObjectType"),
98
        resultMask
99
    };
100
    // tslint:disable:no-shadowed-variable
101
    const browseResult = await session.browse(nodeToBrowse);
18✔
102

103
    const references = browseResult!.references;
18✔
104

105
    if (!references || references.length !== 1) {
18!
UNCOV
106
        throw new Error("cannot find SuperType of " + dataTypeName.toString());
×
107
    }
108
    const nodeId = references[0].nodeId;
18✔
109
    return convertNodeIdToDataTypeAsync(session, nodeId);
18✔
110
}
111

112
function convertToVariant(value: unknown, arg: ArgumentEx, propName: string): Variant {
113
    const dataType = arg._basicDataType || DataType.Null;
4!
114
    const arrayType =
115
        arg.valueRank === 1 ? VariantArrayType.Array : arg.valueRank === -1 ? VariantArrayType.Scalar : VariantArrayType.Matrix;
4!
116

117
    if (value === undefined) {
4!
UNCOV
118
        throw new Error("expecting input argument ");
×
119
    }
120
    if (arrayType === VariantArrayType.Array) {
4!
UNCOV
121
        if (!Array.isArray(value)) {
×
122
            throw new Error("expecting value to be an Array or a TypedArray");
×
123
        }
124
    }
125
    return new Variant({ arrayType, dataType, value });
4✔
126
}
127

128
function convertToVariantArray(inputArgsDef: ArgumentEx[], inputArgs: Record<string, unknown>): Variant[] {
129
    const inputArguments: Variant[] = inputArgsDef.map((arg: ArgumentEx) => {
8✔
130
        const propName = lowerFirstLetter(arg.name || "");
4!
131
        const value = inputArgs[propName];
4✔
132
        return convertToVariant(value, arg, propName);
4✔
133
    });
134
    return inputArguments;
8✔
135
}
136

137
import { ProxyNode } from "./proxy_transition";
138
import { StatusCode } from "node-opcua-status-code";
139

140
function makeFunction(obj: any, methodName: string) {
141
    return async function functionCaller(this: any, inputArgs: Record<string, unknown>): Promise<{ statusCode: StatusCode, output?: Record<string, unknown> }> {
82✔
142
        const session = this.proxyManager.session;
8✔
143

144
        const methodDef = this.$methods[methodName];
8✔
145
        // convert input arguments into Variants
146
        const inputArgsDef = methodDef.inputArguments || [];
8!
147

148
        const inputArguments: Variant[] = convertToVariantArray(inputArgsDef, inputArgs);
8✔
149

150
        const methodToCall = new CallMethodRequest({
8✔
151
            inputArguments,
152
            methodId: methodDef.nodeId,
153
            objectId: obj.nodeId
154
        });
155

156
        const callResult = await session.call(methodToCall);
8✔
157

158
        if (callResult.statusCode.isNotGood()) {
8✔
159
            return { statusCode: callResult.statusCode };
1✔
160
        }
161

162
        callResult.outputArguments = callResult.outputArguments || [];
7!
163

164
        if (callResult.outputArguments.length !== methodDef.outputArguments.length) {
7!
UNCOV
165
            throw new Error(
×
166
                "Internal error callResult.outputArguments.length " +
167
                    callResult.outputArguments.length +
168
                    " " +
169
                    obj[methodName].outputArguments.length
170
            );
171
        }
172
        const output: Record<string, unknown> = {};
7✔
173
        methodDef.outputArguments.map((arg: Argument, index: number) => {
7✔
174
            const variant = callResult!.outputArguments![index];
2✔
175
            const propName = lowerFirstLetter(arg.name!);
2✔
176
            output[propName] = variant.value;
2✔
177
        });
178

179
        return { statusCode: callResult.statusCode, output };
7✔
180
    };
181
}
182

183
async function extractDataType(
184
    session: IBasicSessionReadAsyncSimple & IBasicSessionBrowseAsyncSimple,
185
    arg: ArgumentEx
186
): Promise<void> {
187
    if (arg.dataType && arg._basicDataType) {
165!
UNCOV
188
        return;
×
189
    }
190
    const dataType = await convertNodeIdToDataTypeAsync(session, arg.dataType);
165✔
191
    arg._basicDataType = dataType!;
165✔
192
}
193
/**
194
 
195
 * @private
196
 */
197
async function add_method(proxyManager: UAProxyManager, obj: any, reference: ReferenceDescription): Promise<void> {
198
    const session = proxyManager.session;
82✔
199

200
    const methodName = lowerFirstLetter(reference.browseName.name!);
82✔
201

202
    let inputArguments: ArgumentEx[] = [];
82✔
203
    let outputArguments: ArgumentEx[] = [];
82✔
204

205
    // tslint:disable:no-shadowed-variable
206
    const argumentDefinition = await session.getArgumentDefinition(reference.nodeId);
82✔
207
    inputArguments = (argumentDefinition.inputArguments as ArgumentEx[]) || [];
82!
208
    outputArguments = (argumentDefinition.outputArguments as ArgumentEx[]) || [];
82!
209

210
    const promises: Promise<void>[] = [];
82✔
211
    for (const arg of inputArguments || []) {
82!
212
        promises.push(extractDataType(session, arg));
123✔
213
    }
214
    for (const arg of outputArguments || []) {
82!
215
        promises.push(extractDataType(session, arg));
42✔
216
    }
217
    await Promise.all(promises);
82✔
218

219
    const methodObj: MethodDescription = {
82✔
220
        browseName: methodName,
221
        executableFlag: false,
222
        func: makeFunction(obj, methodName) as any,
223
        nodeId: reference.nodeId,
224
        inputArguments,
225
        outputArguments
226
    };
227
    obj.$methods[methodName] = methodObj;
82✔
228
    obj[methodName] = methodObj.func;
82✔
229

230
    obj[methodName].inputArguments = inputArguments;
82✔
231
    obj[methodName].outputArguments = outputArguments;
82✔
232

233
    doDebug && debugLog("installing method name", methodName);
82!
234
    await proxyManager._monitor_execution_flag(methodObj);
82✔
235
}
236

237
async function add_component(proxyManager: UAProxyManager, obj: any, reference: ReferenceDescription): Promise<void> {
238
    const name = lowerFirstLetter(reference.browseName.name || "");
323!
239
    await proxyManager.getObject(reference.nodeId);
323✔
240
    const childObj = new ObjectExplorer({
323✔
241
        name,
242
        nodeId: reference.nodeId,
243
        parent: obj,
244
        proxyManager
245
    });
246
    obj[name] = childObj;
323✔
247
    obj.$components.push(childObj);
323✔
248
    await childObj.$resolve();
323✔
249
}
250

251
async function addFolderElement(proxyManager: UAProxyManager, obj: any, reference: ReferenceDescription): Promise<void> {
252

253
    const name = lowerFirstLetter(reference.browseName.name || "");
10!
254

255
    const childObj = new ObjectExplorer({
10✔
256
        name,
257
        nodeId: reference.nodeId,
258
        parent: obj,
259
        proxyManager
260
    });
261

262
    obj[name] = childObj;
10✔
263
    obj.$organizes.push(childObj);
10✔
264
    await childObj.$resolve();
10✔
265
}
266

267
async function add_property(proxyManager: UAProxyManager, obj: any, reference: ReferenceDescription): Promise<void> {
268
  
269
    const name = lowerFirstLetter(reference.browseName.name || "");
317!
270

271
    obj[name] = new ProxyVariable(proxyManager, reference.nodeId, reference);
317✔
272
    obj.$properties[name] = obj[name];
317✔
273
}
274

275
async function add_typeDefinition(proxyManager: UAProxyManager, obj: any, references: ReferenceDescription[]): Promise<void> {
276
    references = references || [];
381!
277
    if (references.length !== 1) {
381!
UNCOV
278
        return;
×
279
    }
280
    const reference = references[0];
381✔
281
    assert(!obj.typeDefinition, "type definition can only be set once");
381✔
282
    obj.typeDefinition = reference.browseName.name || "";
381!
283
}
284

285
async function addFromState(proxyManager: UAProxyManager, obj: any, reference: ReferenceDescription): Promise<void> {
286
    const childObj = await proxyManager.getObject(reference.nodeId);
29✔
287
    obj.$fromState = childObj;
29✔
288
}
289

290
async function addToState(proxyManager: UAProxyManager, obj: any, reference: ReferenceDescription): Promise<void> {
291
    const childObj = await proxyManager.getObject(reference.nodeId);
29✔
292
    obj.$toState = childObj;
29✔
293
}
294
export class ObjectExplorer  {
1✔
295
    public proxyManager: UAProxyManager;
296
    public name: string;
297
    public nodeId: NodeId;
298
    public parent: any;
299

300
    constructor(options: ObjectExplorerOptions) {
301
        this.proxyManager = options.proxyManager;
333✔
302
        this.name = options.name;
333✔
303
        this.nodeId = options.nodeId;
333✔
304
        this.parent = options.parent;
333✔
305
    }
306

307
    public async $resolve(): Promise<void> {
308
        const childObj = await this.proxyManager.getObject(this.nodeId);
333✔
309
        this.parent[this.name] = childObj;
333✔
310
        this.parent.$components.push(childObj);
333✔
311
    }
312
}
313

314
function t(references: ReferenceDescription[] | null) {
UNCOV
315
    if (!references) return "";
×
UNCOV
316
    return references.map((r: ReferenceDescription) => r.browseName.name + " " + r.nodeId.toString());
×
317
}
318

319
export async function readUAStructure(proxyManager: UAProxyManager, obj: { nodeId: NodeId }): Promise<ProxyNode> {
1✔
320
    const session = proxyManager.session;
385✔
321

322
    //   0   Object
323
    //   1   Variable
324
    //   2   Method
325
    const nodeId = obj.nodeId;
385✔
326
    const nodesToBrowse = [
385✔
327
        // 0. Components (except Methods)
328
        {
329
            // BrowseDescription
330
            browseDirection: BrowseDirection.Forward,
331
            includeSubtypes: true,
332
            nodeClassMask: makeNodeClassMask("Object | Variable"), // we don't want Method here
333
            nodeId,
334
            referenceTypeId: makeRefId("HasComponent"),
335
            resultMask
336
        },
337
        // 1. Properties
338
        {
339
            // BrowseDescription
340
            browseDirection: BrowseDirection.Forward,
341
            includeSubtypes: true,
342
            // nodeClassMask: makeNodeClassMask("Variable"),
343
            nodeId,
344
            referenceTypeId: makeRefId("HasProperty"),
345
            resultMask
346
        },
347

348
        // 2.  Methods
349
        {
350
            // BrowseDescription
351
            browseDirection: BrowseDirection.Forward,
352
            includeSubtypes: true,
353
            nodeClassMask: makeNodeClassMask("Method"),
354
            nodeId,
355
            referenceTypeId: makeRefId("HasComponent"),
356
            resultMask
357
        },
358
        // TypeDefinition
359
        {
360
            // BrowseDescription
361
            browseDirection: BrowseDirection.Both,
362
            includeSubtypes: true,
363
            nodeId,
364
            referenceTypeId: makeRefId("HasTypeDefinition"),
365
            resultMask
366
        },
367
        // FromState
368
        {
369
            // BrowseDescription
370
            browseDirection: BrowseDirection.Forward,
371
            includeSubtypes: true,
372
            nodeId,
373
            referenceTypeId: makeRefId("FromState"),
374
            resultMask
375
        },
376
        // ToState
377
        {
378
            // BrowseDescription
379
            browseDirection: BrowseDirection.Forward,
380
            includeSubtypes: true,
381
            nodeId,
382
            referenceTypeId: makeRefId("ToState"),
383
            resultMask
384
        },
385
        // (for folders ) Organizes
386
        {
387
            // BrowseDescription
388
            browseDirection: BrowseDirection.Forward,
389
            includeSubtypes: true,
390
            nodeId,
391
            referenceTypeId: makeRefId("Organizes"),
392
            resultMask
393
        }
394
    ];
395
    const browseResults = await session.browse(nodesToBrowse);
385✔
396
    // istanbul ignore next
397
    if (doDebug) {
398
        debugLog("Components", t(browseResults[0].references));
399
        debugLog("Properties", t(browseResults[1].references));
400
        debugLog("Methods", t(browseResults[2].references));
401
    }
402

403
    const promises: Promise<void>[] = [];
385✔
404

405
    for (const reference of browseResults[0].references || []) {
385!
406
        promises.push(add_component(proxyManager, obj, reference));
323✔
407
    }
408
    for (const reference of browseResults[1].references || []) {
385!
409
        promises.push(add_property(proxyManager, obj, reference));
317✔
410
    }
411
    for (const reference of browseResults[2].references || []) {
385!
412
        promises.push(add_method(proxyManager, obj, reference));
82✔
413
    }
414
    browseResults[3].references &&
385✔
415
        browseResults[3].references.length &&
416
        promises.push(add_typeDefinition(proxyManager, obj, browseResults[3].references || []));
381!
417
    browseResults[4].references &&
385✔
418
        browseResults[4].references.length &&
419
        promises.push(addFromState(proxyManager, obj, browseResults[4].references[0]));
420
    browseResults[5].references &&
385✔
421
        browseResults[5].references.length &&
422
        promises.push(addToState(proxyManager, obj, browseResults[5].references[0]));
423
    for (const reference of browseResults[6].references || []) {
385!
424
        promises.push(addFolderElement(proxyManager, obj, reference));
10✔
425
    }
426

427
    await Promise.all(promises);
385✔
428
    return obj as ProxyNode;
385✔
429
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc