node-opcua-pseudo-session
Version:
pure nodejs OPCUA SDK - module pseudo-session
459 lines (402 loc) • 19.8 kB
text/typescript
/**
* @module node-opcua-pseudo-session
*/
import { assert } from "node-opcua-assert";
import { ByteString } from "node-opcua-basic-types";
import { AttributeIds, BrowseDirection, makeResultMask } from "node-opcua-data-model";
import { DataValue } from "node-opcua-data-value";
import { NodeIdLike, resolveNodeId } from "node-opcua-nodeid";
import { BrowseDescription, BrowseDescriptionOptions, BrowseResult } from "node-opcua-service-browse";
import { Argument, CallMethodRequestOptions, CallMethodResult } from "node-opcua-service-call";
import { ReadValueIdOptions } from "node-opcua-service-read";
import { WriteValueOptions } from "node-opcua-service-write";
import { BrowsePath, BrowsePathResult } from "node-opcua-service-translate-browse-path";
import { DataType, VariantArrayType } from "node-opcua-variant";
import { CallbackT, StatusCode, StatusCodes } from "node-opcua-status-code";
import { VariableIds } from "node-opcua-constants";
import { BrowsePathOptions, UserTokenType, X509IdentityTokenOptions } from "node-opcua-types";
export type BrowseDescriptionLike = string | BrowseDescriptionOptions;
export type CallMethodRequestLike = CallMethodRequestOptions;
export type ResponseCallback<T> = (err: Error | null, result?: T) => void;
export type MethodId = NodeIdLike;
export interface ArgumentDefinition {
inputArguments: Argument[];
outputArguments: Argument[];
}
export interface IBasicTransportSettings {
maxMessageSize: number;
}
// #region Browse
export interface IBasicSessionBrowseAsyncSimple {
browse(nodeToBrowse: BrowseDescriptionLike): Promise<BrowseResult>;
}
export interface IBasicSessionBrowseAsyncMultiple {
browse(nodesToBrowse: BrowseDescriptionLike[]): Promise<BrowseResult[]>;
}
export interface IBasicSessionBrowseAsync extends IBasicSessionBrowseAsyncSimple, IBasicSessionBrowseAsyncMultiple {
browse(nodeToBrowse: BrowseDescriptionLike): Promise<BrowseResult>;
browse(nodesToBrowse: BrowseDescriptionLike[]): Promise<BrowseResult[]>;
}
export interface IBasicSessionBrowseCallback {
browse(nodeToBrowse: BrowseDescriptionLike, callback: ResponseCallback<BrowseResult>): void;
browse(nodesToBrowse: BrowseDescriptionLike[], callback: ResponseCallback<BrowseResult[]>): void;
}
export interface IBasicSessionBrowse extends IBasicSessionBrowseAsync, IBasicSessionBrowseCallback {
browse(nodeToBrowse: BrowseDescriptionLike): Promise<BrowseResult>;
browse(nodesToBrowse: BrowseDescriptionLike[]): Promise<BrowseResult[]>;
browse(nodeToBrowse: BrowseDescriptionLike, callback: ResponseCallback<BrowseResult>): void;
browse(nodesToBrowse: BrowseDescriptionLike[], callback: ResponseCallback<BrowseResult[]>): void;
}
// #endregion
// #region BrowseNext
export interface IBasicSessionBrowseNextAsyncSimple {
browseNext(continuationPoint: Buffer, releaseContinuationPoints: boolean): Promise<BrowseResult>;
}
export interface IBasicSessionBrowseNextAsyncMultiple {
browseNext(continuationPoints: Buffer[], releaseContinuationPoints: boolean): Promise<BrowseResult[]>;
}
export interface IBasicSessionBrowseNextAsync extends IBasicSessionBrowseNextAsyncMultiple, IBasicSessionBrowseNextAsyncSimple {
browseNext(continuationPoint: Buffer, releaseContinuationPoints: boolean): Promise<BrowseResult>;
browseNext(continuationPoints: Buffer[], releaseContinuationPoints: boolean): Promise<BrowseResult[]>;
}
export interface IBasicSessionBrowseNextCallback {
/**
*
* @param continuationPoint
* @param releaseContinuationPoints a Boolean parameter with the following values:
* * `true` passed continuationPoints shall be reset to free resources in
* the Server. The continuation points are released and the results
* and diagnosticInfos arrays are empty.
* * `false` passed continuationPoints shall be used to get the next set of
* browse information.
*
* A Client shall always use the continuation point returned by a Browse or
* BrowseNext response to free the resources for the continuation point in the
* Server. If the Client does not want to get the next set of browse information,
* BrowseNext shall be called with this parameter set to `true`.
*
*/
browseNext(continuationPoint: Buffer, releaseContinuationPoints: boolean, callback: ResponseCallback<BrowseResult>): void;
browseNext(continuationPoints: Buffer[], releaseContinuationPoints: boolean, callback: ResponseCallback<BrowseResult[]>): void;
}
export interface IBasicSessionBrowseNext extends IBasicSessionBrowseNextAsync, IBasicSessionBrowseNextCallback {
browseNext(continuationPoint: Buffer, releaseContinuationPoints: boolean): Promise<BrowseResult>;
browseNext(continuationPoints: Buffer[], releaseContinuationPoints: boolean): Promise<BrowseResult[]>;
browseNext(continuationPoint: Buffer, releaseContinuationPoints: boolean, callback: ResponseCallback<BrowseResult>): void;
browseNext(continuationPoints: Buffer[], releaseContinuationPoints: boolean, callback: ResponseCallback<BrowseResult[]>): void;
}
// #endregion
// #region Read
export interface IBasicSessionReadAsyncSimple {
read(nodeToRead: ReadValueIdOptions, maxAge?: number): Promise<DataValue>;
}
export interface IBasicSessionReadAsyncMultiple {
read(nodesToRead: ReadValueIdOptions[], maxAge?: number): Promise<DataValue[]>;
}
export interface IBasicSessionReadAsync extends IBasicSessionReadAsyncSimple, IBasicSessionReadAsyncMultiple {
read(nodeToRead: ReadValueIdOptions, maxAge?: number): Promise<DataValue>;
read(nodesToRead: ReadValueIdOptions[], maxAge?: number): Promise<DataValue[]>;
}
export interface IBasicSessionReadCallback {
read(nodeToRead: ReadValueIdOptions, maxAge: number, callback: ResponseCallback<DataValue>): void;
read(nodesToRead: ReadValueIdOptions[], maxAge: number, callback: ResponseCallback<DataValue[]>): void;
read(nodeToRead: ReadValueIdOptions, callback: ResponseCallback<DataValue>): void;
read(nodesToRead: ReadValueIdOptions[], callback: ResponseCallback<DataValue[]>): void;
}
export interface IBasicSessionRead extends IBasicSessionReadCallback, IBasicSessionReadAsync {
read(nodeToRead: ReadValueIdOptions, maxAge?: number): Promise<DataValue>;
read(nodesToRead: ReadValueIdOptions[], maxAge?: number): Promise<DataValue[]>;
read(nodeToRead: ReadValueIdOptions, maxAge: number, callback: ResponseCallback<DataValue>): void;
read(nodesToRead: ReadValueIdOptions[], maxAge: number, callback: ResponseCallback<DataValue[]>): void;
read(nodeToRead: ReadValueIdOptions, callback: ResponseCallback<DataValue>): void;
read(nodesToRead: ReadValueIdOptions[], callback: ResponseCallback<DataValue[]>): void;
}
// #endregion
// #region Write
export interface IBasicSessionWriteAsyncSimple {
write(nodeToWrite: WriteValueOptions): Promise<StatusCode>;
}
export interface IBasicSessionWriteAsyncMultiple {
write(nodesToWrite: WriteValueOptions[]): Promise<StatusCode[]>;
}
export interface IBasicSessionWriteAsync extends IBasicSessionWriteAsyncSimple, IBasicSessionWriteAsyncMultiple {
write(nodeToWrite: WriteValueOptions): Promise<StatusCode>;
write(nodesToWrite: WriteValueOptions[]): Promise<StatusCode[]>;
}
export interface IBasicSessionWriteCallback {
write(nodeToWrite: WriteValueOptions, callback: ResponseCallback<StatusCode>): void;
write(nodesToWrite: WriteValueOptions[], callback: ResponseCallback<StatusCode[]>): void;
}
export interface IBasicSessionWrite extends IBasicSessionWriteCallback, IBasicSessionWriteAsync {
write(nodeToWrite: WriteValueOptions): Promise<StatusCode>;
write(nodesToWrite: WriteValueOptions[]): Promise<StatusCode[]>;
write(nodeToWrite: WriteValueOptions, callback: ResponseCallback<StatusCode>): void;
write(nodesToWrite: WriteValueOptions[], callback: ResponseCallback<StatusCode[]>): void;
}
// #endregion
// #region Call
export interface IBasicSessionCallAsyncSimple {
call(methodToCall: CallMethodRequestLike): Promise<CallMethodResult>;
}
export interface IBasicSessionCallAsyncMultiple {
call(methodsToCall: CallMethodRequestLike[]): Promise<CallMethodResult[]>;
}
export interface IBasicSessionCallAsync extends IBasicSessionCallAsyncSimple, IBasicSessionCallAsyncMultiple {
call(methodToCall: CallMethodRequestLike): Promise<CallMethodResult>;
call(methodsToCall: CallMethodRequestLike[]): Promise<CallMethodResult[]>;
}
export interface IBasicSessionCallCallback {
call(methodToCall: CallMethodRequestLike, callback: (err: Error | null, result?: CallMethodResult) => void): void;
call(methodsToCall: CallMethodRequestLike[], callback: (err: Error | null, results?: CallMethodResult[]) => void): void;
}
export interface IBasicSessionCall extends IBasicSessionCallCallback, IBasicSessionCallAsync {
/**
*
*
* @param methodToCall {CallMethodRequest} the call method request
* @param callback
*
* @example :
*
* ```javascript
* const methodToCall = {
* objectId: "ns=2;i=12",
* methodId: "ns=2;i=13",
* inputArguments: [
* new Variant({...}),
* new Variant({...}),
* ]
* }
* session.call(methodToCall,function(err,callResult) {
* if (!err) {
* console.log(" statusCode = ",callResult.statusCode);
* console.log(" inputArgumentResults[0] = ",callResult.inputArgumentResults[0].toString());
* console.log(" inputArgumentResults[1] = ",callResult.inputArgumentResults[1].toString());
* console.log(" outputArgument[0] = ",callResult.outputArgument[0].toString()); // array of variant
* }
* });
* ```
*
*
* @param methodsToCall {CallMethodRequest[]} the call method request array
* @param callback
*
*
* @example :
*
* ```javascript
* const methodsToCall = [ {
* objectId: "ns=2;i=12",
* methodId: "ns=2;i=13",
* inputArguments: [
* new Variant({...}),
* new Variant({...}),
* ]
* }];
* session.call(methodsToCall,function(err,callResults) {
* if (!err) {
* const callResult = callResults[0];
* console.log(" statusCode = ",rep.statusCode);
* console.log(" inputArgumentResults[0] = ",callResult.inputArgumentResults[0].toString());
* console.log(" inputArgumentResults[1] = ",callResult.inputArgumentResults[1].toString());
* console.log(" outputArgument[0] = ",callResult.outputArgument[0].toString()); // array of variant
* }
* });
* ```
*/
call(methodToCall: CallMethodRequestLike): Promise<CallMethodResult>;
call(methodsToCall: CallMethodRequestLike[]): Promise<CallMethodResult[]>;
call(methodToCall: CallMethodRequestLike, callback: (err: Error | null, result?: CallMethodResult) => void): void;
call(methodsToCall: CallMethodRequestLike[], callback: (err: Error | null, results?: CallMethodResult[]) => void): void;
}
// #endregion
// #region TranslateBrowsePath
export type BrowsePathLike = BrowsePathOptions;
export interface IBasicSessionTranslateBrowsePathAsyncSimple {
translateBrowsePath(browsePath: BrowsePathLike): Promise<BrowsePathResult>;
}
export interface IBasicSessionTranslateBrowsePathAsyncMultiple {
translateBrowsePath(browsePaths: BrowsePathLike[]): Promise<BrowsePathResult[]>;
}
export interface IBasicSessionTranslateBrowsePathAsync
extends IBasicSessionTranslateBrowsePathAsyncSimple,
IBasicSessionTranslateBrowsePathAsyncMultiple {
translateBrowsePath(browsePath: BrowsePathLike): Promise<BrowsePathResult>;
translateBrowsePath(browsePaths: BrowsePathLike[]): Promise<BrowsePathResult[]>;
}
export interface IBasicSessionTranslateBrowsePathCallback {
translateBrowsePath(browsesPath: BrowsePathLike[], callback: ResponseCallback<BrowsePathResult[]>): void;
translateBrowsePath(browsePath: BrowsePathLike, callback: ResponseCallback<BrowsePathResult>): void;
}
export interface IBasicSessionTranslateBrowsePath
extends IBasicSessionTranslateBrowsePathCallback,
IBasicSessionTranslateBrowsePathAsync {
translateBrowsePath(browsePath: BrowsePathLike): Promise<BrowsePathResult>;
translateBrowsePath(browsePaths: BrowsePathLike[]): Promise<BrowsePathResult[]>;
translateBrowsePath(browsesPath: BrowsePathLike[], callback: ResponseCallback<BrowsePathResult[]>): void;
translateBrowsePath(browsePath: BrowsePathLike, callback: ResponseCallback<BrowsePathResult>): void;
}
// #endregion
export interface IBasicSessionAsyncSimple extends
IBasicSessionBrowseAsyncSimple,
IBasicSessionReadAsyncSimple,
IBasicSessionWriteAsyncSimple,
IBasicSessionCallAsyncSimple,
IBasicSessionTranslateBrowsePathAsyncSimple {}
export interface IBasicSessionAsyncMultiple extends
IBasicSessionBrowseAsyncMultiple,
IBasicSessionReadAsyncMultiple,
IBasicSessionWriteAsyncMultiple,
IBasicSessionCallAsyncMultiple,
IBasicSessionTranslateBrowsePathAsyncMultiple {}
export interface IBasicSessionAsync extends IBasicSessionBrowseAsync,
IBasicSessionReadAsync,
IBasicSessionWriteAsync,
IBasicSessionCallAsync,
IBasicSessionTranslateBrowsePathAsync {}
export type IVeryBasicSession = IBasicSessionAsync;
export interface IBasicSessionAsync2 extends IBasicSessionAsync, IBasicSessionBrowseNextAsync {}
export interface ITransportSettingProvider {
getTransportSettings?: () => IBasicTransportSettings;
}
export interface IBasicSessionGetArgumentDefinitionCallback {
getArgumentDefinition(methodId: MethodId, callback: (err: Error | null, args?: ArgumentDefinition) => void): void;
}
export interface IBasicSessionGetArgumentDefinitionAsync {
getArgumentDefinition(methodId: MethodId): Promise<ArgumentDefinition>;
}
export interface IBasicSessionGetArgumentDefinition extends IBasicSessionGetArgumentDefinitionAsync, IBasicSessionGetArgumentDefinitionCallback {
getArgumentDefinition(methodId: MethodId): Promise<ArgumentDefinition>;
getArgumentDefinition(methodId: MethodId, callback: (err: Error | null, args?: ArgumentDefinition) => void): void;
}
export type IBasicSessionCallback = IBasicSessionReadCallback &
IBasicSessionBrowseNextCallback &
IBasicSessionBrowseCallback &
IBasicSessionTranslateBrowsePathCallback &
IBasicSessionGetArgumentDefinitionCallback &
IBasicSessionWriteCallback;
export interface IBasicSession
extends ITransportSettingProvider,
IBasicSessionBrowse,
IBasicSessionBrowseNext,
IBasicSessionCall,
IBasicSessionRead,
IBasicSessionTranslateBrowsePath,
IBasicSessionWrite,
IBasicSessionGetArgumentDefinition {
}
export type PrivateKeyPEM = string;
export interface UserIdentityInfoUserName {
type: UserTokenType.UserName;
userName: string;
password: string;
}
export interface UserIdentityInfoX509 extends X509IdentityTokenOptions {
type: UserTokenType.Certificate;
certificateData: ByteString;
privateKey: PrivateKeyPEM;
}
export interface AnonymousIdentity {
type: UserTokenType.Anonymous;
}
export type UserIdentityInfo = AnonymousIdentity | UserIdentityInfoX509 | UserIdentityInfoUserName;
export interface IBasicSessionChangeUser {
changeUser(userIdentityInfo: UserIdentityInfo): Promise<StatusCode>;
changeUser(userIdentityInfo: UserIdentityInfo, callback: CallbackT<StatusCode>): void;
}
function isValid(result: DataValue): boolean {
assert(result.statusCode.isGood());
if (result.value.dataType !== DataType.Null) {
assert(result.value.dataType === DataType.ExtensionObject);
assert(result.value.arrayType === VariantArrayType.Array);
}
return true;
}
export async function getArgumentDefinitionHelper(
session: IBasicSessionBrowseAsyncSimple & IBasicSessionReadAsyncMultiple,
methodId: MethodId
): Promise<ArgumentDefinition> {
const browseDescription = new BrowseDescription({
browseDirection: BrowseDirection.Forward,
includeSubtypes: true,
nodeClassMask: 0, // makeNodeClassMask("Variable"),
nodeId: methodId,
referenceTypeId: resolveNodeId("HasProperty"),
resultMask: makeResultMask("BrowseName")
});
const browseResult = await session.browse(browseDescription);
browseResult.references = browseResult.references || [];
const inputArgumentRefArray = browseResult.references.filter((r) => r.browseName.name === "InputArguments");
// note : InputArguments property is optional thus may be missing
const inputArgumentRef = inputArgumentRefArray.length === 1 ? inputArgumentRefArray[0] : null;
const outputArgumentRefArray = browseResult.references.filter((r) => r.browseName.name === "OutputArguments");
// note : OutputArguments property is optional thus may be missing
const outputArgumentRef = outputArgumentRefArray.length === 1 ? outputArgumentRefArray[0] : null;
let inputArguments: Argument[] = [];
let outputArguments: Argument[] = [];
const nodesToRead = [];
const actions: any[] = [];
if (inputArgumentRef) {
nodesToRead.push({
attributeId: AttributeIds.Value,
nodeId: inputArgumentRef.nodeId
});
actions.push((result: DataValue) => {
if (isValid(result)) {
inputArguments = result.value.value as Argument[];
}
});
}
if (outputArgumentRef) {
nodesToRead.push({
attributeId: AttributeIds.Value,
nodeId: outputArgumentRef.nodeId
});
actions.push((result: DataValue) => {
assert(result.statusCode.isGood());
if (isValid(result)) {
outputArguments = result.value.value as Argument[];
}
});
}
if (nodesToRead.length === 0) {
return { inputArguments, outputArguments };
}
// now read the variable
const dataValues = await session.read(nodesToRead);
dataValues.forEach((dataValue, index) => {
actions[index].call(null, dataValue);
});
return { inputArguments, outputArguments };
}
interface SessionWithCache extends IBasicSessionAsync2 {
$$namespaceArray?: string[] | null;
}
type ICascadingSession = { session?: IBasicSessionReadAsyncSimple }
function followSession(session: IBasicSessionReadAsyncSimple & ICascadingSession): SessionWithCache {
if (session.session) {
return followSession(session.session);
}
return session as SessionWithCache;
}
export async function readNamespaceArray(session: IBasicSessionReadAsyncSimple): Promise<string[]> {
const sessionHoldingCache = followSession(session) as SessionWithCache;
if (sessionHoldingCache.$$namespaceArray) {
return sessionHoldingCache.$$namespaceArray!;
}
const nodeId = resolveNodeId(VariableIds.Server_NamespaceArray);
const dataValue = await session.read({
nodeId,
attributeId: AttributeIds.Value
});
if (dataValue.statusCode.isNotGood()) {
// errorLog("namespaceArray is not populated ! Your server must expose a list of namespaces in node ", nodeId.toString());
return [];
}
sessionHoldingCache.$$namespaceArray = dataValue.value.value; // keep a cache
return sessionHoldingCache.$$namespaceArray!;
}
export async function clearSessionCache(session: IBasicSessionAsync2) {
const sessionHoldingCache = followSession(session) as SessionWithCache;
sessionHoldingCache.$$namespaceArray = undefined;
}