node-opcua-client
Version:
pure nodejs OPCUA SDK - module client
1,072 lines (1,070 loc) • 62.2 kB
JavaScript
"use strict";
/**
* @module node-opcua-client-private
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClientSessionImpl = void 0;
const chalk_1 = __importDefault(require("chalk"));
const events_1 = require("events");
const node_opcua_assert_1 = require("node-opcua-assert");
const node_opcua_client_dynamic_extension_object_1 = require("node-opcua-client-dynamic-extension-object");
const node_opcua_data_model_1 = require("node-opcua-data-model");
const node_opcua_data_value_1 = require("node-opcua-data-value");
const node_opcua_debug_1 = require("node-opcua-debug");
const node_opcua_nodeid_1 = require("node-opcua-nodeid");
const node_opcua_pseudo_session_1 = require("node-opcua-pseudo-session");
const node_opcua_secure_channel_1 = require("node-opcua-secure-channel");
const node_opcua_service_browse_1 = require("node-opcua-service-browse");
const node_opcua_service_call_1 = require("node-opcua-service-call");
const node_opcua_service_history_1 = require("node-opcua-service-history");
const node_opcua_service_query_1 = require("node-opcua-service-query");
const node_opcua_service_read_1 = require("node-opcua-service-read");
const node_opcua_service_register_node_1 = require("node-opcua-service-register-node");
const node_opcua_service_subscription_1 = require("node-opcua-service-subscription");
const node_opcua_service_translate_browse_path_1 = require("node-opcua-service-translate-browse-path");
const node_opcua_service_write_1 = require("node-opcua-service-write");
const node_opcua_status_code_1 = require("node-opcua-status-code");
const node_opcua_types_1 = require("node-opcua-types");
const node_opcua_utils_1 = require("node-opcua-utils");
const node_opcua_variant_1 = require("node-opcua-variant");
const client_session_keepalive_manager_1 = require("../client_session_keepalive_manager");
const client_publish_engine_1 = require("./client_publish_engine");
const client_subscription_impl_1 = require("./client_subscription_impl");
const reconnection_1 = require("./reconnection/reconnection");
const helpAPIChange = process.env.DEBUG && process.env.DEBUG.match(/API/);
const debugLog = (0, node_opcua_debug_1.make_debugLog)(__filename);
const doDebug = (0, node_opcua_debug_1.checkDebugFlag)(__filename);
const warningLog = (0, node_opcua_debug_1.make_warningLog)(__filename);
const errorLog = (0, node_opcua_debug_1.make_errorLog)(__filename);
let pendingTransactionMessageDisplayed = false;
function coerceBrowseDescription(data) {
if (typeof data === "string" || data instanceof node_opcua_nodeid_1.NodeId) {
return coerceBrowseDescription({
browseDirection: node_opcua_data_model_1.BrowseDirection.Forward,
includeSubtypes: true,
nodeClassMask: 0,
nodeId: data,
referenceTypeId: "HierarchicalReferences",
resultMask: 63
});
}
else {
data.nodeId = (0, node_opcua_nodeid_1.resolveNodeId)(data.nodeId);
data.referenceTypeId = data.referenceTypeId ? (0, node_opcua_nodeid_1.resolveNodeId)(data.referenceTypeId) : null;
return new node_opcua_service_browse_1.BrowseDescription(data);
}
}
function coerceReadValueId(node) {
if (typeof node === "string" || node instanceof node_opcua_nodeid_1.NodeId) {
return new node_opcua_service_read_1.ReadValueId({
attributeId: node_opcua_service_read_1.AttributeIds.Value,
dataEncoding: undefined, // {namespaceIndex: 0, name: undefined}
indexRange: undefined,
nodeId: (0, node_opcua_nodeid_1.resolveNodeId)(node)
});
}
else {
(0, node_opcua_assert_1.assert)(node instanceof Object);
return new node_opcua_service_read_1.ReadValueId(node);
}
}
const emptyUint32Array = new Uint32Array(0);
/**
* @class ClientSession
*/
class ClientSessionImpl extends events_1.EventEmitter {
static reconnectingElement = new WeakMap();
timeout;
authenticationToken;
requestedMaxReferencesPerNode;
sessionId;
lastRequestSentTime;
lastResponseReceivedTime;
serverCertificate;
userIdentityInfo;
name = "";
serverNonce;
serverSignature; // todo : remove ?
serverEndpoints = [];
_client;
_closed;
_reconnecting;
/**
* @internal
*/
_closeEventHasBeenEmitted;
_publishEngine;
_keepAliveManager;
$$namespaceArray;
recursive_repair_detector = 0;
constructor(client) {
super();
this.serverCertificate = Buffer.alloc(0);
this.sessionId = new node_opcua_nodeid_1.NodeId();
this._closeEventHasBeenEmitted = false;
this._client = client;
this._publishEngine = null;
this._closed = false;
this._reconnecting = {
reconnecting: false,
pendingCallbacks: [],
pendingTransactions: []
};
this.requestedMaxReferencesPerNode = 10000;
this.lastRequestSentTime = new Date(1, 1, 1970);
this.lastResponseReceivedTime = new Date(1, 1, 1970);
this.timeout = 0;
}
getTransportSettings() {
return this._client.getTransportSettings();
}
/**
* the endpoint on which this session is operating
* @property endpoint
* @type {EndpointDescription}
*/
get endpoint() {
return this._client.endpoint;
}
get subscriptionCount() {
return this._publishEngine ? this._publishEngine.subscriptionCount : 0;
}
get isReconnecting() {
return this._client ? this._client.isReconnecting || this._reconnecting?.reconnecting : false;
}
resolveNodeId(nodeId) {
return (0, node_opcua_nodeid_1.resolveNodeId)(nodeId);
}
getPublishEngine() {
if (!this._publishEngine) {
this._publishEngine = new client_publish_engine_1.ClientSidePublishEngine(this);
}
return this._publishEngine;
}
changeUser(userIdentityInfo, callback) {
userIdentityInfo = userIdentityInfo || {
type: node_opcua_types_1.UserTokenType.Anonymous
};
if (!this._client || !this.userIdentityInfo) {
warningLog("changeUser: invalid session");
return callback(null, node_opcua_status_code_1.StatusCodes.BadInternalError);
}
const old_userIdentity = this.userIdentityInfo;
this._client._activateSession(this, userIdentityInfo, (err1, session2) => {
if (err1) {
this.userIdentityInfo = old_userIdentity;
warningLog("activate session error = ", err1.message);
return callback(null, node_opcua_status_code_1.StatusCodes.BadUserAccessDenied);
}
this.userIdentityInfo = userIdentityInfo;
callback(null, node_opcua_status_code_1.StatusCodes.Good);
});
}
/**
* @internal
* @param args
*/
browse(...args) {
const arg0 = args[0];
const isArray = Array.isArray(arg0);
const callback = args[1];
(0, node_opcua_assert_1.assert)(typeof callback === "function");
(0, node_opcua_assert_1.assert)(isFinite(this.requestedMaxReferencesPerNode));
const nodesToBrowse = (isArray ? arg0 : [arg0]).map(coerceBrowseDescription);
const request = new node_opcua_service_browse_1.BrowseRequest({
nodesToBrowse,
requestedMaxReferencesPerNode: this.requestedMaxReferencesPerNode
});
this.performMessageTransaction(request, (err, response) => {
if (err) {
return callback(err);
}
/* c8 ignore next */
if (!response || !(response instanceof node_opcua_service_browse_1.BrowseResponse)) {
return callback(new Error("Internal Error"));
}
const results = response.results ? response.results : [];
if (this.requestedMaxReferencesPerNode > 0) {
for (let i = 0; i < results.length; i++) {
const r = results[i];
/* c8 ignore next */
if (r.references && r.references.length > this.requestedMaxReferencesPerNode) {
warningLog(chalk_1.default.yellow("warning") +
" BrowseResponse : the server didn't take into" +
" account our requestedMaxReferencesPerNode ");
warningLog(" this.requestedMaxReferencesPerNode= " + this.requestedMaxReferencesPerNode);
warningLog(" got " + r.references.length + "for " + nodesToBrowse[i].nodeId.toString());
warningLog(" continuationPoint ", r.continuationPoint);
}
}
}
for (const r of results) {
r.references = r.references || /* c8 ignore next */ [];
}
(0, node_opcua_assert_1.assert)(results[0] instanceof node_opcua_service_browse_1.BrowseResult);
return callback(null, isArray ? results : results[0]);
});
}
browseNext(...args) {
const arg0 = args[0];
const isArray = Array.isArray(arg0);
const releaseContinuationPoints = args[1];
const callback = args[2];
(0, node_opcua_assert_1.assert)(typeof callback === "function", "expecting a callback function here");
const continuationPoints = isArray ? arg0 : [arg0];
const request = new node_opcua_types_1.BrowseNextRequest({
continuationPoints,
releaseContinuationPoints
});
this.performMessageTransaction(request, (err, response) => {
/* c8 ignore next */
if (err) {
return callback(err);
}
/* c8 ignore next */
if (!response || !(response instanceof node_opcua_types_1.BrowseNextResponse)) {
return callback(new Error("Internal Error"));
}
const results = response.results ? response.results : [];
for (const r of results) {
r.references = r.references || [];
}
(0, node_opcua_assert_1.assert)(results[0] instanceof node_opcua_service_browse_1.BrowseResult);
return callback(null, isArray ? results : results[0]);
});
}
/**
* @internal
* @param args
*/
readVariableValue(...args) {
const callback = args[1];
(0, node_opcua_assert_1.assert)(typeof callback === "function");
const isArray = Array.isArray(args[0]);
const nodes = isArray ? args[0] : [args[0]];
const nodesToRead = nodes.map(coerceReadValueId);
const request = new node_opcua_service_read_1.ReadRequest({
nodesToRead,
timestampsToReturn: node_opcua_service_read_1.TimestampsToReturn.Neither
});
this.performMessageTransaction(request, (err, response) => {
/* c8 ignore next */
if (err) {
return callback(err);
}
/* c8 ignore next */
if (!(response instanceof node_opcua_service_read_1.ReadResponse)) {
return callback(new Error("Internal Error"));
}
/* c8 ignore next */
if (response.responseHeader.serviceResult.isNot(node_opcua_status_code_1.StatusCodes.Good)) {
return callback(new Error(response.responseHeader.serviceResult.toString()));
}
/* c8 ignore next */
if (!response.results) {
response.results = [];
}
(0, node_opcua_assert_1.assert)(nodes.length === response.results.length);
callback(null, isArray ? response.results : response.results[0]);
});
}
readHistoryValue(...args) {
const startTime = args[1];
const endTime = args[2];
let options = {};
let callback = args[3];
if (typeof callback !== "function") {
options = args[3];
callback = args[4];
}
(0, node_opcua_assert_1.assert)(typeof callback === "function");
// adjust parameters
options.numValuesPerNode = options.numValuesPerNode || 0;
options.returnBounds = options.returnBounds || options.returnBounds === undefined ? true : false;
options.isReadModified = options.isReadModified || false;
options.timestampsToReturn = options.timestampsToReturn ?? node_opcua_service_read_1.TimestampsToReturn.Both;
const arg0 = args[0];
const isArray = Array.isArray(arg0);
const nodes = isArray ? arg0 : [arg0];
const nodesToRead = [];
for (const node of nodes) {
if (!node.nodeId) {
nodesToRead.push({
continuationPoint: undefined,
dataEncoding: undefined, // {namespaceIndex: 0, name: undefined},
indexRange: undefined,
nodeId: this.resolveNodeId(node)
});
}
else {
nodesToRead.push(node);
}
}
const readRawModifiedDetails = new node_opcua_service_history_1.ReadRawModifiedDetails({
endTime,
isReadModified: false,
numValuesPerNode: options.numValuesPerNode,
returnBounds: options.returnBounds,
startTime
});
const request = new node_opcua_service_history_1.HistoryReadRequest({
historyReadDetails: readRawModifiedDetails,
nodesToRead,
releaseContinuationPoints: false,
timestampsToReturn: options.timestampsToReturn
});
request.nodesToRead = request.nodesToRead || [];
(0, node_opcua_assert_1.assert)(nodes.length === request.nodesToRead.length);
this.historyRead(request, (err, response) => {
/* c8 ignore next */
if (err) {
return callback(err);
}
/* c8 ignore next */
if (!response || !(response instanceof node_opcua_service_history_1.HistoryReadResponse)) {
return callback(new Error("Internal Error"));
}
response.results = response.results || [];
(0, node_opcua_assert_1.assert)(nodes.length === response.results.length);
callback(null, isArray ? response.results : response.results[0]);
});
}
historyRead(request, callback) {
/* c8 ignore next */
if (!callback) {
throw new Error("expecting a callback");
}
this.performMessageTransaction(request, (err, response) => {
/* c8 ignore next */
if (err) {
return callback(err);
}
/* c8 ignore next */
if (!response || !(response instanceof node_opcua_service_history_1.HistoryReadResponse)) {
return callback(new Error("Internal Error"));
}
if (response.responseHeader.serviceResult.isNot(node_opcua_status_code_1.StatusCodes.Good)) {
return callback(new Error(response.responseHeader.serviceResult.toString()));
}
response.results = response.results || /* c8 ignore next */ [];
// perform ExtensionObject resolution
const promises = response.results.map(async (result) => {
if (result.historyData && result.historyData instanceof node_opcua_service_history_1.HistoryData) {
if (result.historyData.dataValues) {
await (0, node_opcua_client_dynamic_extension_object_1.promoteOpaqueStructure)(this, result.historyData.dataValues);
}
}
});
Promise.all(promises)
.then(() => {
callback(null, response);
})
.catch((err) => callback(err));
});
}
readAggregateValue(arg0, startTime, endTime, aggregateFn, processingInterval, ...args) {
const callback = typeof args[0] === "function" ? args[0] : args[1];
(0, node_opcua_assert_1.assert)(typeof callback === "function");
const defaultAggregateFunction = {
percentDataBad: 100,
percentDataGood: 100,
treatUncertainAsBad: true,
useServerCapabilitiesDefaults: true,
useSlopedExtrapolation: false
};
const aggregateConfiguration = typeof args[0] === "function" ? defaultAggregateFunction : args[0];
const isArray = Array.isArray(arg0);
const nodesToRead = isArray
? arg0
: [arg0];
const aggregateFns = Array.isArray(aggregateFn)
? aggregateFn
: [aggregateFn];
(0, node_opcua_assert_1.assert)(aggregateFns.length === nodesToRead.length);
const readProcessedDetails = new node_opcua_service_history_1.ReadProcessedDetails({
aggregateType: aggregateFns,
endTime,
processingInterval,
startTime,
aggregateConfiguration
});
const request = new node_opcua_service_history_1.HistoryReadRequest({
historyReadDetails: readProcessedDetails,
nodesToRead,
releaseContinuationPoints: false,
timestampsToReturn: node_opcua_service_read_1.TimestampsToReturn.Both
});
(0, node_opcua_assert_1.assert)(nodesToRead.length === request.nodesToRead.length);
this.performMessageTransaction(request, (err, response) => {
/* c8 ignore next */
if (err) {
return callback(err);
}
/* c8 ignore next */
if (!response || !(response instanceof node_opcua_service_history_1.HistoryReadResponse)) {
return callback(new Error("Internal Error"));
}
if (response.responseHeader.serviceResult.isNot(node_opcua_status_code_1.StatusCodes.Good)) {
return callback(new Error(response.responseHeader.serviceResult.toString()));
}
response.results = response.results || /* c8 ignore next */ [];
(0, node_opcua_assert_1.assert)(nodesToRead.length === response.results.length);
callback(null, isArray ? response.results : response.results[0]);
});
}
/**
* @internal
* @param args
*/
write(...args) {
const arg0 = args[0];
const isArray = Array.isArray(arg0);
const nodesToWrite = isArray ? arg0 : [arg0];
const callback = args[1];
(0, node_opcua_assert_1.assert)(typeof callback === "function");
const request = new node_opcua_service_write_1.WriteRequest({ nodesToWrite });
this.performMessageTransaction(request, (err, response) => {
/* c8 ignore next */
if (err) {
return callback(err, response);
}
/* c8 ignore next */
if (!response || !(response instanceof node_opcua_service_write_1.WriteResponse)) {
return callback(new Error("Internal Error"));
}
/* c8 ignore next */
if (response.responseHeader.serviceResult.isNot(node_opcua_status_code_1.StatusCodes.Good)) {
return callback(new Error(response.responseHeader.serviceResult.toString()));
}
response.results = response.results || /* c8 ignore next */ [];
(0, node_opcua_assert_1.assert)(nodesToWrite.length === response.results.length);
callback(null, isArray ? response.results : response.results[0]);
});
}
writeSingleNode(...args) {
const nodeId = args[0];
const value = args[1];
const callback = args[2];
(0, node_opcua_assert_1.assert)(typeof callback === "function");
const nodeToWrite = new node_opcua_service_write_1.WriteValue({
attributeId: node_opcua_service_read_1.AttributeIds.Value,
indexRange: undefined,
nodeId: this.resolveNodeId(nodeId),
value: new node_opcua_data_value_1.DataValue({ value })
});
this.write(nodeToWrite, (err, statusCode) => {
/* c8 ignore next */
if (err) {
return callback(err);
}
(0, node_opcua_assert_1.assert)(statusCode);
callback(null, statusCode);
});
}
readAllAttributes(...args) {
const nodes = args[0];
const callback = args[1];
(0, node_opcua_pseudo_session_1.readAllAttributes)(this, nodes)
.then((data) => callback(null, data))
.catch((err) => callback(err));
}
/**
* @internal
* @param args
*/
read(...args) {
if (args.length === 2) {
return this.read(args[0], 0, args[1]);
}
(0, node_opcua_assert_1.assert)(args.length === 3);
const isArray = Array.isArray(args[0]);
const nodesToRead = isArray ? args[0] : [args[0]];
(0, node_opcua_assert_1.assert)(Array.isArray(nodesToRead));
const maxAge = args[1];
const callback = args[2];
(0, node_opcua_assert_1.assert)(typeof callback === "function");
/* c8 ignore next */
if (helpAPIChange) {
// the read method deprecation detection and warning
if (!((0, node_opcua_utils_1.getFunctionParameterNames)(callback)[1] === "dataValues" || (0, node_opcua_utils_1.getFunctionParameterNames)(callback)[1] === "dataValue")) {
warningLog(chalk_1.default.red("[NODE-OPCUA-E04] the ClientSession#read API has changed !!, please fix the client code"));
warningLog(chalk_1.default.red(" replace ..:"));
warningLog(chalk_1.default.cyan(" session.read(nodesToRead,function(err,nodesToRead,results) {}"));
warningLog(chalk_1.default.red(" with .... :"));
warningLog(chalk_1.default.cyan(" session.read(nodesToRead,function(err,dataValues) {}"));
warningLog("");
warningLog(chalk_1.default.yellow("please make sure to refactor your code and check that " +
"the second argument of your callback function is named"), chalk_1.default.cyan("dataValue" + (isArray ? "s" : "")));
warningLog(chalk_1.default.cyan("to make this exception disappear"));
throw new Error("ERROR ClientSession#read API has changed !!, please fix the client code");
}
}
// coerce nodeIds
for (const node of nodesToRead) {
node.nodeId = this.resolveNodeId(node.nodeId);
}
const request = new node_opcua_service_read_1.ReadRequest({
maxAge,
nodesToRead,
timestampsToReturn: node_opcua_service_read_1.TimestampsToReturn.Both
});
this.performMessageTransaction(request, (err, response) => {
/* c8 ignore next */
if (err) {
return callback(err, response);
}
/* c8 ignore next */
if (!response || !(response instanceof node_opcua_service_read_1.ReadResponse)) {
return callback(new Error("Internal Error"));
}
// perform ExtensionObject resolution
(0, node_opcua_client_dynamic_extension_object_1.promoteOpaqueStructure)(this, response.results)
.then(() => {
response.results = response.results || /* c8 ignore next */ [];
callback(null, isArray ? response.results : response.results[0]);
})
.catch((err) => {
callback(err);
});
});
}
emitCloseEvent(statusCode) {
if (!this._closeEventHasBeenEmitted) {
debugLog("ClientSession#emitCloseEvent");
this._closeEventHasBeenEmitted = true;
this.emit("session_closed", statusCode);
}
}
createSubscription(options, callback) {
this._defaultRequest(node_opcua_service_subscription_1.CreateSubscriptionRequest, node_opcua_service_subscription_1.CreateSubscriptionResponse, options, callback);
}
createSubscription2(...args) {
const createSubscriptionRequest = args[0];
let callback = args[1];
const subscription = new client_subscription_impl_1.ClientSubscriptionImpl(this, createSubscriptionRequest);
// tslint:disable-next-line:no-empty
subscription.on("error", (err) => {
if (callback) {
callback(err);
callback = null;
}
});
subscription.on("started", () => {
(0, node_opcua_assert_1.assert)(subscription.session === this, "expecting a session here");
if (callback) {
callback(null, subscription);
callback = null;
}
});
}
deleteSubscriptions(options, callback) {
this._defaultRequest(node_opcua_service_subscription_1.DeleteSubscriptionsRequest, node_opcua_service_subscription_1.DeleteSubscriptionsResponse, options, callback);
}
setTriggering(request, callback) {
this._defaultRequest(node_opcua_service_subscription_1.SetTriggeringRequest, node_opcua_service_subscription_1.SetTriggeringResponse, request, callback);
}
/**
*/
transferSubscriptions(options, callback) {
this._defaultRequest(node_opcua_service_subscription_1.TransferSubscriptionsRequest, node_opcua_service_subscription_1.TransferSubscriptionsResponse, options, callback);
}
createMonitoredItems(options, callback) {
this._defaultRequest(node_opcua_service_subscription_1.CreateMonitoredItemsRequest, node_opcua_service_subscription_1.CreateMonitoredItemsResponse, options, callback);
}
modifyMonitoredItems(options, callback) {
this._defaultRequest(node_opcua_service_subscription_1.ModifyMonitoredItemsRequest, node_opcua_service_subscription_1.ModifyMonitoredItemsResponse, options, callback);
}
/**
*
*/
modifySubscription(options, callback) {
this._defaultRequest(node_opcua_service_subscription_1.ModifySubscriptionRequest, node_opcua_service_subscription_1.ModifySubscriptionResponse, options, callback);
}
setMonitoringMode(options, callback) {
this._defaultRequest(node_opcua_service_subscription_1.SetMonitoringModeRequest, node_opcua_service_subscription_1.SetMonitoringModeResponse, options, callback);
}
/**
*/
publish(options, callback) {
this._defaultRequest(node_opcua_service_subscription_1.PublishRequest, node_opcua_service_subscription_1.PublishResponse, options, callback);
}
/**
*
*/
republish(options, callback) {
this._defaultRequest(node_opcua_service_subscription_1.RepublishRequest, node_opcua_service_subscription_1.RepublishResponse, options, callback);
}
/**
*
*/
deleteMonitoredItems(options, callback) {
this._defaultRequest(node_opcua_service_subscription_1.DeleteMonitoredItemsRequest, node_opcua_service_subscription_1.DeleteMonitoredItemsResponse, options, callback);
}
/**
* @internal
*/
setPublishingMode(...args) {
const publishingEnabled = args[0];
const isArray = Array.isArray(args[1]);
const subscriptionIds = isArray ? args[1] : [args[1]];
const callback = args[2];
(0, node_opcua_assert_1.assert)(typeof callback === "function");
(0, node_opcua_assert_1.assert)(publishingEnabled === true || publishingEnabled === false);
const options = new node_opcua_service_subscription_1.SetPublishingModeRequest({
publishingEnabled,
subscriptionIds
});
this._defaultRequest(node_opcua_service_subscription_1.SetPublishingModeRequest, node_opcua_service_subscription_1.SetPublishingModeResponse, options, (err, response) => {
/* c8 ignore next */
if (err) {
return callback(err);
}
/* c8 ignore next */
if (!response) {
return callback(new Error("Internal Error"));
}
response.results = response.results || /* c8 ignore next */ [];
callback(err, isArray ? response.results : response.results[0]);
});
}
/**
* @internal
* @param args
*/
translateBrowsePath(...args) {
const isArray = Array.isArray(args[0]);
const browsePaths = isArray ? args[0] : [args[0]];
const callback = args[1];
(0, node_opcua_assert_1.assert)(typeof callback === "function");
const request = new node_opcua_service_translate_browse_path_1.TranslateBrowsePathsToNodeIdsRequest({ browsePaths });
this.performMessageTransaction(request, (err, response) => {
/* c8 ignore next */
if (err) {
return callback(err, response);
}
/* c8 ignore next */
if (!response || !(response instanceof node_opcua_service_translate_browse_path_1.TranslateBrowsePathsToNodeIdsResponse)) {
return callback(new Error("Internal Error"));
}
response.results = response.results || /* c8 ignore next */ [];
callback(null, isArray ? response.results : response.results[0]);
});
}
channelId() {
return this._client !== null && this._client._secureChannel !== null && this._client._secureChannel.isOpened()
? this._client._secureChannel.channelId
: -1;
}
isChannelValid() {
/* c8 ignore next */
if (!this._client) {
debugLog(chalk_1.default.red("Warning SessionClient is null ?"));
}
return this._client !== null && this._client._secureChannel !== null && this._client._secureChannel.isOpened();
}
performMessageTransaction(request, callback) {
if (!this._client) {
// session may have been closed by user ... but is still in used !!
return callback(new Error("Session has been closed and should not be used to perform a transaction anymore"));
}
if (request instanceof node_opcua_service_subscription_1.PublishRequest ||
request instanceof node_opcua_types_1.ActivateSessionRequest ||
request instanceof node_opcua_types_1.CloseSessionRequest) {
return this._performMessageTransaction(request, callback);
}
if (this._reconnecting.pendingTransactions.length > 0) {
/* c8 ignore next */
if (this._reconnecting.pendingTransactions.length > 10) {
if (!pendingTransactionMessageDisplayed) {
pendingTransactionMessageDisplayed = true;
warningLog("[NODE-OPCUA-W21]", "Pending transactions: ", this._reconnecting.pendingTransactions.map((a) => a.request.constructor.name).join(" "));
warningLog("[NODE-OPCUA-W22]", chalk_1.default.yellow("Warning : your opcua client is sending multiple requests simultaneously to the server", request.constructor.name), "\n", chalk_1.default.yellow(" please fix your application code"));
}
}
else if (this._reconnecting.pendingTransactions.length > 3) {
debugLog(chalk_1.default.yellow("Warning : your client is sending multiple requests simultaneously to the server", request.constructor.name));
}
this._reconnecting.pendingTransactions.push({ request, callback });
return;
}
this.#reprocessRequest(0, request, callback);
}
#reprocessRequest(attemptCount, request, callback) {
attemptCount > 0 &&
warningLog("reprocessRequest => ", request.constructor.name, this._reconnecting.pendingTransactions.length);
this._performMessageTransaction(request, (err, response) => {
if (err && err.message.match(/BadSessionIdInvalid/) && request.constructor.name !== "ActivateSessionRequest") {
warningLog("Transaction on Invalid Session ", request.constructor.name, err.message, "isReconnecting?=", this.isReconnecting, "q=", this._reconnecting.pendingTransactions.length);
request.requestHeader.requestHandle = node_opcua_secure_channel_1.requestHandleNotSetValue;
if (this.isReconnecting) {
this.once("session_restored", () => {
warningLog("redoing", request.constructor.name, this.isReconnecting);
this.#reprocessRequest(attemptCount + 1, request, callback);
});
}
else {
this.#_recreate_session_and_reperform_transaction(request, callback);
}
return;
}
callback(err, response);
const length = this._reconnecting.pendingTransactions.length; // record length before callback is called !
if (length > 0) {
debugLog("reprocessRequest => ", this._reconnecting.pendingTransactions.length, " transaction(s) left in queue");
// tslint:disable-next-line: no-shadowed-variable
const { request, callback } = this._reconnecting.pendingTransactions.shift();
this.#reprocessRequest(0, request, callback);
}
});
}
_performMessageTransaction(request, callback) {
(0, node_opcua_assert_1.assert)(typeof callback === "function");
/* c8 ignore next */
if (!this._client) {
// session may have been closed by user ... but is still in used !!
return callback(new Error("Session has been closed and should not be used to perform a transaction anymore"));
}
if (!this.isChannelValid()) {
// the secure channel is broken, may be the server has crashed or the network cable has been disconnected
// for a long time
// we may need to queue this transaction, as a secure token may be being reprocessed
debugLog(chalk_1.default.bgWhite.red("!!! Performing transaction on invalid channel !!! ", request.constructor.name));
return callback(new Error("Invalid Channel BadConnectionClosed"));
}
// is this stuff useful?
if (request.requestHeader) {
request.requestHeader.authenticationToken = this.authenticationToken;
}
this.lastRequestSentTime = new Date();
this._client.performMessageTransaction(request, (err, response) => {
this.lastResponseReceivedTime = new Date();
/* c8 ignore next */
if (err) {
if (response && response.responseHeader.serviceDiagnostics) {
err.serviceDiagnostics = response.responseHeader.serviceDiagnostics;
}
if (response && response.diagnosticInfos) {
err.diagnosticsInfo = response.diagnosticInfos;
}
return callback(err);
}
/* c8 ignore next */
if (!response) {
return callback(new Error("internal Error"));
}
/* c8 ignore next */
if (response.responseHeader.serviceResult.isNot(node_opcua_status_code_1.StatusCodes.Good)) {
err = new Error(" ServiceResult is " +
response.responseHeader.serviceResult.toString() +
" request was " +
request.constructor.name);
if (response && response.responseHeader.serviceDiagnostics) {
err.serviceDiagnostics = response.responseHeader.serviceDiagnostics;
}
if (response && response.diagnosticInfos) {
err.diagnosticsInfo = response.diagnosticInfos;
}
return callback(err, response);
}
return callback(null, response);
});
}
/**
* evaluate the remaining time for the session
*
*
* evaluate the time in milliseconds that the session will live
* on the server end from now.
* The remaining live time is calculated based on when the last message was sent to the server
* and the session timeout.
*
* * In normal operation , when server and client communicates on a regular
* basis, evaluateRemainingLifetime will return a number slightly below
* session.timeout
*
* * when the client and server cannot communicate due to a network issue
* (or a server crash), evaluateRemainingLifetime returns the estimated number
* of milliseconds before the server (if not crash) will keep the session alive
* on its end to allow a automatic reconnection with session.
*
* * When evaluateRemainingLifetime returns zero , this mean that
* the session has probably ended on the server side and will have to be recreated
* from scratch in case of a reconnection.
*
* @return the number of milliseconds before session expires
*/
evaluateRemainingLifetime() {
const now = Date.now();
const expiryTime = this.lastRequestSentTime.getTime() + this.timeout;
return Math.max(0, expiryTime - now);
}
_terminatePublishEngine() {
if (this._publishEngine) {
this._publishEngine.terminate();
this._publishEngine = null;
}
}
/**
* @internal
* @param args
*/
close(...args) {
if (arguments.length === 1) {
return this.close(true, args[0]);
}
const deleteSubscription = args[0];
const callback = args[1];
(0, node_opcua_assert_1.assert)(typeof callback === "function");
(0, node_opcua_assert_1.assert)(typeof deleteSubscription === "boolean");
/* c8 ignore next */
if (!this._client) {
debugLog("ClientSession#close : warning, client is already closed");
return callback(); // already close ?
}
(0, node_opcua_assert_1.assert)(this._client);
this._terminatePublishEngine();
this._client.closeSession(this, deleteSubscription, (err) => {
debugLog("session Close err ", err ? err.message : "null");
callback();
});
}
/**
* @return {Boolean}
*/
hasBeenClosed() {
return (0, node_opcua_utils_1.isNullOrUndefined)(this._client) || this._closed || this._closeEventHasBeenEmitted;
}
/**
* @internal
* @param args
*/
call(...args) {
const isArray = Array.isArray(args[0]);
const methodsToCall = isArray ? args[0] : [args[0]];
(0, node_opcua_assert_1.assert)(Array.isArray(methodsToCall));
const callback = args[1];
// Note : The client has no explicit address space and therefore will struggle to
// access the method arguments signature.
// There are two methods that can be considered:
// - get the object definition by querying the server
// - load a fake address space to have some thing to query on our end
// const request = this._client.factory.constructObjectId("CallRequest",{ methodsToCall: methodsToCall});
const request = new node_opcua_service_call_1.CallRequest({ methodsToCall });
this.performMessageTransaction(request, (err, response) => {
/* c8 ignore next */
if (err) {
return callback(err);
}
/* c8 ignore next */
if (!response || !(response instanceof node_opcua_service_call_1.CallResponse)) {
return callback(new Error("internal error"));
}
response.results = response.results || [];
promoteOpaqueStructureForCall(this, response.results)
.then(() => {
callback(null, isArray ? response.results : response.results[0]);
})
.catch((err) => {
callback(err);
});
});
}
getMonitoredItems(...args) {
const subscriptionId = args[0];
const callback = args[1];
// <UAObject NodeId="i=2253" BrowseName="Server">
// <UAMethod NodeId="i=11492" BrowseName="GetMonitoredItems"
// ParentNodeId="i=2253" MethodDeclarationId="i=11489">
// <UAMethod NodeId="i=11489" BrowseName="GetMonitoredItems" ParentNodeId="i=2004">
const methodsToCall = new node_opcua_service_call_1.CallMethodRequest({
inputArguments: [
// BaseDataType
{ dataType: node_opcua_variant_1.DataType.UInt32, value: subscriptionId }
],
methodId: (0, node_opcua_nodeid_1.coerceNodeId)("ns=0;i=11492"), // MethodIds.Server_GetMonitoredItems;
objectId: (0, node_opcua_nodeid_1.coerceNodeId)("ns=0;i=2253") // ObjectId.Server
});
this.call(methodsToCall, (err, result) => {
/* c8 ignore next */
if (err) {
return callback(err);
}
/* c8 ignore next */
if (!result) {
return callback(new Error("internal error"));
}
/* c8 ignore next */
if (result.statusCode.isNot(node_opcua_status_code_1.StatusCodes.Good)) {
callback(new Error(result.statusCode.toString()));
}
else {
result.outputArguments = result.outputArguments || [];
(0, node_opcua_assert_1.assert)(result.outputArguments.length === 2);
const data = {
clientHandles: result.outputArguments[1].value,
serverHandles: result.outputArguments[0].value //
};
// Note some server might return null array
// let make sure we have Uint32Array and not a null pointer
data.serverHandles = data.serverHandles || /* c8 ignore next */ emptyUint32Array;
data.clientHandles = data.clientHandles || /* c8 ignore next */ emptyUint32Array;
(0, node_opcua_assert_1.assert)(data.serverHandles instanceof Uint32Array);
(0, node_opcua_assert_1.assert)(data.clientHandles instanceof Uint32Array);
callback(null, data);
}
});
}
/**
* @internal
*/
getArgumentDefinition(...args) {
const methodId = args[0];
const callback = args[1];
(0, node_opcua_assert_1.assert)(typeof callback === "function");
return (0, node_opcua_pseudo_session_1.getArgumentDefinitionHelper)(this, methodId)
.then((result) => {
callback(null, result);
})
.catch((err) => {
callback(err);
});
}
registerNodes(...args) {
const nodesToRegister = args[0];
const callback = args[1];
(0, node_opcua_assert_1.assert)(typeof callback === "function");
(0, node_opcua_assert_1.assert)(Array.isArray(nodesToRegister));
const request = new node_opcua_service_register_node_1.RegisterNodesRequest({
nodesToRegister: nodesToRegister.map((n) => this.resolveNodeId(n))
});
this.performMessageTransaction(request, (err, response) => {
/* c8 ignore next */
if (err) {
return callback(err);
}
/* c8 ignore next */
if (!response || !(response instanceof node_opcua_service_register_node_1.RegisterNodesResponse)) {
return callback(new Error("Internal Error"));
}
response.registeredNodeIds = response.registeredNodeIds || /* c8 ignore next */ [];
callback(null, response.registeredNodeIds);
});
}
unregisterNodes(...args) {
const nodesToUnregister = args[0];
const callback = args[1];
(0, node_opcua_assert_1.assert)(typeof callback === "function");
(0, node_opcua_assert_1.assert)(Array.isArray(nodesToUnregister));
const request = new node_opcua_service_register_node_1.UnregisterNodesRequest({
nodesToUnregister: nodesToUnregister.map((n) => this.resolveNodeId(n))
});
this.performMessageTransaction(request, (err, response) => {
/* c8 ignore next */
if (err) {
return callback(err);
}
/* c8 ignore next */
if (!response || !(response instanceof node_opcua_service_register_node_1.UnregisterNodesResponse)) {
return callback(new Error("Internal Error"));
}
callback();
});
}
queryFirst(...args) {
const queryFirstRequest = args[0];
const callback = args[1];
(0, node_opcua_assert_1.assert)(typeof callback === "function");
const request = new node_opcua_service_query_1.QueryFirstRequest(queryFirstRequest);
this.performMessageTransaction(request, (err, response) => {
/* c8 ignore next */
if (err) {
return callback(err);
}
/* c8 ignore next */
if (!response || !(response instanceof node_opcua_service_query_1.QueryFirstResponse)) {
return callback(new Error("internal error"));
}
callback(null, response);
});
}
startKeepAliveManager(keepAliveInterval) {
if (this._keepAliveManager) {
// "keepAliveManger already started"
return;
}
this._keepAliveManager = new client_session_keepalive_manager_1.ClientSessionKeepAliveManager(this);
this._keepAliveManager.on("failure", () => {
/**
* raised when a keep-alive request has failed on the session, may be the session has timeout
* unexpectedly on the server side, may be the connection is broken.
* @event keepalive_failure
*/
this.emit("keepalive_failure");
});
this._keepAliveManager.on("keepalive", (state, count) => {
/**
* @event keepalive
*/
this.emit("keepalive", state, count);
});
this._keepAliveManager.start(keepAliveInterval);
}
stopKeepAliveManager() {
if (this._keepAliveManager) {
this._keepAliveManager.stop();
this._keepAliveManager = undefined;
}
}
dispose() {
(0, node_opcua_assert_1.assert)(this._closeEventHasBeenEmitted);
this._terminatePublishEngine();
this.stopKeepAliveManager();
this.removeAllListeners();
//
const privateThis = this;
if (!(!privateThis.pendingTransactions || privateThis.pendingTransactions.length === 0)) {
// tslint:disable-next-line: no-console
warningLog("dispose when pendingTransactions is not empty ");
}
}
toString() {
const now = Date.now();
const lap1 = now - this.lastRequestSentTime.getTime();
const lap2 = now - this.lastResponseReceivedTime.getTime();
const timeoutDelay = this.timeout - lap1;
const timeoutInfo = timeoutDelay < 0
? chalk_1.default.red(" expired since " + -timeoutDelay / 1000 + " seconds")
: chalk_1.default.green(" timeout in " + timeoutDelay / 1000 + " seconds");
let str = "";
str += " name..................... " + this.name;
str += "\n sessionId................ " + this.sessionId.toString();
str += "\n authenticationToken...... " + (this.authenticationToken ? this.authenticationToken.toString() : "");
str += "\n timeout.................. " + this.timeout + "ms" + timeoutInfo;
str += "\n serverNonce.............. " + (this.serverNonce ? this.serverNonce.toString("hex") : "");
str += "\n serverCertificate........ " + (0, node_opcua_utils_1.buffer_ellipsis)(this.serverCertificate);
str += "\n serverSignature.......... " + this.serverSignature;
str += "\n lastRequestSentTime...... " + new Date(this.lastRequestSentTime).toISOString() + " (" + lap1 + ")";
str += "\n lastResponseReceivedTime. " + new Date(this.lastResponseReceivedTime).toISOString() + " (" + lap2 + ")";
str += "\n isReconnecting........... " + this.isReconnecting;
str += "\n isValidChannel........... " + this.isChannelValid() + " has been closed " + this.hasBeenClosed();
str += "\n channelId................ " + this.channelId();
str += "\n remaining life time...... " + this.evaluateRemainingLifetime();
str += "\n subscription count....... " + this.subscriptionCount;
if (this._client && this._client._secureChannel) {
if (this._client._secureChannel.activeSecurityToken) {
str += "\n reviseTokenLifetime...... " + this._client._secureChannel.activeSecurityToken.revisedLifetime;
}
}
str += "\n keepAlive ................ " + this._keepAliveManager ? true : false;
if (this._keepAliveManager) {
str += "\n keepAlive checkInterval.. " + this._keepAliveManager.checkInterval + " ms";
str += "\n (defaultTransportTimeout)." + node_opcua_secure_channel_1.ClientSecureChannelLayer.defaultTransportTimeout + " ms";
str += "\n session timeout " + this.timeout + " ms";
}
return str;
}
getBuiltInDataType(...args) {
const nodeId = args[0];