module import { EventEmitter } from "events" ; import * as async from "async" ; import { zip } from "underscore" ; import { UAReferenceType } from "node-opcua-address-space" ; import { assert } from "node-opcua-assert" ; import { BrowseDescriptionLike , ReadValueIdOptions , ResponseCallback } from "node-opcua-client" ; import { DataTypeDefinition } from "node-opcua-types" ; import { ReferenceTypeIds , VariableIds } from "node-opcua-constants" ; import { AttributeIds , BrowseDirection , coerceLocalizedText , LocalizedText , makeResultMask , NodeClass , QualifiedName } from "node-opcua-data-model" ; import { DataValue } from "node-opcua-data-value" ; import { checkDebugFlag , make_debugLog , make_warningLog } from "node-opcua-debug" ; import { makeNodeId , NodeId , NodeIdLike , resolveNodeId , sameNodeId } from "node-opcua-nodeid" ; import { BrowseDescription , BrowseResult , ReferenceDescription } from "node-opcua-service-browse" ; import { StatusCodes } from "node-opcua-status-code" ; import { ErrorCallback } from "node-opcua-status-code" ; import { CacheNodeReferenceType , CacheNodeVariableType , CacheNodeObjectType , CacheNodeVariable , CacheNode , CacheNodeDataType } from "./cache_node" ; import { pendingBrowseName , TaskExtraReference , Task , TaskBrowseNode , TaskBrowseNext , EmptyCallback , TaskCrawl , TaskProcessBrowseResponse , dedup_reference } from "./private" ; const debugLog = make_debugLog ( __filename ) ; const doDebug = checkDebugFlag ( __filename ) ; const warningLog = make_warningLog ( __filename ) ; const resultMask = makeResultMask ( "ReferenceType | IsForward | BrowseName | DisplayName | NodeClass | TypeDefinition" ) ; function make_node_attribute_key ( nodeId , attributeId ) { return nodeId . toString ( ) + "_" + AttributeIds [ attributeId ] ; } function convertToStandardArray ( a ) { if ( a === undefined || a === null ) { return undefined ; } if ( a instanceof Array ) { return a ; } if ( a instanceof Buffer ) { return a ; } try { const b = [ ] ; for ( const x of a ) { b . push ( x ) ; } return b ; } catch ( err ) { warningLog ( a ) ; warningLog ( "convertToStandardArray error" , ( err as Error ) . message ) ; return a as unknown as number [ ] ; } } function _fetch_elements < T > ( arr , maxNode ) { assert ( Array . isArray ( arr ) ) ; assert ( arr . length > 0 ) ; const highLimit = maxNode <= 0 ? arr . length : maxNode ; const tmp = arr . splice ( 0 , highLimit ) ; assert ( tmp . length > 0 ) ; return tmp ; } type CacheNodeWithAbstractField = CacheNodeReferenceType | CacheNodeVariableType | CacheNodeObjectType ; type CacheNodeWithDataTypeField = CacheNodeVariable | CacheNodeVariableType ; type CacheNodeWithAccessLevelField = CacheNodeVariable ; const referencesNodeId = resolveNodeId ( "References" ) ; const hasTypeDefinitionNodeId = resolveNodeId ( "HasTypeDefinition" ) ; function _setExtraReference ( task , callback ) { const param = task . param ; assert ( param . userData . setExtraReference ) ; param . userData . setExtraReference ! ( param . parentNode , param . reference , param . childCacheNode , param . userData ) ; callback ( ) ; } export interface UserData { onBrowse : ( crawler : NodeCrawlerBase , cacheNode : CacheNode , userData : UserData ) => void ; setExtraReference ? : ( parentNode : CacheNode , reference : any , childCacheNode : CacheNode , userData : UserData ) => void ; } interface NodeCrawlerEvents { on ( event , handler ) : void ; } export interface NodeCrawlerClientSession { read ( nodesToRead , callback ) : void ; browse ( nodesToBrowse , callback ) : void ; browseNext ( continuationPoints , releaseContinuationPoints , callback ) : void ; } type ReadNodeAction = ( value : any , dataValue : DataValue ) => void ; interface TaskReadNode { nodeToRead : { attributeId : AttributeIds ; nodeId : NodeId ; } ; action : ReadNodeAction ; } function getReferenceTypeId ( referenceType ) { if ( ! referenceType ) { return null ; } if ( referenceType . toString ( ) === "i=45" || referenceType === "HasSubtype" ) { return NodeId . resolveNodeId ( "i=45" ) ; } else if ( referenceType . toString ( ) === "i=35" || referenceType === "Organizes" ) { return NodeId . resolveNodeId ( "i=35" ) ; } else if ( referenceType . toString ( ) === "i=47" || referenceType === "HasComponent" ) { return NodeId . resolveNodeId ( "i=47" ) ; } else if ( referenceType . toString ( ) === "i=46" || referenceType === "HasProperty" ) { return NodeId . resolveNodeId ( "i=46" ) ; } else if ( referenceType . toString ( ) === NodeId . resolveNodeId ( "HasEncoding" ) . toString ( ) || referenceType === "HasEncoding" ) { return NodeId . resolveNodeId ( "HasEncoding" ) ; } else if ( referenceType . toString ( ) === NodeId . resolveNodeId ( "HasDescription" ) . toString ( ) || referenceType === "HasDescription" ) { return NodeId . resolveNodeId ( "HasDescription" ) ; } else { throw new Error ( "Invalid reference Type" + referenceType . toString ( ) ) ; } } export type Pojo = Record < string , unknown > ; export type ObjectMap = { [ key ] : Pojo ; } ; class param session constructor export class NodeCrawlerBase extends EventEmitter implements NodeCrawlerEvents { public static follow ( crawler , cacheNode , userData , referenceType ? , browseDirection ? ) { const referenceTypeNodeId = getReferenceTypeId ( referenceType ) ; for ( const reference of cacheNode . references ) { if ( browseDirection ! === BrowseDirection . Forward && ! reference . isForward ) { continue ; } if ( browseDirection ! === BrowseDirection . Inverse && reference . isForward ) { continue ; } if ( ! referenceTypeNodeId ) { crawler . followReference ( cacheNode , reference , userData ) ; } else { if ( NodeId . sameNodeId ( referenceTypeNodeId , reference . referenceTypeId ) ) { crawler . followReference ( cacheNode , reference , userData ) ; } } } } public maxNodesPerRead = 0 ; public maxNodesPerBrowse = 0 ; public startTime : Date = new Date ( ) ; public readCounter = 0 ; public browseCounter = 0 ; public browseNextCounter = 0 ; public transactionCounter = 0 ; private readonly session : NodeCrawlerClientSession ; private readonly browseNameMap : ObjectMap ; private readonly taskQueue : async . QueueObject < Task > ; private readonly pendingReadTasks : TaskReadNode [ ] ; private readonly pendingBrowseTasks : TaskBrowseNode [ ] ; private readonly pendingBrowseNextTasks : TaskBrowseNext [ ] ; protected readonly _objectCache : { [ key ] : CacheNode ; } ; protected readonly _objMap : ObjectMap ; private _crawled : Set < string > ; private _visitedNode : Set < string > ; private _prePopulatedSet = new WeakSet ( ) ; constructor ( session ) { super ( ) ; this . session = session ; assert ( typeof session . browse === "function" ) ; assert ( typeof session . read === "function" ) ; this . browseNameMap = { } ; this . _objectCache = { } ; this . _objMap = { } ; this . _crawled = new Set < string > ( ) ; this . _visitedNode = new Set < string > ( ) ; this . _initialize_referenceTypeId ( ) ; this . pendingReadTasks = [ ] ; this . pendingBrowseTasks = [ ] ; this . pendingBrowseNextTasks = [ ] ; this . taskQueue = async . queue ( ( task , callback ) => { if ( doDebug ) { debugLog ( " executing Task " , task . name ) ; } setImmediate ( ( ) => { ( task . func as any ) . call ( this , task , ( ) => { this . resolve_deferred_browseNext ( ) ; this . resolve_deferred_browseNode ( ) ; this . resolve_deferred_readNode ( ) ; callback ( ) ; } ) ; } ) ; } , 1 ) ; this . maxNodesPerRead = 0 ; this . maxNodesPerBrowse = 0 ; this . startTime = new Date ( ) ; this . readCounter = 0 ; this . browseCounter = 0 ; this . transactionCounter = 0 ; } public dispose ( ) { assert ( this . pendingReadTasks . length === 0 ) ; assert ( this . pendingBrowseTasks . length === 0 ) ; assert ( this . pendingBrowseNextTasks . length === 0 ) ; this . pendingReadTasks . length = 0 ; this . pendingBrowseTasks . length = 0 ; this . pendingBrowseNextTasks . length = 0 ; assert ( this . taskQueue . length ( ) === 0 ) ; Object . values ( this . _objectCache ) . map ( ( cache ) => ( cache as CacheNode ) . dispose ( ) ) ; Object . values ( this . _objMap ) . map ( ( obj ) => { Object . keys ( obj as any ) . map ( ( k ) => ( ( obj as any ) [ k ] = undefined ) ) ; } ) ; this . taskQueue . kill ( ) ; ( this as any ) . session = null ; ( this as any ) . browseNameMap = null ; ( this as any ) . taskQueue = null ; ( this as any ) . _objectCache = { } ; ( this as any ) . _objMap = null ; ( this as any ) . _crawled = null ; ( this as any ) . _visitedNode = null ; ( this as any ) . _prePopulatedSet = null ; } public toString ( ) { return ( "" + `reads:       ${ this . readCounter }\n` + `browses:     ${ this . browseCounter }  \n` + `transaction: ${ this . transactionCounter }  \n` ) ; } public crawl ( nodeId , userData ) ; public crawl ( nodeId , userData , endCallback ) ; public crawl ( nodeId , userData , ... args ) { const endCallback = args [ 0 ] as ErrorCallback ; assert ( endCallback instanceof Function , "expecting callback" ) ; nodeId = resolveNodeId ( nodeId ) as NodeId ; assert ( typeof endCallback === "function" ) ; this . _readOperationalLimits ( ( err ? ) => { if ( err ) { return endCallback ( err ) ; } this . _inner_crawl ( nodeId as NodeId , userData , endCallback ) ; } ) ; } internal private private _inner_crawl ( nodeId , userData , endCallback ) { assert ( userData !== null && typeof userData === "object" ) ; assert ( typeof endCallback === "function" ) ; let hasEnded = false ; this . taskQueue . drain ( ( ) => { debugLog ( "taskQueue is empty !!" , this . taskQueue . length ( ) ) ; if ( ! hasEnded ) { hasEnded = true ; this . _visitedNode = new Set < string > ( ) ; this . _crawled = new Set < string > ( ) ; this . emit ( "end" ) ; endCallback ( ) ; } } ) ; let cacheNode = this . _getCacheNode ( nodeId ) ; if ( ! cacheNode ) { cacheNode = this . _createCacheNode ( nodeId ) ; } assert ( cacheNode . nodeId . toString ( ) === nodeId . toString ( ) ) ; async . parallel ( { task1 : ( callback ) => { this . _defer_readNode ( cacheNode . nodeId , AttributeIds . BrowseName , ( err , value ? ) => { if ( err ) { return callback ( err ) ; } assert ( value instanceof QualifiedName ) ; cacheNode . browseName = value ! ; setImmediate ( callback ) ; } ) ; } , task2 : ( callback ) => { this . _defer_readNode ( cacheNode . nodeId , AttributeIds . NodeClass , ( err , value ? ) => { if ( err ) { return callback ( err ) ; } cacheNode . nodeClass = value ! ; setImmediate ( callback ) ; } ) ; } , task3 : ( callback ) => { this . _defer_readNode ( cacheNode . nodeId , AttributeIds . DisplayName , ( err , value ? ) => { if ( err ) { return callback ( err ) ; } assert ( value instanceof LocalizedText ) ; cacheNode . displayName = value ! ; setImmediate ( callback ) ; } ) ; } , task4 : ( callback ) => { this . _resolve_deferred_readNode ( callback ) ; } } , ( err ? , data ? ) => { this . _add_crawl_task ( cacheNode , userData ) ; } ) ; } private _add_crawl_task ( cacheNode , userData ) { assert ( userData ) ; assert ( this !== null && typeof this === "object" ) ; const key = cacheNode . nodeId . toString ( ) ; if ( this . _crawled . has ( key ) ) { return ; } this . _crawled . add ( key ) ; const task = { func : NodeCrawlerBase . prototype . _crawl_task , param : { cacheNode , userData } } ; this . _push_task ( "_crawl task" , task ) ; } public followReference ( parentNode , reference , userData ) { assert ( reference instanceof ReferenceDescription ) ; let referenceTypeIdCacheNode = this . _getCacheNode ( reference . referenceTypeId ) ; if ( this . _prePopulatedSet . has ( referenceTypeIdCacheNode ) ) { this . _prePopulatedSet . delete ( referenceTypeIdCacheNode ) ; this . _add_crawl_task ( referenceTypeIdCacheNode , userData ) ; } if ( ! referenceTypeIdCacheNode ) { referenceTypeIdCacheNode = this . _createCacheNode ( reference . referenceTypeId ) ; referenceTypeIdCacheNode . nodeClass = NodeClass . ReferenceType ; this . _add_crawl_task ( referenceTypeIdCacheNode , userData ) ; } let childCacheNode = this . _getCacheNode ( reference . nodeId ) ; if ( ! childCacheNode ) { childCacheNode = this . _createCacheNode ( reference . nodeId , parentNode , reference ) ; childCacheNode . browseName = reference . browseName ; childCacheNode . displayName = reference . displayName ; childCacheNode . typeDefinition = reference . typeDefinition ; childCacheNode . nodeClass = reference . nodeClass as NodeClass ; assert ( childCacheNode . parent === parentNode ) ; assert ( childCacheNode . referenceToParent === reference ) ; this . _add_crawl_task ( childCacheNode , userData ) ; } else { if ( userData . setExtraReference ) { const task = { func : _setExtraReference , param : { childCacheNode , parentNode , reference , userData } } ; this . _push_task ( "setExtraRef" , task ) ; } } } method param callback private internal private _resolve_deferred_readNode ( callback ) { if ( this . pendingReadTasks . length === 0 ) { callback ( ) ; return ; } debugLog ( "_resolve_deferred_readNode = " , this . pendingReadTasks . length ) ; const selectedPendingReadTasks = _fetch_elements ( this . pendingReadTasks , this . maxNodesPerRead ) ; const nodesToRead = selectedPendingReadTasks . map ( ( e ) => e . nodeToRead ) ; this . readCounter += nodesToRead . length ; this . transactionCounter ++ ; this . session . read ( nodesToRead , ( err , dataValues ? ) => { if ( err ) { return callback ( err ) ; } for ( const pair of zip ( selectedPendingReadTasks , dataValues ! ) ) { const readTask = pair [ 0 ] ; const dataValue = pair [ 1 ] ; assert ( Object . prototype . hasOwnProperty . call ( dataValue , "statusCode" ) ) ; if ( dataValue . statusCode . equals ( StatusCodes . Good ) ) { if ( dataValue . value === null ) { readTask . action ( null , dataValue ) ; } else { readTask . action ( dataValue . value . value , dataValue ) ; } } else { readTask . action ( { name : dataValue . statusCode . toString ( ) } , dataValue ) ; } } callback ( ) ; } ) ; } private _resolve_deferred_browseNode ( callback ) { if ( this . pendingBrowseTasks . length === 0 ) { callback ( ) ; return ; } debugLog ( "_resolve_deferred_browseNode = " , this . pendingBrowseTasks . length ) ; const objectsToBrowse = _fetch_elements ( this . pendingBrowseTasks , this . maxNodesPerBrowse ) ; const nodesToBrowse = objectsToBrowse . map ( ( e ) => { assert ( Object . prototype . hasOwnProperty . call ( e , "referenceTypeId" ) ) ; return new BrowseDescription ( { browseDirection : BrowseDirection . Forward , includeSubtypes : true , nodeId : e . nodeId , referenceTypeId : e . referenceTypeId , resultMask } ) ; } ) ; this . browseCounter += nodesToBrowse . length ; this . transactionCounter ++ ; this . session . browse ( nodesToBrowse , ( err , browseResults ? ) => { if ( err ) { debugLog ( "session.browse err:" , err ) ; return callback ( err || undefined ) ; } assert ( browseResults ! . length === nodesToBrowse . length ) ; browseResults = browseResults || [ ] ; const task = { func : NodeCrawlerBase . prototype . _process_browse_response_task , param : { browseResults , objectsToBrowse } } ; this . _unshift_task ( "process browseResults" , task ) ; callback ( ) ; } ) ; } private _resolve_deferred_browseNext ( callback ) { if ( this . pendingBrowseNextTasks . length === 0 ) { callback ( ) ; return ; } debugLog ( "_resolve_deferred_browseNext = " , this . pendingBrowseNextTasks . length ) ; const objectsToBrowse = _fetch_elements ( this . pendingBrowseNextTasks , this . maxNodesPerBrowse ) ; const continuationPoints = objectsToBrowse . map ( ( e ) => e . continuationPoint ) ; this . browseNextCounter += continuationPoints . length ; this . transactionCounter ++ ; this . session . browseNext ( continuationPoints , false , ( err , browseResults ? ) => { if ( err ) { debugLog ( "session.browse err:" , err ) ; return callback ( err || undefined ) ; } assert ( browseResults ! . length === continuationPoints . length ) ; browseResults = browseResults || [ ] ; const task = { func : NodeCrawlerBase . prototype . _process_browse_response_task , param : { browseResults , objectsToBrowse } } ; this . _unshift_task ( "process browseResults" , task ) ; callback ( ) ; } ) ; } method param name param task private private _unshift_task ( name , task ) { assert ( typeof task . func === "function" ) ; assert ( task . func . length === 2 ) ; this . taskQueue . unshift ( task ) ; debugLog ( "unshift task" , name ) ; } method param name param task private private _push_task ( name , task ) { assert ( typeof task . func === "function" ) ; assert ( task . func . length === 2 ) ; debugLog ( "pushing task" , name ) ; this . taskQueue . push ( task ) ; } method param cacheNode param userData private private _emit_on_crawled ( cacheNode , userData ) { this . emit ( "browsed" , cacheNode , userData ) ; } private _crawl_task ( task , callback ) { const cacheNode = task . param . cacheNode ; const nodeId = task . param . cacheNode . nodeId ; const key = nodeId . toString ( ) ; if ( this . _visitedNode . has ( key ) ) { debugLog ( "skipping already visited" , key ) ; callback ( ) ; return ; } this . _visitedNode . add ( key ) ; const browseNodeAction = ( err , cacheNode1 ? ) => { if ( err || ! cacheNode1 ) { return ; } for ( const reference of cacheNode1 . references ) { if ( ! this . has_cache_NodeAttribute ( reference . nodeId , AttributeIds . BrowseName ) ) { this . set_cache_NodeAttribute ( reference . nodeId , AttributeIds . BrowseName , reference . browseName ) ; } if ( ! this . has_cache_NodeAttribute ( reference . nodeId , AttributeIds . DisplayName ) ) { this . set_cache_NodeAttribute ( reference . nodeId , AttributeIds . DisplayName , reference . displayName ) ; } if ( ! this . has_cache_NodeAttribute ( reference . nodeId , AttributeIds . NodeClass ) ) { this . set_cache_NodeAttribute ( reference . nodeId , AttributeIds . NodeClass , reference . nodeClass ) ; } } this . _emit_on_crawled ( cacheNode1 , task . param . userData ) ; const userData = task . param . userData ; if ( userData . onBrowse ) { userData . onBrowse ( this , cacheNode1 , userData ) ; } } ; this . _defer_browse_node ( cacheNode , referencesNodeId , browseNodeAction ) ; callback ( ) ; } private _initialize_referenceTypeId ( ) { const appendPrepopulatedReference = ( browseName ) => { const nodeId = makeNodeId ( ( ReferenceTypeIds as any ) [ browseName ] , 0 ) ; assert ( nodeId ) ; const cacheNode = this . _createCacheNode ( nodeId ) ; cacheNode . browseName = new QualifiedName ( { name : browseName } ) ; cacheNode . nodeClass = NodeClass . ReferenceType ; this . _prePopulatedSet . add ( cacheNode ) ; } ; appendPrepopulatedReference ( "HasSubtype" ) ; if ( false ) { appendPrepopulatedReference ( "HasTypeDefinition" ) ; appendPrepopulatedReference ( "HasChild" ) ; appendPrepopulatedReference ( "HasProperty" ) ; appendPrepopulatedReference ( "HasComponent" ) ; appendPrepopulatedReference ( "HasHistoricalConfiguration" ) ; appendPrepopulatedReference ( "Organizes" ) ; appendPrepopulatedReference ( "HasEventSource" ) ; appendPrepopulatedReference ( "HasModellingRule" ) ; appendPrepopulatedReference ( "HasEncoding" ) ; appendPrepopulatedReference ( "HasDescription" ) ; } } private _readOperationalLimits ( callback ) { const n1 = makeNodeId ( VariableIds . Server_ServerCapabilities_OperationLimits_MaxNodesPerRead ) ; const n2 = makeNodeId ( VariableIds . Server_ServerCapabilities_OperationLimits_MaxNodesPerBrowse ) ; const nodesToRead = [ { nodeId : n1 , attributeId : AttributeIds . Value } , { nodeId : n2 , attributeId : AttributeIds . Value } ] ; this . transactionCounter ++ ; this . session . read ( nodesToRead , ( err , dataValues ? ) : void => { if ( err ) { return callback ( err ) ; } dataValues = dataValues ! ; const fix = ( self , maxNodePerX , dataValue ) => { if ( dataValue . statusCode . equals ( StatusCodes . Good ) ) { const value = dataValue . value . value ; if ( self [ maxNodePerX ] ) { if ( value > 0 ) { self [ maxNodePerX ] = Math . min ( self [ maxNodePerX ] , value ) ; } } else { self [ maxNodePerX ] = value ; } } else { debugLog ( "warning: server does not provide a valid dataValue for " + maxNodePerX , dataValue . statusCode . toString ( ) ) ; } self [ maxNodePerX ] = self [ maxNodePerX ] || 100 ; debugLog ( "maxNodesPerRead" , self [ maxNodePerX ] ) ; } ; fix ( this , "maxNodesPerRead" , dataValues [ 0 ] ) ; fix ( this , "maxNodesPerBrowse" , dataValues [ 1 ] ) ; callback ( ) ; } ) ; } private set_cache_NodeAttribute ( nodeId , attributeId , value ) { const key = make_node_attribute_key ( nodeId , attributeId ) ; this . browseNameMap [ key ] = value ; } private has_cache_NodeAttribute ( nodeId , attributeId ) { const key = make_node_attribute_key ( nodeId , attributeId ) ; return Object . prototype . hasOwnProperty . call ( this . browseNameMap , key ) ; } private get_cache_NodeAttribute ( nodeId , attributeId ) { const key = make_node_attribute_key ( nodeId , attributeId ) ; return this . browseNameMap [ key ] ; } method param nodeId param attributeId param callback private internal private _defer_readNode ( nodeId , attributeId , callback ) ; private _defer_readNode ( nodeId , attributeId , callback ) ; private _defer_readNode ( nodeId , attributeId , callback ) ; private _defer_readNode ( nodeId , attributeId , callback ) ; private _defer_readNode ( nodeId , attributeId , callback ) ; private _defer_readNode ( nodeId , attributeId , callback ) ; private _defer_readNode ( nodeId , attributeId , callback ) ; private _defer_readNode ( nodeId , attributeId , callback ) ; private _defer_readNode ( nodeId , attributeId , callback ) { nodeId = resolveNodeId ( nodeId ) ; const key = make_node_attribute_key ( nodeId , attributeId ) ; if ( this . has_cache_NodeAttribute ( nodeId , attributeId ) ) { callback ( null , this . get_cache_NodeAttribute ( nodeId , attributeId ) ) ; } else { this . pendingReadTasks . push ( { action : ( value , dataValue ) => { if ( attributeId === AttributeIds . Value ) { this . set_cache_NodeAttribute ( nodeId , attributeId , dataValue ) ; callback ( null , dataValue ) ; return ; } if ( attributeId === AttributeIds . ArrayDimensions ) { value = dataValue . statusCode !== StatusCodes . Good ? null : value ; this . set_cache_NodeAttribute ( nodeId , attributeId , value ) ; callback ( null , value ) ; return ; } if ( dataValue . statusCode !== StatusCodes . Good ) { this . set_cache_NodeAttribute ( nodeId , attributeId , dataValue ) ; callback ( null , null ) ; return ; } if ( dataValue . statusCode === StatusCodes . Good ) { this . set_cache_NodeAttribute ( nodeId , attributeId , value ) ; callback ( null , value ) ; } else { callback ( new Error ( "Error " + dataValue . statusCode . toString ( ) + " while reading " + nodeId . toString ( ) + " attributeIds " + AttributeIds [ attributeId ] ) ) ; } } , nodeToRead : { attributeId , nodeId } } ) ; } } private _resolve_deferred ( comment , collection , method ) { debugLog ( "_resolve_deferred " , comment , collection . length ) ; if ( collection . length > 0 ) { this . _push_task ( "adding operation " + comment , { func : ( task , callback ) => { method . call ( this , callback ) ; } , param : { } } ) ; } } private resolve_deferred_readNode ( ) { this . _resolve_deferred ( "read_node" , this . pendingReadTasks , this . _resolve_deferred_readNode ) ; } private resolve_deferred_browseNode ( ) { this . _resolve_deferred ( "browse_node" , this . pendingBrowseTasks , this . _resolve_deferred_browseNode ) ; } private resolve_deferred_browseNext ( ) { this . _resolve_deferred ( "browse_next" , this . pendingBrowseNextTasks , this . _resolve_deferred_browseNext ) ; } private _getCacheNode ( nodeId ) { const key = resolveNodeId ( nodeId ) . toString ( ) ; return this . _objectCache [ key ] ; } private _createCacheNode ( nodeId , parentNode ? , referenceToParent ? ) { const key = resolveNodeId ( nodeId ) . toString ( ) ; let cacheNode = this . _objectCache [ key ] ; if ( cacheNode ) { throw new Error ( "NodeCrawlerBase#_createCacheNode :" + " cache node should not exist already : " + nodeId . toString ( ) ) ; } const nodeClass = ( referenceToParent ? referenceToParent ! . nodeClass : NodeClass . Unspecified ) as NodeClass ; switch ( nodeClass ) { case NodeClass . Method : cacheNode = new CacheNode ( nodeId ) ; cacheNode . nodeClass = NodeClass . Method ; break ; case NodeClass . Object : cacheNode = new CacheNode ( nodeId ) ; cacheNode . nodeClass = NodeClass . Object ; break ; case NodeClass . ObjectType : cacheNode = new CacheNode ( nodeId ) ; cacheNode . nodeClass = NodeClass . ObjectType ; break ; case NodeClass . Variable : cacheNode = new CacheNodeVariable ( nodeId ) ; break ; case NodeClass . VariableType : cacheNode = new CacheNodeVariableType ( nodeId ) ; break ; default : cacheNode = new CacheNode ( nodeId ) ; cacheNode . nodeClass = nodeClass ; break ; } cacheNode . parent = parentNode ; cacheNode . referenceToParent = referenceToParent ; assert ( ! Object . prototype . hasOwnProperty . call ( this . _objectCache , key ) ) ; this . _objectCache [ key ] = cacheNode ; return cacheNode ; } method private private _defer_browse_node ( cacheNode , referenceTypeId , actionOnBrowse ) { this . pendingBrowseTasks . push ( { action : ( object ) => { assert ( object === cacheNode ) ; assert ( Array . isArray ( object . references ) ) ; assert ( cacheNode . browseName . name !== "pending" ) ; actionOnBrowse ( null , cacheNode ) ; } , cacheNode , nodeId : cacheNode . nodeId , referenceTypeId } ) ; } private _defer_browse_next ( cacheNode , continuationPoint , referenceTypeId , actionOnBrowse ) { this . pendingBrowseNextTasks . push ( { action : ( object ) => { assert ( object === cacheNode ) ; assert ( Array . isArray ( object . references ) ) ; assert ( cacheNode . browseName . name !== "pending" ) ; actionOnBrowse ( null , cacheNode ) ; } , cacheNode , continuationPoint , nodeId : cacheNode . nodeId , referenceTypeId } ) ; } method param _objectToBrowse param browseResult private private _process_single_browseResult ( _objectToBrowse , browseResult ) { const cacheNode = _objectToBrowse . cacheNode as CacheNode ; cacheNode . references = cacheNode . references . concat ( browseResult . references ! ) ; if ( browseResult . continuationPoint ) { this . _defer_browse_next ( _objectToBrowse . cacheNode , browseResult . continuationPoint , _objectToBrowse . referenceTypeId , ( err , cacheNode1 ? ) => { this . _process_single_browseResult2 ( _objectToBrowse ) ; } ) ; } else { this . _process_single_browseResult2 ( _objectToBrowse ) ; } } private _process_single_browseResult2 ( _objectToBrowse ) { const cacheNode = _objectToBrowse . cacheNode as CacheNode ; cacheNode . references = dedup_reference ( cacheNode . references ) ; const tmp = cacheNode . references . filter ( ( x ) => sameNodeId ( x . referenceTypeId , hasTypeDefinitionNodeId ) ) ; if ( tmp . length ) { cacheNode . typeDefinition = tmp [ 0 ] . nodeId ; } async . parallel ( { task1_read_browseName : ( callback ) => { if ( cacheNode . browseName !== pendingBrowseName ) { return callback ( ) ; } this . _defer_readNode ( cacheNode . nodeId , AttributeIds . BrowseName , ( err , browseName ? ) => { cacheNode . browseName = browseName ! ; callback ( ) ; } ) ; } , task2_read_displayName : ( callback ) => { if ( cacheNode . displayName ) { return callback ( ) ; } this . _defer_readNode ( cacheNode . nodeId , AttributeIds . DisplayName , ( err , value ? ) => { if ( err ) { return callback ( err ) ; } cacheNode . displayName = value ! ; callback ( ) ; } ) ; } , task3_read_description : ( callback ) => { this . _defer_readNode ( cacheNode . nodeId , AttributeIds . Description , ( err , value ? ) => { if ( err ) { return callback ( ) ; } cacheNode . description = coerceLocalizedText ( value ) ! ; callback ( ) ; } ) ; } , task4_variable_dataType : ( callback ) => { if ( cacheNode . nodeClass !== NodeClass . Variable && cacheNode . nodeClass !== NodeClass . VariableType ) { return callback ( ) ; } const cache = cacheNode as CacheNodeWithDataTypeField ; this . _defer_readNode ( cacheNode . nodeId , AttributeIds . DataType , ( err , dataType ? ) => { if ( ! ( dataType instanceof NodeId ) ) { return callback ( ) ; } cache . dataType = dataType ; callback ( ) ; } ) ; } , task5_variable_dataValue : ( callback ) => { if ( cacheNode . nodeClass !== NodeClass . Variable && cacheNode . nodeClass !== NodeClass . VariableType ) { return callback ( ) ; } const cache = cacheNode as CacheNodeVariable | CacheNodeVariableType ; this . _defer_readNode ( cacheNode . nodeId , AttributeIds . Value , ( err , value ? ) => { if ( ! err ) { assert ( value instanceof DataValue ) ; cache . dataValue = value ! ; } callback ( ) ; } ) ; } , task6a_variable_arrayDimension : ( callback ) => { if ( cacheNode . nodeClass !== NodeClass . Variable && cacheNode . nodeClass !== NodeClass . VariableType ) { return callback ( ) ; } const cache = cacheNode as CacheNodeVariable | CacheNodeVariableType ; this . _defer_readNode ( cacheNode . nodeId , AttributeIds . ArrayDimensions , ( err , value ? ) => { if ( ! err ) { const standardArray = convertToStandardArray ( value ) ; cache . arrayDimensions = standardArray ; } else { cache . arrayDimensions = undefined ; } callback ( ) ; } ) ; } , task6b_variable_valueRank : ( callback ) => { if ( cacheNode . nodeClass !== NodeClass . Variable && cacheNode . nodeClass !== NodeClass . VariableType ) { return callback ( ) ; } const cache = cacheNode as CacheNodeVariable | CacheNodeVariableType ; this . _defer_readNode ( cacheNode . nodeId , AttributeIds . ValueRank , ( err , value ? ) => { if ( ! err ) { cache . valueRank = value ! ; } callback ( ) ; } ) ; } , task7_variable_minimumSamplingInterval : ( callback ) => { if ( cacheNode . nodeClass !== NodeClass . Variable ) { return callback ( ) ; } const cache = cacheNode as CacheNodeVariable ; this . _defer_readNode ( cacheNode . nodeId , AttributeIds . MinimumSamplingInterval , ( err , value ? ) => { cache . minimumSamplingInterval = value ! ; callback ( ) ; } ) ; } , task8_variable_accessLevel : ( callback ) => { if ( cacheNode . nodeClass !== NodeClass . Variable ) { return callback ( ) ; } const cache = cacheNode as CacheNodeWithAccessLevelField ; this . _defer_readNode ( cacheNode . nodeId , AttributeIds . AccessLevel , ( err , value ? ) => { if ( err ) { return callback ( err ) ; } cache . accessLevel = value ! ; callback ( ) ; } ) ; } , task9_variable_userAccessLevel : ( callback ) => { if ( cacheNode . nodeClass !== NodeClass . Variable ) { return callback ( ) ; } const cache = cacheNode as CacheNodeVariable ; this . _defer_readNode ( cacheNode . nodeId , AttributeIds . UserAccessLevel , ( err , value ? ) => { if ( err ) { return callback ( err ) ; } cache . userAccessLevel = value ! ; callback ( ) ; } ) ; } , taskA_referenceType_inverseName : ( callback ) => { if ( cacheNode . nodeClass !== NodeClass . ReferenceType ) { return callback ( ) ; } const cache = cacheNode as CacheNodeReferenceType ; this . _defer_readNode ( cacheNode . nodeId , AttributeIds . InverseName , ( err , value ? ) => { if ( err ) { return callback ( err ) ; } cache . inverseName = value ! ; callback ( ) ; } ) ; } , taskB_isAbstract : ( callback ) => { if ( cacheNode . nodeClass !== NodeClass . ReferenceType ) { return callback ( ) ; } const cache = cacheNode as CacheNodeWithAbstractField ; this . _defer_readNode ( cacheNode . nodeId , AttributeIds . IsAbstract , ( err , value ? ) => { if ( err ) { return callback ( err ) ; } cache . isAbstract = value ! ; callback ( ) ; } ) ; } , taskC_dataTypeDefinition : ( callback ) => { if ( cacheNode . nodeClass !== NodeClass . DataType ) { return callback ( ) ; } const cache = cacheNode as CacheNodeDataType ; this . _defer_readNode ( cacheNode . nodeId , AttributeIds . DataTypeDefinition , ( err , value ? ) => { if ( err ) { return callback ( ) ; } cache . dataTypeDefinition = value ! ; callback ( ) ; } ) ; } } , ( ) => { _objectToBrowse . action ( cacheNode ) ; } ) ; } private _process_browse_response_task ( task , callback ) { const objectsToBrowse = task . param . objectsToBrowse ; const browseResults = task . param . browseResults ; for ( const pair of zip ( objectsToBrowse , browseResults ) ) { const objectToBrowse = pair [ 0 ] ; const browseResult = pair [ 1 ] ; assert ( browseResult instanceof BrowseResult ) ; this . _process_single_browseResult ( objectToBrowse , browseResult ) ; } setImmediate ( callback ) ; } } const thenify = require ( "thenify" ) ; NodeCrawlerBase . prototype . crawl = thenify . withCallback ( NodeCrawlerBase . prototype . crawl ) ;