• 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

87.72
/packages/node-opcua-client-crawler/source/node_crawler_base.ts
1
/**
2
 * @module node-opcua-client-crawler
3
 */
4
import { EventEmitter } from "events";
1✔
5
import async from "async";
1✔
6

7
import {
1✔
8
    assert,
9
    AttributeIds,
10
    browseAll,
11
    BrowseDescription,
12
    BrowseDescriptionLike,
13
    BrowseDirection,
14
    BrowseResult,
15
    checkDebugFlag,
16
    coerceLocalizedText,
17
    coerceQualifiedName,
18
    DataTypeDefinition,
19
    DataValue,
20
    ErrorCallback,
21
    IBasicSessionAsync2,
22
    LocalizedText,
23
    make_debugLog,
24
    make_warningLog,
25
    makeNodeId,
26
    makeResultMask,
27
    NodeClass,
28
    NodeId,
29
    NodeIdLike,
30
    QualifiedName,
31
    ReadValueIdOptions,
32
    ReferenceDescription,
33
    ReferenceTypeIds,
34
    resolveNodeId,
35
    sameNodeId,
36
    StatusCodes,
37
    VariableIds
38
} from "node-opcua-client";
39

40
import {
1✔
41
    CacheNodeReferenceType,
42
    CacheNodeVariableType,
43
    CacheNodeObjectType,
44
    CacheNodeVariable,
45
    CacheNode,
46
    CacheNodeDataType
47
} from "./cache_node";
48
import {
1✔
49
    pendingBrowseName,
50
    TaskExtraReference,
51
    Task,
52
    TaskBase,
53
    TaskBrowseNode,
54
     EmptyCallback,
55
    TaskCrawl,
56
    TaskProcessBrowseResponse,
57
    dedup_reference
58
} from "./private";
59

60
const debugLog = make_debugLog(__filename);
1✔
61
const doDebug = checkDebugFlag(__filename);
1✔
62
const doDebug1 = doDebug && false;
1!
63
const warningLog = make_warningLog(__filename);
1✔
64

65
console.log("+-------------------------------------------------------------------------------------+");
1✔
66
console.log("| Warning:                                                                            |");
1✔
67
console.log("| node-opcua-client-crawler module has been deprecated and is not maintained anymore. |");
1✔
68
console.log("| Please use '@sterfive/crawler' instead.                                             |");
1✔
69
console.log("| '@sterfive/crawler' is available to the NodeOPCUA Subscription members              |");
1✔
70
console.log("+-------------------------------------------------------------------------------------+");
1✔
71

72
//                         "ReferenceType | IsForward | BrowseName | NodeClass | DisplayName | TypeDefinition"
73
const resultMask = makeResultMask("ReferenceType | IsForward | BrowseName | DisplayName | NodeClass | TypeDefinition");
1✔
74

75
function zip<T1, T2>(arrA: T1[], arrB: T2[]): [T1, T2][] {
76
    return arrA.map((value, idx) => [value, arrB[idx]]);
338,995✔
77
}
78

79
function make_node_attribute_key(nodeId: NodeId, attributeId: AttributeIds): string {
80
    return nodeId.toString() + "_" + AttributeIds[attributeId];
1,348,263✔
81
}
82
function convertToStandardArray(a: number[] | Uint32Array | undefined | null): number[] | undefined {
83
    if (a === undefined || a === null) {
35,233✔
84
        return undefined;
29,544✔
85
    }
86
    if (a instanceof Array) {
5,689!
UNCOV
87
        return a;
×
88
    }
89
    if (a instanceof Buffer) {
5,689!
UNCOV
90
        return a;
×
91
    }
92
    try {
5,689✔
93
        const b: number[] = [];
5,689✔
94
        for (const x of a) {
5,689✔
95
            b.push(x);
5,874✔
96
        }
97
        return b;
5,689✔
98
    } catch (err) {
UNCOV
99
        warningLog(a);
×
100
        warningLog("convertToStandardArray error", (err as Error).message);
×
101
        return a as unknown as number[];
×
102
    }
103
}
104

105
//
106
// some server do not expose the ReferenceType Node in their address space
107
// ReferenceType are defined by the OPCUA standard and can be pre-populated in the crawler.
108
// Pre-populating the ReferenceType node in the crawler will also reduce the network traffic.
109
//
110
/*=
111
 *
112
 * @param arr
113
 * @param maxNode
114
 * @private
115
 * @return {*}
116
 */
117
function _fetch_elements<T>(arr: T[], maxNode: number): T[] {
118
    assert(Array.isArray(arr));
14,475✔
119
    assert(arr.length > 0);
14,475✔
120
    const highLimit = maxNode <= 0 ? arr.length : maxNode;
14,475!
121
    const tmp = arr.splice(0, highLimit);
14,475✔
122
    assert(tmp.length > 0);
14,475✔
123
    return tmp;
14,475✔
124
}
125

126
type CacheNodeWithAbstractField = CacheNodeReferenceType | CacheNodeVariableType | CacheNodeObjectType;
127
type CacheNodeWithDataTypeField = CacheNodeVariable | CacheNodeVariableType;
128
type CacheNodeWithAccessLevelField = CacheNodeVariable;
129

130
const referencesNodeId = resolveNodeId("References");
1✔
131
// const hierarchicalReferencesId = resolveNodeId("HierarchicalReferences");
132
const hasTypeDefinitionNodeId = resolveNodeId("HasTypeDefinition");
1✔
133

134
function _setExtraReference(task: TaskExtraReference, callback: ErrorCallback) {
UNCOV
135
    const param = task.param;
×
136
    assert(param.userData.setExtraReference);
×
137
    param.userData.setExtraReference!(param.parentNode, param.reference, param.childCacheNode, param.userData);
×
138
    callback();
×
139
}
140

141
export interface UserData {
142
    onBrowse: (crawler: NodeCrawlerBase, cacheNode: CacheNode, userData: UserData) => void;
143
    setExtraReference?: (parentNode: CacheNode, reference: any, childCacheNode: CacheNode, userData: UserData) => void;
144
}
145

146
interface NodeCrawlerEvents {
147
    on(event: "browsed", handler: (cacheNode: CacheNode, userData: UserData) => void): void;
148
}
149

150
export interface NodeCrawlerClientSession {
151
    read(nodesToRead: ReadValueIdOptions[]): Promise<DataValue[]>;
152
    browse(nodesToBrowse: BrowseDescriptionLike[]): Promise<BrowseResult[]>;
153
    browseNext(continuationPoints: Buffer[], releaseContinuationPoints: boolean): Promise<BrowseResult[]>;
154
}
155

156
type ReadNodeAction = (value: any, dataValue: DataValue) => void;
157

158
interface TaskReadNode {
159
    nodeToRead: {
160
        attributeId: AttributeIds;
161
        nodeId: NodeId;
162
    };
163
    action: ReadNodeAction;
164
}
165

166
function getReferenceTypeId(referenceType: undefined | string | NodeId ): NodeId | null {
167
    if (!referenceType) {
64,918✔
168
        return null;
34,639✔
169
    }
170
    /* istanbul ignore next */
171
    if (referenceType.toString() === "i=45" || referenceType === "HasSubtype") {
172
        return NodeId.resolveNodeId("i=45");
173
    } else if (referenceType.toString() === "i=35" || referenceType === "Organizes") {
174
        return NodeId.resolveNodeId("i=35");
175
    } else if (referenceType.toString() === "i=47" || referenceType === "HasComponent") {
176
        return NodeId.resolveNodeId("i=47");
177
    } else if (referenceType.toString() === "i=46" || referenceType === "HasProperty") {
178
        return NodeId.resolveNodeId("i=46");
179
    } else if (referenceType.toString() === NodeId.resolveNodeId("HasEncoding").toString() || referenceType === "HasEncoding") {
180
        return NodeId.resolveNodeId("HasEncoding");
181
    } else if (
182
        referenceType.toString() === NodeId.resolveNodeId("HasDescription").toString() ||
183
        referenceType === "HasDescription"
184
    ) {
185
        return NodeId.resolveNodeId("HasDescription");
186
    } else if (referenceType.toString() === "i=31" || referenceType === "References") {
187
        return NodeId.resolveNodeId("i=31");
188
    } else {
189
        warningLog("Invalid or Unknown reference Type" + referenceType.toString());
190
        return null;
191
    }
192
}
193

194
export type Pojo = Record<string, unknown>;
195
export type ObjectMap = { [key: string]: Pojo };
196

197
// tslint:disable:max-classes-per-file
198
/**
199
 * @class NodeCrawlerBase
200
 * @param session
201
 * @constructor
202
 * @deprecated  the "node-opcua-client-crawler" is now deprecated.
203
 *              use NodeCrawlerBase from "@sterfive/crawler".
204
 *              Contact contact@sterfive.com for License information.
205
 *
206
 */
207
export class NodeCrawlerBase extends EventEmitter implements NodeCrawlerEvents {
1✔
208
    public static follow(
209
        crawler: NodeCrawlerBase,
210
        cacheNode: CacheNode,
211
        userData: UserData,
212
        referenceType?: string ,
213
        browseDirection?: BrowseDirection
214
    ): void {
215
        const referenceTypeNodeId = getReferenceTypeId(referenceType);
64,918✔
216

217
        for (const reference of cacheNode.references) {
64,918✔
218
            if (browseDirection! === BrowseDirection.Forward && !reference.isForward) {
146,431!
UNCOV
219
                continue;
×
220
            }
221
            if (browseDirection! === BrowseDirection.Inverse && reference.isForward) {
146,431!
UNCOV
222
                continue;
×
223
            }
224

225
            if (!referenceTypeNodeId) {
146,431✔
226
                crawler.followReference(cacheNode, reference, userData);
85,915✔
227
            } else {
228
                if (NodeId.sameNodeId(referenceTypeNodeId, reference.referenceTypeId)) {
60,516✔
229
                    crawler.followReference(cacheNode, reference, userData);
10,082✔
230
                }
231
            }
232
        }
233
    }
234

235
    public maxNodesPerRead = 0;
20✔
236
    public maxNodesPerBrowse = 0;
20✔
237
    public startTime: Date = new Date();
20✔
238
    public readCounter = 0;
20✔
239
    public browseCounter = 0;
20✔
240
    public browseNextCounter = 0;
20✔
241
    public transactionCounter = 0;
20✔
242
    private readonly session: NodeCrawlerClientSession;
243
    private readonly browseNameMap: ObjectMap;
244
    private readonly taskQueue: async.QueueObject<TaskBase>;
245
    private readonly pendingReadTasks: TaskReadNode[];
246
    private readonly pendingBrowseTasks: TaskBrowseNode[];
247

248
    protected readonly _objectCache: { [key: string]: CacheNode };
249
    private _crawled: Set<string>;
250
    private _visitedNode: Set<string>;
251
    private _prePopulatedSet = new WeakSet();
20✔
252

253
    constructor(session: NodeCrawlerClientSession) {
254
        super();
20✔
255

256
        this.session = session;
20✔
257
        // verify that session object provides the expected methods (browse/read)
258
        this.browseNameMap = {};
20✔
259
        this._objectCache = {};
20✔
260
        this._crawled = new Set<string>();
20✔
261
        this._visitedNode = new Set<string>();
20✔
262

263
        this._initialize_referenceTypeId();
20✔
264

265
        this.pendingReadTasks = [];
20✔
266
        this.pendingBrowseTasks = [];
20✔
267

268
        this.taskQueue = async.queue((task: TaskBase, callback: EmptyCallback) => {
20✔
269
            // use process next tick to relax the stack frame
270

271
            /* istanbul ignore next */
272
            if (doDebug) {
273
                debugLog(" executing Task ", task.name); // JSON.stringify(task, null, " "));
274
            }
275

276
            setImmediate(() => {
196,684✔
277
                task.func.call(this, task, () => {
196,684✔
278
                    this.resolve_deferred_browseNode();
196,684✔
279
                    this.resolve_deferred_readNode();
196,684✔
280
                    callback();
196,684✔
281
                });
282
            });
283
        }, 1);
284

285
        // MaxNodesPerRead from Server.ServerCapabilities.OperationLimits
286
        // VariableIds.ServerType_ServerCapabilities_OperationLimits_MaxNodesPerRead
287
        this.maxNodesPerRead = 0;
20✔
288

289
        //  MaxNodesPerBrowse from Server.ServerCapabilities.OperationLimits
290
        //  VariableIds.Server_ServerCapabilities_OperationLimits_MaxNodesPerBrowse
291
        this.maxNodesPerBrowse = 0; // 0 = no limits
20✔
292

293
        // statistics
294
        this.startTime = new Date();
20✔
295
        this.readCounter = 0;
20✔
296
        this.browseCounter = 0;
20✔
297
        this.transactionCounter = 0;
20✔
298
    }
299
    public dispose(): void {
300
        assert(this.pendingReadTasks.length === 0);
20✔
301
        assert(this.pendingBrowseTasks.length === 0);
20✔
302

303
        this.pendingReadTasks.length = 0;
20✔
304
        this.pendingBrowseTasks.length = 0;
20✔
305

306
        assert(this.taskQueue.length() === 0);
20✔
307

308
        Object.values(this._objectCache).map((cache) => (cache as CacheNode).dispose());
45,854✔
309
        this.taskQueue.kill();
20✔
310

311
        (this as any).session = null;
20✔
312
        (this as any).browseNameMap = null;
20✔
313
        (this as any).taskQueue = null;
20✔
314
        (this as any)._objectCache = {};
20✔
315
        (this as any)._crawled = null;
20✔
316
        (this as any)._visitedNode = null;
20✔
317
        (this as any)._prePopulatedSet = null;
20✔
318
    }
319

320
    public toString(): string {
UNCOV
321
        return (
×
322
            "" +
323
            `reads:       ${this.readCounter}\n` +
324
            `browses:     ${this.browseCounter}  \n` +
325
            `transaction: ${this.transactionCounter}  \n`
326
        );
327
    }
328

329
    public crawl(nodeId: NodeIdLike, userData: UserData): Promise<void>;
330
    public crawl(nodeId: NodeIdLike, userData: UserData, endCallback: ErrorCallback): void;
331
    public crawl(nodeId: NodeIdLike, userData: UserData, ...args: any[]): any {
332
        const endCallback = args[0] as ErrorCallback;
22✔
333
        assert(typeof endCallback === "function", "expecting callback");
22✔
334
        nodeId = resolveNodeId(nodeId) as NodeId;
22✔
335
        assert(typeof endCallback === "function");
22✔
336
        this._readOperationalLimits((err?: Error) => {
22✔
337
            /* istanbul ignore next */
338
            if (err) {
339
                return endCallback(err);
340
            }
341
            this._inner_crawl(nodeId as NodeId, userData, endCallback);
22✔
342
        });
343
    }
344

345
    /**
346
     * @internal
347
     * @private
348
     */
349
    private _inner_crawl(nodeId: NodeId, userData: UserData, endCallback: ErrorCallback) {
350
        assert(userData !== null && typeof userData === "object");
22✔
351
        assert(typeof endCallback === "function");
22✔
352

353
        let hasEnded = false;
22✔
354

355
        this.taskQueue.drain(() => {
22✔
356
            debugLog("taskQueue is empty !!", this.taskQueue.length());
22✔
357

358
            if (!hasEnded) {
22!
359
                hasEnded = true;
22✔
360
                this._visitedNode = new Set<string>();
22✔
361
                this._crawled = new Set<string>();
22✔
362
                this.emit("end");
22✔
363
                endCallback();
22✔
364
            }
365
        });
366

367
        let cacheNode = this._getCacheNode(nodeId);
22✔
368
        if (!cacheNode) {
22✔
369
            cacheNode = this._createCacheNode(nodeId);
21✔
370
        }
371

372
        // ----------------------- Read missing essential information about node
373
        // such as nodeClass, typeDefinition browseName, displayName
374
        // this sequence is only necessary on the top node being crawled,
375
        // as browseName,displayName,nodeClass will be provided by ReferenceDescription later on for child nodes
376
        //
377
        async.parallel(
22✔
378
            {
379
                task1: (callback: ErrorCallback) => {
380
                    this._defer_readNode(
22✔
381
                        cacheNode.nodeId,
382
                        AttributeIds.BrowseName,
383

384
                        (err: Error | null, value?: QualifiedName) => {
385
                            /* istanbul ignore else */
386
                            if (err) {
22!
UNCOV
387
                                return callback(err);
×
388
                            }
389
                            if (!(value instanceof QualifiedName)) {
22!
UNCOV
390
                                warningLog(" node ", cacheNode.nodeId.toString(), " has a invalid browseName", value);
×
391
                                cacheNode.browseName = coerceQualifiedName("<INVALID BROWSE NAME>");
×
392
                            } else {
393
                                cacheNode.browseName = value;
22✔
394
                            }
395
                            setImmediate(callback);
22✔
396
                        }
397
                    );
398
                },
399

400
                task2: (callback: ErrorCallback) => {
401
                    this._defer_readNode(cacheNode.nodeId, AttributeIds.NodeClass, (err: Error | null, value?: NodeClass) => {
22✔
402
                        /* istanbul ignore else */
403
                        if (err) {
22!
UNCOV
404
                            return callback(err);
×
405
                        }
406
                        cacheNode.nodeClass = value!;
22✔
407
                        setImmediate(callback);
22✔
408
                    });
409
                },
410

411
                task3: (callback: ErrorCallback) => {
412
                    this._defer_readNode(
22✔
413
                        cacheNode.nodeId,
414
                        AttributeIds.DisplayName,
415

416
                        (err: Error | null, value?: LocalizedText) => {
417
                            /* istanbul ignore else */
418
                            if (err) {
22!
UNCOV
419
                                return callback(err);
×
420
                            }
421
                            if (!(value instanceof LocalizedText)) {
22!
UNCOV
422
                                warningLog(" node ", cacheNode.nodeId.toString(), " has a invalid displayName", value);
×
423
                                cacheNode.displayName = coerceLocalizedText("<INVALID LOCALIZED NAME>")!;
×
424
                            } else {
425
                                cacheNode.displayName = value;
22✔
426
                            }
427
                            setImmediate(callback);
22✔
428
                        }
429
                    );
430
                },
431

432
                task4: (callback: ErrorCallback) => {
433
                    this._resolve_deferred_readNode(callback);
22✔
434
                }
435
            },
436
            (err?: Error | null, data?: any) => {
437
                this._add_crawl_task(cacheNode, userData);
22✔
438
            }
439
        );
440
    }
441

442
    private _add_crawl_task(cacheNode: CacheNode, userData: UserData) {
443
        assert(userData);
45,843✔
444
        assert(this !== null && typeof this === "object");
45,843✔
445

446
        const key = cacheNode.nodeId.toString();
45,843✔
447

448
        /* istanbul ignore else */
449
        if (this._crawled.has(key)) {
45,843!
UNCOV
450
            return;
×
451
        }
452
        this._crawled.add(key);
45,843✔
453

454
        const task: TaskCrawl = {
45,843✔
455
            func: NodeCrawlerBase.prototype._crawl_task,
456
            param: {
457
                cacheNode,
458
                userData
459
            }
460
        };
461
        this._push_task("_crawl task", task);
45,843✔
462
    }
463

464
    public followReference(parentNode: CacheNode, reference: ReferenceDescription, userData: UserData): void {
465
        assert(reference instanceof ReferenceDescription);
99,097✔
466

467
        let referenceTypeIdCacheNode = this._getCacheNode(reference.referenceTypeId);
99,097✔
468
        if (this._prePopulatedSet.has(referenceTypeIdCacheNode)) {
99,097✔
469
            this._prePopulatedSet.delete(referenceTypeIdCacheNode);
8✔
470
            this._add_crawl_task(referenceTypeIdCacheNode, userData);
8✔
471
        }
472
        if (!referenceTypeIdCacheNode) {
99,097✔
473
            referenceTypeIdCacheNode = this._createCacheNode(reference.referenceTypeId);
127✔
474
            referenceTypeIdCacheNode.nodeClass = NodeClass.ReferenceType;
127✔
475
            this._add_crawl_task(referenceTypeIdCacheNode, userData);
127✔
476
        }
477

478
        let childCacheNode = this._getCacheNode(reference.nodeId);
99,097✔
479
        if (!childCacheNode) {
99,097✔
480
            childCacheNode = this._createCacheNode(reference.nodeId, parentNode, reference);
45,686✔
481
            childCacheNode.browseName = reference.browseName;
45,686✔
482
            childCacheNode.displayName = reference.displayName;
45,686✔
483
            childCacheNode.typeDefinition = reference.typeDefinition;
45,686✔
484
            childCacheNode.nodeClass = reference.nodeClass as NodeClass;
45,686✔
485
            assert(childCacheNode.parent === parentNode);
45,686✔
486
            assert(childCacheNode.referenceToParent === reference);
45,686✔
487

488
            this._add_crawl_task(childCacheNode, userData);
45,686✔
489
        } else {
490
            if (userData.setExtraReference) {
53,411!
UNCOV
491
                const task: TaskExtraReference = {
×
492
                    func: _setExtraReference,
493
                    param: {
494
                        childCacheNode,
495
                        parentNode,
496
                        reference,
497
                        userData
498
                    }
499
                };
UNCOV
500
                this._push_task("setExtraRef", task);
×
501
            }
502
        }
503
    }
504

505
    /**
506
     * perform pending read Node operation
507

508
     * @param callback
509
     * @private
510
     * @internal
511
     */
512
    private _resolve_deferred_readNode(callback: ErrorCallback): void {
513
        if (this.pendingReadTasks.length === 0) {
79,405✔
514
            // nothing to read
515
            callback();
68,773✔
516
            return;
68,773✔
517
        }
518

519
        doDebug1 && debugLog("_resolve_deferred_readNode = ", this.pendingReadTasks.length);
10,632!
520

521
        const selectedPendingReadTasks: TaskReadNode[] = _fetch_elements(this.pendingReadTasks, this.maxNodesPerRead);
10,632✔
522

523
        const nodesToRead = selectedPendingReadTasks.map((e: TaskReadNode) => e.nodeToRead);
293,152✔
524

525
        this.readCounter += nodesToRead.length;
10,632✔
526
        this.transactionCounter++;
10,632✔
527

528
        this.session
10,632✔
529
            .read(nodesToRead)
530
            .then((dataValues) => {
531
                for (const [readTask, dataValue] of zip(selectedPendingReadTasks, dataValues)) {
10,632✔
532
                    assert(Object.prototype.hasOwnProperty.call(dataValue, "statusCode"));
293,152✔
533
                    if (dataValue.statusCode.equals(StatusCodes.Good)) {
293,152✔
534
                        /* istanbul ignore else */
535
                        if (dataValue.value === null) {
291,945!
UNCOV
536
                            readTask.action(null, dataValue);
×
537
                        } else {
538
                            readTask.action(dataValue.value.value, dataValue);
539
                        }
540
                    } else {
541
                        readTask.action({ name: dataValue.statusCode.toString() }, dataValue);
1,207✔
542
                    }
543
                }
544
                callback();
10,632✔
545
            })
546
            .catch((err: Error) => {
UNCOV
547
                callback(err);
×
548
            });
549
    }
550

551
    private _resolve_deferred_browseNode(callback: ErrorCallback): void {
552
        if (this.pendingBrowseTasks.length === 0) {
67,615✔
553
            callback();
63,772✔
554
            return;
63,772✔
555
        }
556

557
        doDebug1 && debugLog("_resolve_deferred_browseNode = ", this.pendingBrowseTasks.length);
3,843!
558

559
        const objectsToBrowse: TaskBrowseNode[] = _fetch_elements(this.pendingBrowseTasks, this.maxNodesPerBrowse);
3,843✔
560

561
        const nodesToBrowse = objectsToBrowse.map((e: TaskBrowseNode) => {
3,843✔
562
            assert(Object.prototype.hasOwnProperty.call(e, "referenceTypeId"));
45,843✔
563

564
            return new BrowseDescription({
45,843✔
565
                browseDirection: BrowseDirection.Forward,
566
                includeSubtypes: true,
567
                nodeId: e.nodeId,
568
                referenceTypeId: e.referenceTypeId,
569
                resultMask
570
            });
571
        });
572

573
        this.browseCounter += nodesToBrowse.length;
3,843✔
574
        this.transactionCounter++;
3,843✔
575

576
        browseAll(this.session as IBasicSessionAsync2, nodesToBrowse)
3,843✔
577
            .then((browseResults?: BrowseResult[]) => {
578
                assert(browseResults!.length === nodesToBrowse.length);
3,843✔
579
                browseResults = browseResults || [];
3,843!
580
                const task: TaskProcessBrowseResponse = {
3,843✔
581
                    func: NodeCrawlerBase.prototype._process_browse_response_task,
582
                    param: {
583
                        browseResults,
584
                        objectsToBrowse
585
                    }
586
                };
587
                this._unshift_task("process browseResults", task);
3,843✔
588
                callback();
3,843✔
589
            })
590
            .catch((err) => {
UNCOV
591
                debugLog("session.browse err:", err);
×
592
                return callback(err || undefined);
×
593
            });
594
    }
595
    /**
596

597
     * add a task on top of the queue (high priority)
598
     * @param name
599
     * @param task
600
     * @private
601
     */
602
    private _unshift_task(name: string, task: Task) {
603
        assert(typeof task.func === "function");
3,843✔
604
        assert(task.func.length === 2);
3,843✔
605
        task.name = task.name || name;
3,843✔
606
        this.taskQueue.unshift(task);
3,843✔
607
        doDebug1 && debugLog("unshift task", name);
3,843!
608
    }
609

610
    /**
611

612
     * add a task at the bottom of the queue (low priority)
613
     * @param name
614
     * @param task
615
     * @private
616
     */
617
    private _push_task(name: string, task: Task) {
618
        assert(typeof task.func === "function");
192,841✔
619
        assert(task.func.length === 2);
192,841✔
620
        doDebug1 && debugLog("pushing task", name);
192,841!
621
        task.name = task.name || name;
192,841✔
622
        this.taskQueue.push(task);
192,841✔
623
    }
624

625
    /***
626

627
     * @param cacheNode
628
     * @param userData
629
     * @private
630
     */
631
    private _emit_on_crawled(cacheNode: CacheNode, userData: UserData) {
632
        this.emit("browsed", cacheNode, userData);
45,843✔
633
    }
634

635
    private _crawl_task(task: TaskCrawl, callback: EmptyCallback) {
636
        const cacheNode = task.param.cacheNode;
45,843✔
637
        const nodeId = task.param.cacheNode.nodeId;
45,843✔
638
        const key = nodeId.toString();
45,843✔
639

640
        if (this._visitedNode.has(key)) {
45,843!
UNCOV
641
            debugLog("skipping already visited", key);
×
642
            callback();
×
643
            return; // already visited
×
644
        }
645
        // mark as visited to avoid infinite recursion
646
        this._visitedNode.add(key);
45,843✔
647

648
        const browseNodeAction = (err: Error | null, cacheNode1?: CacheNode) => {
45,843✔
649
            if (err || !cacheNode1) {
45,843!
UNCOV
650
                return;
×
651
            }
652
            for (const reference of cacheNode1.references) {
45,843✔
653
                // those ones come for free
654
                if (!this.has_cache_NodeAttribute(reference.nodeId, AttributeIds.BrowseName)) {
110,395✔
655
                    this.set_cache_NodeAttribute(reference.nodeId, AttributeIds.BrowseName, reference.browseName);
45,828✔
656
                }
657
                if (!this.has_cache_NodeAttribute(reference.nodeId, AttributeIds.DisplayName)) {
110,395✔
658
                    this.set_cache_NodeAttribute(reference.nodeId, AttributeIds.DisplayName, reference.displayName);
45,861✔
659
                }
660
                if (!this.has_cache_NodeAttribute(reference.nodeId, AttributeIds.NodeClass)) {
110,395✔
661
                    this.set_cache_NodeAttribute(reference.nodeId, AttributeIds.NodeClass, reference.nodeClass);
45,861✔
662
                }
663
            }
664
            this._emit_on_crawled(cacheNode1, task.param.userData);
45,843✔
665
            const userData = task.param.userData;
45,843✔
666
            if (userData.onBrowse) {
45,843✔
667
                userData.onBrowse(this, cacheNode1, userData);
45,841✔
668
            }
669
        };
670

671
        this._defer_browse_node(cacheNode, referencesNodeId, browseNodeAction);
45,843✔
672
        callback();
45,843✔
673
    }
674

675
    private _initialize_referenceTypeId() {
676
        const appendPrepopulatedReference = (browseName: string) => {
20✔
677
            const nodeId = makeNodeId((ReferenceTypeIds as any)[browseName], 0);
20✔
678
            assert(nodeId);
20✔
679
            const cacheNode = this._createCacheNode(nodeId);
20✔
680
            cacheNode.browseName = new QualifiedName({ name: browseName });
20✔
681
            cacheNode.nodeClass = NodeClass.ReferenceType;
20✔
682
            this._prePopulatedSet.add(cacheNode);
20✔
683
        };
684

685
        //  References
686
        //  +->(hasSubtype) NonHierarchicalReferences
687
        //                  +->(hasSubtype) HasTypeDefinition
688
        //  +->(hasSubtype) HierarchicalReferences
689
        //                  +->(hasSubtype) HasChild/ChildOf
690
        //                                  +->(hasSubtype) Aggregates/AggregatedBy
691
        //                                                  +-> HasProperty/PropertyOf
692
        //                                                  +-> HasComponent/ComponentOf
693
        //                                                  +-> HasHistoricalConfiguration/HistoricalConfigurationOf
694
        //                                 +->(hasSubtype) HasSubtype/HasSupertype
695
        //                  +->(hasSubtype) Organizes/OrganizedBy
696
        //                  +->(hasSubtype) HasEventSource/EventSourceOf
697
        appendPrepopulatedReference("HasSubtype");
20✔
698

699
        /* istanbul ignore else */
700
        if (false) {
20!
UNCOV
701
            appendPrepopulatedReference("HasTypeDefinition");
×
702
            appendPrepopulatedReference("HasChild");
×
703
            appendPrepopulatedReference("HasProperty");
×
704
            appendPrepopulatedReference("HasComponent");
×
705
            appendPrepopulatedReference("HasHistoricalConfiguration");
×
706
            appendPrepopulatedReference("Organizes");
×
707
            appendPrepopulatedReference("HasEventSource");
×
708
            appendPrepopulatedReference("HasModellingRule");
×
709
            appendPrepopulatedReference("HasEncoding");
×
710
            appendPrepopulatedReference("HasDescription");
×
711
        }
712
    }
713

714
    private _readOperationalLimits(callback: ErrorCallback) {
715
        const n1 = makeNodeId(VariableIds.Server_ServerCapabilities_OperationLimits_MaxNodesPerRead);
22✔
716
        const n2 = makeNodeId(VariableIds.Server_ServerCapabilities_OperationLimits_MaxNodesPerBrowse);
22✔
717
        const nodesToRead = [
22✔
718
            { nodeId: n1, attributeId: AttributeIds.Value },
719
            { nodeId: n2, attributeId: AttributeIds.Value }
720
        ];
721
        this.transactionCounter++;
22✔
722
        this.session
22✔
723
            .read(nodesToRead)
724
            .then((dataValues): void => {
725
                dataValues = dataValues!;
22✔
726
                const fix = (self: any, maxNodePerX: string, dataValue: DataValue) => {
22✔
727
                    if (dataValue.statusCode.equals(StatusCodes.Good)) {
44!
728
                        const value = dataValue.value.value;
44✔
729
                        // if this.maxNodesPerRead has been set (<>0) by the user before call is made,
730
                        // then it serve as a minimum
731
                        if (self[maxNodePerX]) {
44✔
732
                            if (value > 0) {
17✔
733
                                self[maxNodePerX] = Math.min(self[maxNodePerX], value);
8✔
734
                            }
735
                        } else {
736
                            self[maxNodePerX] = value;
27✔
737
                        }
738
                    } else {
UNCOV
739
                        debugLog(
×
740
                            "warning: server does not provide a valid dataValue for " + maxNodePerX,
741
                            dataValue.statusCode.toString()
742
                        );
743
                    }
744
                    // ensure we have a sensible maxNodesPerRead value in case the server doesn't specify one
745
                    self[maxNodePerX] = self[maxNodePerX] || 100;
44✔
746
                    debugLog(maxNodePerX, " set to ", self[maxNodePerX]);
44✔
747
                };
748

749
                fix(this, "maxNodesPerRead", dataValues[0]);
22✔
750
                fix(this, "maxNodesPerBrowse", dataValues[1]);
22✔
751
                callback();
22✔
752
            })
753
            .catch((err: Error) => {
UNCOV
754
                callback(err);
×
755
            });
756
    }
757

758
    private set_cache_NodeAttribute(nodeId: NodeId, attributeId: AttributeIds, value: any) {
759
        const key = make_node_attribute_key(nodeId, attributeId);
430,702✔
760
        this.browseNameMap[key] = value;
430,702✔
761
    }
762

763
    private has_cache_NodeAttribute(nodeId: NodeId, attributeId: AttributeIds) {
764
        const key = make_node_attribute_key(nodeId, attributeId);
624,361✔
765
        return Object.prototype.hasOwnProperty.call(this.browseNameMap, key);
624,361✔
766
    }
767

768
    private get_cache_NodeAttribute(nodeId: NodeId, attributeId: AttributeIds) {
769
        const key = make_node_attribute_key(nodeId, attributeId);
24✔
770
        return this.browseNameMap[key];
24✔
771
    }
772

773
    /**
774
     * request a read operation for a Node+Attribute in the future, provides a callback
775
     *
776

777
     * @param nodeId
778
     * @param attributeId
779
     * @param callback
780
     * @private
781
     * @internal
782
     */
783
    private _defer_readNode(
784
        nodeId: NodeId,
785
        attributeId: AttributeIds.Value,
786
        callback: (err: Error | null, value?: DataValue) => void
787
    ): void;
788
    private _defer_readNode(
789
        nodeId: NodeId,
790
        attributeId: AttributeIds.DisplayName | AttributeIds.Description | AttributeIds.InverseName,
791
        callback: (err: Error | null, value?: LocalizedText) => void
792
    ): void;
793
    private _defer_readNode(
794
        nodeId: NodeId,
795
        attributeId: AttributeIds.BrowseName,
796
        callback: (err: Error | null, value?: QualifiedName) => void
797
    ): void;
798
    private _defer_readNode(
799
        nodeId: NodeId,
800
        attributeId: AttributeIds.DataType,
801
        callback: (err: Error | null, value?: NodeId) => void
802
    ): void;
803
    private _defer_readNode(
804
        nodeId: NodeId,
805
        attributeId: AttributeIds.IsAbstract | AttributeIds.ContainsNoLoops,
806
        callback: (err: Error | null, value?: boolean) => void
807
    ): void;
808
    private _defer_readNode(
809
        nodeId: NodeId,
810
        attributeId: AttributeIds.DataTypeDefinition,
811
        callback: (err: Error | null, value?: DataTypeDefinition) => void
812
    ): void;
813
    private _defer_readNode(
814
        nodeId: NodeId,
815
        attributeId: AttributeIds.ArrayDimensions,
816
        callback: (err: Error | null, value?: number[] | Uint32Array) => void
817
    ): void;
818
    private _defer_readNode(
819
        nodeId: NodeId,
820
        attributeId:
821
            | AttributeIds.AccessLevel
822
            | AttributeIds.ValueRank
823
            | AttributeIds.UserAccessLevel
824
            | AttributeIds.MinimumSamplingInterval
825
            | AttributeIds.NodeClass,
826
        callback: (err: Error | null, value?: number) => void
827
    ): void;
828

829
    private _defer_readNode(nodeId: NodeId, attributeId: AttributeIds, callback: (err: Error | null, value?: any) => void): void {
830
        nodeId = resolveNodeId(nodeId);
293,176✔
831
        const key = make_node_attribute_key(nodeId, attributeId);
293,176✔
832
        if (this.has_cache_NodeAttribute(nodeId, attributeId)) {
293,176✔
833
            callback(null, this.get_cache_NodeAttribute(nodeId, attributeId));
24✔
834
        } else {
835
            //   this.browseNameMap[key] = { "?": 1 };
836
            this.pendingReadTasks.push({
293,152✔
837
                action: (value: any, dataValue: DataValue) => {
838
                    if (attributeId === AttributeIds.Value) {
293,152✔
839
                        this.set_cache_NodeAttribute(nodeId, attributeId, dataValue);
35,233✔
840
                        callback(null, dataValue);
35,233✔
841
                        return;
35,233✔
842
                    }
843
                    if (attributeId === AttributeIds.ArrayDimensions) {
257,919✔
844
                        value = dataValue.statusCode.isNotGood() ? null : value;
35,233!
845
                        this.set_cache_NodeAttribute(nodeId, attributeId, value);
35,233✔
846
                        callback(null, value);
35,233✔
847
                        return;
35,233✔
848
                    }
849
                    if (dataValue.statusCode.isNotGood()) {
222,686✔
850
                        this.set_cache_NodeAttribute(nodeId, attributeId, dataValue);
293✔
851
                        callback(null, null);
293✔
852
                        return;
293✔
853
                    }
854
                    if (dataValue.statusCode.isGood()) {
222,393!
855
                        this.set_cache_NodeAttribute(nodeId, attributeId, value);
222,393✔
856
                        callback(null, value);
222,393✔
857
                    } else {
UNCOV
858
                        callback(
×
859
                            new Error(
860
                                "Error " +
861
                                    dataValue.statusCode.toString() +
862
                                    " while reading " +
863
                                    nodeId.toString() +
864
                                    " attributeIds " +
865
                                    AttributeIds[attributeId]
866
                            )
867
                        );
868
                    }
869
                },
870
                nodeToRead: {
871
                    attributeId,
872
                    nodeId
873
                }
874
            });
875
        }
876
    }
877

878
    private _resolve_deferred(comment: string, collection: any[], method: (callback: EmptyCallback) => void) {
879
        if (collection.length > 0) {
393,368✔
880
            doDebug1 && debugLog("_resolve_deferred ", comment, collection.length);
146,998!
881
            this._push_task("adding operation " + comment, {
146,998✔
882
                func: (task: Task, callback: EmptyCallback) => {
883
                    debugLog("executing task", comment);
146,998✔
884
                    method.call(this, callback);
146,998✔
885
                },
886
                param: {}
887
            });
888
        }
889
    }
890

891
    private resolve_deferred_readNode() {
892
        this._resolve_deferred("read_node", this.pendingReadTasks, this._resolve_deferred_readNode);
196,684✔
893
    }
894

895
    private resolve_deferred_browseNode() {
896
        this._resolve_deferred("browse_node", this.pendingBrowseTasks, this._resolve_deferred_browseNode);
196,684✔
897
    }
898

899
    // ---------------------------------------------------------------------------------------
900

901
    private _getCacheNode(nodeId: NodeIdLike): CacheNode {
902
        const key = resolveNodeId(nodeId).toString();
198,216✔
903
        return this._objectCache[key];
198,216✔
904
    }
905

906
    private _createCacheNode(nodeId: NodeId, parentNode?: CacheNode, referenceToParent?: ReferenceDescription): CacheNode {
907
        const key = resolveNodeId(nodeId).toString();
45,854✔
908
        let cacheNode: CacheNode = this._objectCache[key];
45,854✔
909

910
        /* istanbul ignore else */
911
        if (cacheNode) {
45,854!
UNCOV
912
            throw new Error("NodeCrawlerBase#_createCacheNode :" + " cache node should not exist already : " + nodeId.toString());
×
913
        }
914
        const nodeClass = (referenceToParent ? referenceToParent!.nodeClass : NodeClass.Unspecified) as NodeClass;
45,854✔
915
        switch (nodeClass) {
45,854✔
916
            case NodeClass.Method:
917
                cacheNode = new CacheNode(nodeId);
2,771✔
918
                cacheNode.nodeClass = NodeClass.Method;
2,771✔
919
                break;
2,771✔
920
            case NodeClass.Object:
921
                cacheNode = new CacheNode(nodeId);
4,156✔
922
                cacheNode.nodeClass = NodeClass.Object;
4,156✔
923
                break;
4,156✔
924
            case NodeClass.ObjectType:
925
                cacheNode = new CacheNode(nodeId);
2,111✔
926
                cacheNode.nodeClass = NodeClass.ObjectType;
2,111✔
927
                break;
2,111✔
928
            case NodeClass.Variable:
929
                cacheNode = new CacheNodeVariable(nodeId);
34,737✔
930
                break;
34,737✔
931
            case NodeClass.VariableType:
932
                cacheNode = new CacheNodeVariableType(nodeId);
496✔
933
                break;
496✔
934
            default:
935
                cacheNode = new CacheNode(nodeId);
1,583✔
936
                cacheNode.nodeClass = nodeClass;
1,583✔
937
                break;
1,583✔
938
        }
939
        cacheNode.parent = parentNode;
45,854✔
940
        cacheNode.referenceToParent = referenceToParent;
45,854✔
941
        assert(!Object.prototype.hasOwnProperty.call(this._objectCache, key));
45,854✔
942
        this._objectCache[key] = cacheNode;
45,854✔
943
        return cacheNode;
45,854✔
944
    }
945

946
    /**
947
     * perform a deferred browse
948
     * instead of calling session.browse directly, this function add the request to a list
949
     * so that request can be grouped and send in one single browse command to the server.
950
     *
951

952
     * @private
953
     *
954
     */
955
    private _defer_browse_node(
956
        cacheNode: CacheNode,
957
        referenceTypeId: NodeId,
958
        actionOnBrowse: (err: Error | null, cacheNode?: CacheNode) => void
959
    ) {
960
        this.pendingBrowseTasks.push({
45,843✔
961
            action: (object: CacheNode) => actionOnBrowse(null, cacheNode),
45,843✔
962
            cacheNode,
963
            nodeId: cacheNode.nodeId,
964
            referenceTypeId
965
        });
966
    }
967

968
    private _process_single_browseResult(_objectToBrowse: TaskBrowseNode, browseResult: BrowseResult) {
969
        const cacheNode = _objectToBrowse.cacheNode as CacheNode;
45,843✔
970
        assert(this._visitedNode.has(cacheNode.nodeId.toString()));
45,843✔
971
        cacheNode.references = cacheNode.references.concat(browseResult.references!);
45,843✔
972
        this._process_single_browseResult2(_objectToBrowse);
45,843✔
973
    }
974

975
    private _process_single_browseResult2(_objectToBrowse: TaskBrowseNode) {
976
        const cacheNode = _objectToBrowse.cacheNode as CacheNode;
45,843✔
977

978
        // note : some OPCUA may expose duplicated reference, they need to be filtered out
979
        // dedup reference
980
        cacheNode.references = dedup_reference(cacheNode, cacheNode.references);
45,843✔
981

982
        // extract the reference containing HasTypeDefinition
983
        const tmp = cacheNode.references.filter((x) => sameNodeId(x.referenceTypeId, hasTypeDefinitionNodeId));
110,395✔
984
        if (tmp.length) {
45,843✔
985
            // istanbul ignore next
986
            if (tmp.length !== 1) {
987
                warningLog(`node ${cacheNode.nodeId.toString()} ${cacheNode.browseName.toString()} has two typeDefinitions}`);
988
            }
989
            // istanbul ignore next
990
            if (cacheNode.typeDefinition) {
991
                if (cacheNode.typeDefinition.toString() !== tmp[0].nodeId.toString()) {
992
                    warningLog(`node ${cacheNode.nodeId.toString()} ${cacheNode.browseName.toString()} has wrong typeDefinitions}`);
993
                }
994
            }
995
            cacheNode.typeDefinition = tmp[0].nodeId;
38,912✔
996
        }
997

998
        async.parallel(
45,843✔
999
            {
1000
                task1_read_browseName: (callback: ErrorCallback) => {
1001
                    if (cacheNode.browseName !== pendingBrowseName) {
45,843✔
1002
                        // browse name already processed
1003
                        return callback();
45,716✔
1004
                    }
1005
                    this._defer_readNode(
127✔
1006
                        cacheNode.nodeId,
1007
                        AttributeIds.BrowseName,
1008
                        (err: Error | null, browseName?: QualifiedName) => {
1009
                            cacheNode.browseName = browseName!;
127✔
1010
                            callback();
127✔
1011
                        }
1012
                    );
1013
                },
1014
                task2_read_displayName: (callback: ErrorCallback) => {
1015
                    if (cacheNode.displayName) {
45,843!
1016
                        // display name already processed
1017
                        return callback();
45,843✔
1018
                    }
UNCOV
1019
                    this._defer_readNode(cacheNode.nodeId, AttributeIds.DisplayName, (err: Error | null, value?: LocalizedText) => {
×
1020
                        if (err) {
×
1021
                            return callback(err);
×
1022
                        }
UNCOV
1023
                        cacheNode.displayName = value!;
×
1024
                        callback();
×
1025
                    });
1026
                },
1027
                task3_read_description: (callback: ErrorCallback) => {
1028
                    this._defer_readNode(cacheNode.nodeId, AttributeIds.Description, (err: Error | null, value?: LocalizedText) => {
45,843✔
1029
                        if (err) {
45,843!
1030
                            // description may not be defined and this is OK !
UNCOV
1031
                            return callback();
×
1032
                        }
1033
                        cacheNode.description = coerceLocalizedText(value)!;
45,843✔
1034
                        callback();
45,843✔
1035
                    });
1036
                },
1037
                task4_variable_dataType: (callback: ErrorCallback) => {
1038
                    // only if nodeClass is Variable || VariableType
1039
                    if (cacheNode.nodeClass !== NodeClass.Variable && cacheNode.nodeClass !== NodeClass.VariableType) {
45,843✔
1040
                        return callback();
10,610✔
1041
                    }
1042
                    const cache = cacheNode as CacheNodeWithDataTypeField;
35,233✔
1043
                    // read dataType and DataType if node is a variable
1044
                    this._defer_readNode(cacheNode.nodeId, AttributeIds.DataType, (err: Error | null, dataType?: NodeId) => {
35,233✔
1045
                        if (!(dataType instanceof NodeId)) {
35,233!
UNCOV
1046
                            return callback();
×
1047
                        }
1048
                        cache.dataType = dataType;
35,233✔
1049
                        callback();
35,233✔
1050
                    });
1051
                },
1052
                task5_variable_dataValue: (callback: ErrorCallback) => {
1053
                    // only if nodeClass is Variable || VariableType
1054
                    if (cacheNode.nodeClass !== NodeClass.Variable && cacheNode.nodeClass !== NodeClass.VariableType) {
45,843✔
1055
                        return callback();
10,610✔
1056
                    }
1057
                    const cache = cacheNode as CacheNodeVariable | CacheNodeVariableType;
35,233✔
1058
                    this._defer_readNode(cacheNode.nodeId, AttributeIds.Value, (err: Error | null, value?: DataValue) => {
35,233✔
1059
                        if (!err) {
35,233!
1060
                            assert(value instanceof DataValue);
35,233✔
1061
                            cache.dataValue = value!;
35,233✔
1062
                        }
1063
                        callback();
35,233✔
1064
                    });
1065
                },
1066
                task6a_variable_arrayDimension: (callback: ErrorCallback) => {
1067
                    if (cacheNode.nodeClass !== NodeClass.Variable && cacheNode.nodeClass !== NodeClass.VariableType) {
45,843✔
1068
                        return callback();
10,610✔
1069
                    }
1070
                    const cache = cacheNode as CacheNodeVariable | CacheNodeVariableType;
35,233✔
1071
                    this._defer_readNode(
35,233✔
1072
                        cacheNode.nodeId,
1073
                        AttributeIds.ArrayDimensions,
1074
                        (err: Error | null, value?: number[] | Uint32Array | null) => {
1075
                            if (!err) {
35,233!
1076
                                const standardArray = convertToStandardArray(value);
35,233✔
1077
                                cache.arrayDimensions = standardArray;
35,233✔
1078
                            } else {
UNCOV
1079
                                cache.arrayDimensions = undefined; // set explicitly
×
1080
                            }
1081
                            callback();
35,233✔
1082
                        }
1083
                    );
1084
                },
1085
                task6b_variable_valueRank: (callback: ErrorCallback) => {
1086
                    if (cacheNode.nodeClass !== NodeClass.Variable && cacheNode.nodeClass !== NodeClass.VariableType) {
45,843✔
1087
                        return callback();
10,610✔
1088
                    }
1089
                    const cache = cacheNode as CacheNodeVariable | CacheNodeVariableType;
35,233✔
1090
                    this._defer_readNode(cacheNode.nodeId, AttributeIds.ValueRank, (err: Error | null, value?: number) => {
35,233✔
1091
                        if (!err) {
35,233!
1092
                            cache.valueRank = value!;
35,233✔
1093
                        }
1094
                        callback();
35,233✔
1095
                    });
1096
                },
1097
                task7_variable_minimumSamplingInterval: (callback: ErrorCallback) => {
1098
                    if (cacheNode.nodeClass !== NodeClass.Variable) {
45,843✔
1099
                        return callback();
11,106✔
1100
                    }
1101
                    const cache = cacheNode as CacheNodeVariable;
34,737✔
1102
                    this._defer_readNode(
34,737✔
1103
                        cacheNode.nodeId,
1104
                        AttributeIds.MinimumSamplingInterval,
1105
                        (err: Error | null, value?: number) => {
1106
                            cache.minimumSamplingInterval = value!;
34,737✔
1107
                            callback();
34,737✔
1108
                        }
1109
                    );
1110
                },
1111
                task8_variable_accessLevel: (callback: ErrorCallback) => {
1112
                    if (cacheNode.nodeClass !== NodeClass.Variable) {
45,843✔
1113
                        return callback();
11,106✔
1114
                    }
1115
                    const cache = cacheNode as CacheNodeWithAccessLevelField;
34,737✔
1116
                    this._defer_readNode(cacheNode.nodeId, AttributeIds.AccessLevel, (err: Error | null, value?: number) => {
34,737✔
1117
                        if (err) {
34,737!
UNCOV
1118
                            return callback(err);
×
1119
                        }
1120
                        cache.accessLevel = value!;
34,737✔
1121
                        callback();
34,737✔
1122
                    });
1123
                },
1124
                task9_variable_userAccessLevel: (callback: ErrorCallback) => {
1125
                    if (cacheNode.nodeClass !== NodeClass.Variable) {
45,843✔
1126
                        return callback();
11,106✔
1127
                    }
1128
                    const cache = cacheNode as CacheNodeVariable;
34,737✔
1129
                    this._defer_readNode(cacheNode.nodeId, AttributeIds.UserAccessLevel, (err: Error | null, value?: number) => {
34,737✔
1130
                        if (err) {
34,737!
UNCOV
1131
                            return callback(err);
×
1132
                        }
1133
                        cache.userAccessLevel = value!;
34,737✔
1134
                        callback();
34,737✔
1135
                    });
1136
                },
1137
                taskA_referenceType_inverseName: (callback: ErrorCallback) => {
1138
                    if (cacheNode.nodeClass !== NodeClass.ReferenceType) {
45,843✔
1139
                        return callback();
45,389✔
1140
                    }
1141
                    const cache = cacheNode as CacheNodeReferenceType;
454✔
1142
                    this._defer_readNode(cacheNode.nodeId, AttributeIds.InverseName, (err: Error | null, value?: LocalizedText) => {
454✔
1143
                        if (err) {
454!
UNCOV
1144
                            return callback(err);
×
1145
                        }
1146
                        cache.inverseName = value!;
454✔
1147
                        callback();
454✔
1148
                    });
1149
                },
1150
                taskB_isAbstract: (callback: ErrorCallback) => {
1151
                    if (cacheNode.nodeClass !== NodeClass.ReferenceType) {
45,843✔
1152
                        return callback();
45,389✔
1153
                    }
1154
                    const cache = cacheNode as CacheNodeWithAbstractField;
454✔
1155
                    this._defer_readNode(cacheNode.nodeId, AttributeIds.IsAbstract, (err: Error | null, value?: boolean) => {
454✔
1156
                        if (err) {
454!
UNCOV
1157
                            return callback(err);
×
1158
                        }
1159
                        cache.isAbstract = value!;
454✔
1160
                        callback();
454✔
1161
                    });
1162
                },
1163
                taskC_dataTypeDefinition: (callback: ErrorCallback) => {
1164
                    if (cacheNode.nodeClass !== NodeClass.DataType) {
45,843✔
1165
                        return callback();
44,754✔
1166
                    }
1167
                    // dataTypeDefinition is new in 1.04
1168
                    const cache = cacheNode as CacheNodeDataType;
1,089✔
1169
                    this._defer_readNode(cacheNode.nodeId, AttributeIds.DataTypeDefinition, (err, value?: DataTypeDefinition) => {
1,089✔
1170
                        if (err) {
1,089!
1171
                            // may be we are crawling a 1.03 server => DataTypeDefinition was not defined yet
UNCOV
1172
                            return callback();
×
1173
                        }
1174
                        cache.dataTypeDefinition = value!;
1,089✔
1175
                        callback();
1,089✔
1176
                    });
1177
                }
1178
            },
1179
            () => {
1180
                _objectToBrowse.action(cacheNode);
45,843✔
1181
            }
1182
        );
1183
    }
1184

1185
    private _process_browse_response_task(task: TaskProcessBrowseResponse, callback: EmptyCallback) {
1186
        const objectsToBrowse = task.param.objectsToBrowse;
3,843✔
1187
        const browseResults = task.param.browseResults;
3,843✔
1188
        for (const [objectToBrowse, browseResult] of zip(objectsToBrowse, browseResults)) {
3,843✔
1189
            assert(browseResult instanceof BrowseResult);
45,843✔
1190
            this._process_single_browseResult(objectToBrowse, browseResult);
45,843✔
1191
        }
1192
        setImmediate(callback);
3,843✔
1193
    }
1194
}
1195

1196
// tslint:disable:no-var-requires
1197
// tslint:disable:max-line-length
1198
import { withCallback } from "thenify-ex";
1✔
1199
NodeCrawlerBase.prototype.crawl = withCallback(NodeCrawlerBase.prototype.crawl);
1✔
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