"use strict";
/**
* @module opcua.client
*/
const util = require("util");
const _ = require("underscore");
const assert = require("node-opcua-assert").assert;
const crypto = require("crypto");
const async = require("async");
const chalk = require("chalk");
const StatusCodes = require("node-opcua-status-code").StatusCodes;
const session_service = require("node-opcua-service-session");
const AnonymousIdentityToken = session_service.AnonymousIdentityToken;
const CreateSessionRequest = session_service.CreateSessionRequest;
const CreateSessionResponse = session_service.CreateSessionResponse;
const ActivateSessionRequest = session_service.ActivateSessionRequest;
const ActivateSessionResponse = session_service.ActivateSessionResponse;
const CloseSessionRequest = session_service.CloseSessionRequest;
const endpoints_service = require("node-opcua-service-endpoints");
const ApplicationDescription = endpoints_service.ApplicationDescription;
const ApplicationType = endpoints_service.ApplicationType;
const EndpointDescription = endpoints_service.EndpointDescription;
const MessageSecurityMode = require("node-opcua-service-secure-channel").MessageSecurityMode;
const SecurityPolicy = require("node-opcua-secure-channel").SecurityPolicy;
const getCryptoFactory = require("node-opcua-secure-channel").getCryptoFactory;
const fromURI = require("node-opcua-secure-channel").fromURI;
const crypto_utils = require("node-opcua-crypto");
const UserNameIdentityToken = session_service.UserNameIdentityToken;
const buffer_utils = require("node-opcua-buffer-utils");
const createFastUninitializedBuffer = buffer_utils.createFastUninitializedBuffer;
const UserIdentityTokenType = require("node-opcua-service-endpoints").UserIdentityTokenType;
const ClientSession = require("./client_session").ClientSession;
const utils = require("node-opcua-utils");
const debugLog = require("node-opcua-debug").make_debugLog(__filename);
const doDebug = require("node-opcua-debug").checkDebugFlag(__filename);
const OPCUAClientBase = require("./client_base").OPCUAClientBase;
const isNullOrUndefined = require("node-opcua-utils").isNullOrUndefined;
function validateServerNonce(serverNonce) {
return (serverNonce && serverNonce.length < 32) ? false : true;
}
/**
* @class OPCUAClient
* @extends OPCUAClientBase
* @param options
* @param [options.securityMode=MessageSecurityMode.None] {MessageSecurityMode} the default security mode.
* @param [options.securityPolicy =SecurityPolicy.NONE] {SecurityPolicy} the security mode.
* @param [options.requestedSessionTimeout= 60000] {Number} the requested session time out in CreateSession
* @param [options.applicationName="NodeOPCUA-Client"] {string} the client application name
* @param [options.endpoint_must_exist=true] {Boolean} set to false if the client should accept server endpoint mismatch
* @param [options.keepSessionAlive=false]{Boolean}
* @param [options.certificateFile="certificates/client_selfsigned_cert_1024.pem"] {String} client certificate pem file.
* @param [options.privateKeyFile="certificates/client_key_1024.pem"] {String} client private key pem file.
* @param [options.clientName=""] {String} a client name string that will be used to generate session names.
* @constructor
*/
function OPCUAClient(options) {
options = options || {};
OPCUAClientBase.apply(this, arguments);
// @property endpoint_must_exist {Boolean}
// if set to true , create Session will only accept connection from server which endpoint_url has been reported
// by GetEndpointsRequest.
// By default, the client is strict.
this.endpoint_must_exist = (isNullOrUndefined(options.endpoint_must_exist)) ? true : !!options.endpoint_must_exist;
this.requestedSessionTimeout = options.requestedSessionTimeout || 60000; // 1 minute
this.applicationName = options.applicationName || "NodeOPCUA-Client";
}
util.inherits(OPCUAClient, OPCUAClientBase);
OPCUAClient.prototype._nextSessionName = function () {
if (!this.___sessionName_counter) {
this.___sessionName_counter = 0;
}
this.___sessionName_counter += 1;
return this.clientName + this.___sessionName_counter;
};
const makeApplicationUrn = require("node-opcua-common").makeApplicationUrn;
const hostname = require("node-opcua-hostname").get_fully_qualified_domain_name();
OPCUAClient.prototype._getApplicationUri = function () {
// get applicationURI from certificate
const exploreCertificate = require("node-opcua-crypto").exploreCertificate;
const certificate = this.getCertificate();
let applicationUri;
if (certificate) {
const e = exploreCertificate(certificate);
if (!e.tbsCertificate.extensions || !e.tbsCertificate.extensions.subjectAltName) {
console.log(chalk.red(" Warning: client certificate is invalid : subjectAltName is missing"));
applicationUri = makeApplicationUrn(hostname, this.applicationName);
} else {
applicationUri = e.tbsCertificate.extensions.subjectAltName.uniformResourceIdentifier[0];
}
} else {
applicationUri = makeApplicationUrn(hostname, this.applicationName);
}
return applicationUri;
};
OPCUAClient.prototype.__resolveEndPoint = function () {
this.securityPolicy = this.securityPolicy || SecurityPolicy.None;
let endpoint = this.findEndpoint(this._secureChannel.endpointUrl, this.securityMode, this.securityPolicy);
this.endpoint = endpoint;
// this is explained here : see OPCUA Part 4 Version 1.02 $5.4.1 page 12:
// A Client shall verify the HostName specified in the Server Certificate is the same as the HostName
// contained in the endpointUrl provided in the EndpointDescription. If there is a difference then the
// Client shall report the difference and may close the SecureChannel.
if (!this.endpoint) {
if (this.endpoint_must_exist) {
debugLog("OPCUAClient#endpoint_must_exist = true and endpoint with url ", this._secureChannel.endpointUrl, " cannot be found");
return false;
} else {
// fallback :
// our strategy is to take the first server_end_point that match the security settings
// ( is this really OK ?)
// this will permit us to access a OPCUA Server using it's IP address instead of its hostname
endpoint = this.findEndpointForSecurity(this.securityMode, this.securityPolicy);
if (!endpoint) {
return false;
}
this.endpoint = endpoint;
}
}
return true;
};
OPCUAClient.prototype._createSession = function (callback) {
const self = this;
assert(typeof callback === "function");
assert(self._secureChannel);
if (!self.__resolveEndPoint() || !self.endpoint) {
console.log(this._server_endpoints.map(function (endpoint){
return endpoint.endpointUrl + " " + endpoint.securityMode.toString() + " " + endpoint.securityPolicyUri;
}));
return callback(new Error(" End point must exist " + self._secureChannel.endpointUrl));
}
self.serverUri = self.endpoint.server.applicationUri;
self.endpointUrl = self._secureChannel.endpointUrl;
const session = new ClientSession(self);
this.__createSession_step2(session, callback);
};
function verifyEndpointDescriptionMatches(client,responseServerEndpoints) {
// The Server returns its EndpointDescriptions in the response. Clients use this information to
// determine whether the list of EndpointDescriptions returned from the Discovery Endpoint matches
// the Endpoints that the Server has. If there is a difference then the Client shall close the
// Session and report an error.
// The Server returns all EndpointDescriptions for the serverUri
// specified by the Client in the request. The Client only verifies EndpointDescriptions with a
// transportProfileUri that matches the profileUri specified in the original GetEndpoints request.
// A Client may skip this check if the EndpointDescriptions were provided by a trusted source
// such as the Administrator.
//serverEndpoints:
// The Client shall verify this list with the list from a Discovery Endpoint if it used a Discovery Endpoint
// fetch to the EndpointDescriptions.
// ToDo
return true;
}
OPCUAClient.prototype.__createSession_step2 = function (session, callback) {
const self = this;
assert(typeof callback === "function");
assert(self._secureChannel);
assert(self.serverUri, " must have a valid server URI");
assert(self.endpointUrl, " must have a valid server endpointUrl");
assert(self.endpoint);
const applicationUri = self._getApplicationUri();
const applicationDescription = new ApplicationDescription({
applicationUri: applicationUri,
productUri: "NodeOPCUA-Client",
applicationName: {text: self.applicationName},
applicationType: ApplicationType.CLIENT,
gatewayServerUri: undefined,
discoveryProfileUri: undefined,
discoveryUrls: []
});
// note : do not confuse CreateSessionRequest.clientNonce with OpenSecureChannelRequest.clientNonce
// which are two different nonce, with different size (although they share the same name )
self.clientNonce = crypto.randomBytes(32);
const request = new CreateSessionRequest({
clientDescription: applicationDescription,
serverUri: self.serverUri,
endpointUrl: self.endpointUrl,
sessionName: self._nextSessionName(),
clientNonce: self.clientNonce,
clientCertificate: self.getCertificate(),
requestedSessionTimeout: self.requestedSessionTimeout,
maxResponseMessageSize: 800000
});
/* a client Nonce must be provided if security mode is set*/
assert(self._secureChannel.securityMode === MessageSecurityMode.NONE || request.clientNonce !== null);
self.performMessageTransaction(request, function (err, response) {
if (!err) {
//xx console.log("xxxxx response",response.toString());
//xx console.log("xxxxx response",response.responseHeader.serviceResult);
if (response.responseHeader.serviceResult === StatusCodes.BadTooManySessions) {
err = new Error("Too Many Sessions : " + response.responseHeader.serviceResult.toString());
} else if (response.responseHeader.serviceResult === StatusCodes.Good) {
assert(response instanceof CreateSessionResponse);
// istanbul ignore next
if (!validateServerNonce(request.serverNonce)) {
return callback(new Error("invalid server Nonce"));
}
// todo: verify SignedSoftwareCertificates and response.serverSignature
session = session || new ClientSession(self);
session.name = request.sessionName;
session.sessionId = response.sessionId;
session.authenticationToken = response.authenticationToken;
session.timeout = response.revisedSessionTimeout;
session.serverNonce = response.serverNonce;
session.serverCertificate = response.serverCertificate;
session.serverSignature = response.serverSignature;
debugLog("revised session timeout = ".yellow, session.timeout);
if (!verifyEndpointDescriptionMatches(self,response.serverEndpoints)) {
console.log("Endpoint description previously retrieved with GetendpointsDescription");
console.log(self._server_endpoints);
console.log("CreateSessionResponse.serverEndpoints= ");
console.log(response.serverEndpoints);
return callback(new Error("Invalid endpoint descriptions Found" ));
}
//xx self._server_endpoints = response.serverEndpoints;
session.serverEndpoints = response.serverEndpoints;
} else {
err = new Error("Error " + response.responseHeader.serviceResult.name + " " + response.responseHeader.serviceResult.description);
}
}
if (err) {
callback(err);
} else {
callback(null, session);
}
});
};
const computeSignature = require("node-opcua-secure-channel").computeSignature;
OPCUAClient.prototype.computeClientSignature = function (channel, serverCertificate, serverNonce) {
const self = this;
return computeSignature(serverCertificate, serverNonce, self.getPrivateKey(), channel.messageBuilder.securityPolicy);
};
function isAnonymous(userIdentityInfo) {
return !userIdentityInfo || (!userIdentityInfo.userName && !userIdentityInfo.password);
}
function isUserNamePassword(userIdentityInfo) {
const res = (userIdentityInfo.userName !== undefined) && (userIdentityInfo.password !== undefined);
return res;
}
function findUserTokenPolicy(endpoint_description, userTokenType) {
assert(endpoint_description instanceof EndpointDescription);
const r = _.filter(endpoint_description.userIdentityTokens, function (userIdentity) {
// assert(userIdentity instanceof UserTokenPolicy)
assert(userIdentity.tokenType);
return userIdentity.tokenType === userTokenType;
});
return r.length === 0 ? null : r[0];
}
function createAnonymousIdentityToken(session) {
const endpoint_desc = session.endpoint;
assert(endpoint_desc instanceof EndpointDescription);
const userTokenPolicy = findUserTokenPolicy(endpoint_desc, UserIdentityTokenType.ANONYMOUS);
if (!userTokenPolicy) {
throw new Error("Cannot find ANONYMOUS user token policy in end point description");
}
return new AnonymousIdentityToken({policyId: userTokenPolicy.policyId});
}
function createUserNameIdentityToken(session, userName, password) {
// assert(endpoint instanceof EndpointDescription);
assert(userName === null || typeof userName === "string");
assert(password === null || typeof password === "string");
const endpoint_desc = session.endpoint;
assert(endpoint_desc instanceof EndpointDescription);
const userTokenPolicy = findUserTokenPolicy(endpoint_desc, UserIdentityTokenType.USERNAME);
// istanbul ignore next
if (!userTokenPolicy) {
throw new Error("Cannot find USERNAME user token policy in end point description");
}
let securityPolicy = fromURI(userTokenPolicy.securityPolicyUri);
// if the security policy is not specified we use the session security policy
if (securityPolicy === SecurityPolicy.Invalid) {
securityPolicy = session._client._secureChannel.securityPolicy;
assert(securityPolicy);
}
let userIdentityToken;
let serverCertificate = session.serverCertificate;
// if server does not provide certificate use unencrypted password
if (serverCertificate === null) {
userIdentityToken = new UserNameIdentityToken({
userName: userName,
password: Buffer.from(password, "utf-8"),
encryptionAlgorithm: null,
policyId: userTokenPolicy.policyId
});
return userIdentityToken;
}
assert(serverCertificate instanceof Buffer);
serverCertificate = crypto_utils.toPem(serverCertificate, "CERTIFICATE");
const publicKey = crypto_utils.extractPublicKeyFromCertificateSync(serverCertificate);
let serverNonce = session.serverNonce;
// if serverNonce not specified by server
if (serverNonce === null) {
serverNonce = Buffer.alloc(0);
}
assert(serverNonce instanceof Buffer);
// see Release 1.02 155 OPC Unified Architecture, Part 4
const cryptoFactory = getCryptoFactory(securityPolicy);
// istanbul ignore next
if (!cryptoFactory) {
throw new Error(" Unsupported security Policy");
}
userIdentityToken = new UserNameIdentityToken({
userName: userName,
password: Buffer.from(password, "utf-8"),
encryptionAlgorithm: cryptoFactory.asymmetricEncryptionAlgorithm,
policyId: userTokenPolicy.policyId
});
// now encrypt password as requested
const lenBuf = createFastUninitializedBuffer(4);
lenBuf.writeUInt32LE(userIdentityToken.password.length + serverNonce.length, 0);
const block = Buffer.concat([lenBuf, userIdentityToken.password, serverNonce]);
userIdentityToken.password = cryptoFactory.asymmetricEncrypt(block, publicKey);
return userIdentityToken;
}
OPCUAClient.prototype.createUserIdentityToken = function (session, userIdentityToken, callback) {
assert(_.isFunction(callback));
const self = this;
if (null === self.userIdentityInfo) {
return callback(null,null);
}
if (isAnonymous(self.userIdentityInfo)) {
try {
userIdentityToken = createAnonymousIdentityToken(session);
return callback(null, userIdentityToken);
}
catch (err) {
return callback(err);
}
} else if (isUserNamePassword(self.userIdentityInfo)) {
const userName = self.userIdentityInfo.userName;
const password = self.userIdentityInfo.password;
try {
userIdentityToken = createUserNameIdentityToken(session, userName, password);
return callback(null, userIdentityToken);
}
catch (err) {
//xx console.log(err.stack);
return callback(err);
}
} else {
console.log(" userIdentityToken = ", userIdentityToken);
return callback(new Error("CLIENT: Invalid userIdentityToken"));
}
};
// see OPCUA Part 4 - $7.35
OPCUAClient.prototype._activateSession = function (session, callback) {
assert(typeof callback === "function");
const self = this;
// istanbul ignore next
if (!self._secureChannel) {
return callback(new Error(" No secure channel"));
}
const serverCertificate = session.serverCertificate;
// If the securityPolicyUri is NONE and none of the UserTokenPolicies requires encryption,
// the Client shall ignore the ApplicationInstanceCertificate (serverCertificate)
assert(serverCertificate === null || serverCertificate instanceof Buffer);
const serverNonce = session.serverNonce;
assert(!serverNonce || serverNonce instanceof Buffer);
// make sure session is attached to this client
const _old_client = session._client;
session._client = self;
self.createUserIdentityToken(session, self.userIdentityInfo, function (err, userIdentityToken) {
if (err) {
session._client = _old_client;
return callback(err);
}
// TODO. fill the ActivateSessionRequest
// see 5.6.3.2 Parameters OPC Unified Architecture, Part 4 30 Release 1.02
const request = new ActivateSessionRequest({
// This is a signature generated with the private key associated with the
// clientCertificate. The SignatureAlgorithm shall be the AsymmetricSignatureAlgorithm
// specified in the SecurityPolicy for the Endpoint. The SignatureData type is defined in 7.30.
clientSignature: self.computeClientSignature(self._secureChannel, serverCertificate, serverNonce),
// These are the SoftwareCertificates which have been issued to the Client application. The productUri contained
// in the SoftwareCertificates shall match the productUri in the ApplicationDescription passed by the Client in
// the CreateSession requests. Certificates without matching productUri should be ignored. Servers may reject
// connections from Clients if they are not satisfied with the SoftwareCertificates provided by the Client.
// This parameter only needs to be specified in the first ActivateSession request after CreateSession.
// It shall always be omitted if the maxRequestMessageSize returned from the Server in the CreateSession
// response is less than one megabyte. The SignedSoftwareCertificate type is defined in 7.31.
clientSoftwareCertificates: [],
// List of locale ids in priority order for localized strings. The first LocaleId in the list has the highest
// priority. If the Server returns a localized string to the Client, the Server shall return the translation
// with the highest priority that it can. If it does not have a translation for any of the locales identified
// in this list, then it shall return the string value that it has and include the locale id with the string.
// See Part 3 for more detail on locale ids. If the Client fails to specify at least one locale id, the Server
// shall use any that it has.
// This parameter only needs to be specified during the first call to ActivateSession during a single
// application Session. If it is not specified the Server shall keep using the current localeIds for the Session.
localeIds: [],
// The credentials of the user associated with the Client application. The Server uses these credentials to
// determine whether the Client should be allowed to activate a Session and what resources the Client has access
// to during this Session. The UserIdentityToken is an extensible parameter type defined in 7.35.
// The EndpointDescription specifies what UserIdentityTokens the Server shall accept.
userIdentityToken: userIdentityToken,
// If the Client specified a user identity token that supports digital signatures,
// then it shall create a signature and pass it as this parameter. Otherwise the parameter is omitted.
// The SignatureAlgorithm depends on the identity token type.
userTokenSignature: {
algorithm: null,
signature: null
}
});
session.performMessageTransaction(request, function (err, response) {
if (!err && response.responseHeader.serviceResult === StatusCodes.Good) {
assert(response instanceof ActivateSessionResponse);
session.serverNonce = response.serverNonce;
if (!validateServerNonce(session.serverNonce)) {
return callback(new Error("Invalid server Nonce"));
}
return callback(null, session);
} else {
err = err || new Error(response.responseHeader.serviceResult.toString());
session._client = _old_client;
return callback(err, null);
}
});
});
};
/**
* transfer session to this client
* @method reactivateSession
* @param session
* @param callback
* @return {*}
*/
OPCUAClient.prototype.reactivateSession = function (session, callback) {
const self = this;
assert(typeof callback === "function");
assert(this._secureChannel, " client must be connected first");
// istanbul ignore next
if (!this.__resolveEndPoint() || !this.endpoint) {
return callback(new Error(" End point must exist " + this._secureChannel.endpointUrl));
}
assert(!session._client || session._client.endpointUrl === self.endpointUrl, "cannot reactivateSession on a different endpoint");
const old_client = session._client;
debugLog("OPCUAClient#reactivateSession");
this._activateSession(session, function (err) {
if (!err) {
if (old_client !== self) {
// remove session from old client:
if (old_client) {
old_client._removeSession(session);
assert(!_.contains(old_client._sessions, session));
}
self._addSession(session);
assert(session._client === self);
assert(!session.closed,"session should not vbe closed");
assert(_.contains(self._sessions, session));
}
} else {
// istanbul ignore next
if (doDebug) {
console.log("reactivateSession has failed !".red.bgWhite, err.message);
}
}
callback(err);
});
};
/**
* create and activate a new session
* @async
* @method createSession
*
* @param [userIdentityInfo {Object} ] optional
* @param [userIdentityInfo.userName {String} ]
* @param [userIdentityInfo.password {String} ]
*
* @param callback {Function}
* @param callback.err {Error|null} - the Error if the async method has failed
* @param callback.session {ClientSession} - the created session object.
*
*
* @example :
* // create a anonymous session
* client.createSession(function(err,session) {
* if (err) {} else {}
* });
*
* @example :
* // create a session with a userName and password
* client.createSession({userName: "JoeDoe", password:"secret"}, function(err,session) {
* if (err) {} else {}
* });
*
*/
OPCUAClient.prototype.createSession = function (userIdentityInfo, callback) {
const self = this;
if (_.isFunction(userIdentityInfo)) {
callback = userIdentityInfo;
userIdentityInfo = {};
}
self.userIdentityInfo = userIdentityInfo;
assert(_.isFunction(callback));
self._createSession(function (err, session) {
if (err) {
callback(err);
} else {
self._addSession(session);
self._activateSession(session, function (err) {
callback(err, session);
});
}
});
};
/**
* @method changeSessionIdentity
* @param session
* @param userIdentityInfo
* @param callback
* @async
*/
OPCUAClient.prototype.changeSessionIdentity = function (session, userIdentityInfo, callback) {
const self = this;
assert(_.isFunction(callback));
const old_userIdentity = self.userIdentityInfo;
self.userIdentityInfo = userIdentityInfo;
self._activateSession(session, function (err) {
callback(err);
});
};
OPCUAClient.prototype._closeSession = function (session, deleteSubscriptions, callback) {
const self = this;
assert(_.isFunction(callback));
assert(_.isBoolean(deleteSubscriptions));
// istanbul ignore next
if (!self._secureChannel) {
return callback(new Error("no channel"));
}
assert(self._secureChannel);
const request = new CloseSessionRequest({
deleteSubscriptions: deleteSubscriptions
});
if (!self._secureChannel.isValid()) {
return callback();
}
if (self.isReconnecting) {
console.log("OPCUAClient#_closeSession called while reconnection in progress ! What shall we do");
return callback();
}
session.performMessageTransaction(request, function (err, response) {
if (err) {
//xx console.log("xxx received : ", err, response);
//xx self._secureChannel.close(function () {
//xx callback(err, null);
//xx });
callback(err, null);
} else {
callback(err, response);
}
});
};
/**
*
* @method closeSession
* @async
* @param session {ClientSession} - the created client session
* @param deleteSubscriptions {Boolean} - whether to delete subscriptions or not
* @param callback {Function} - the callback
* @param callback.err {Error|null} - the Error if the async method has failed
*/
OPCUAClient.prototype.closeSession = function (session, deleteSubscriptions, callback) {
const self = this;
assert(_.isBoolean(deleteSubscriptions));
assert(_.isFunction(callback));
assert(session);
assert(session._client === self, "session must be attached to self");
session._closed = true;
//todo : send close session on secure channel
self._closeSession(session, deleteSubscriptions, function (err) {
session.emitCloseEvent();
self._removeSession(session);
session.dispose();
assert(!_.contains(self._sessions, session));
assert(session._closed, "session must indicate it is closed");
callback(err);
});
};
const repair_client_sessions= require("./reconnection").repair_client_sessions;
OPCUAClient.prototype._on_connection_reestablished = function (callback) {
const self = this;
assert(_.isFunction(callback));
// call base class implementation first
OPCUAClientBase.prototype._on_connection_reestablished.call(self, function (err) {
repair_client_sessions(self,callback);
});
};
OPCUAClient.prototype.toString = function () {
OPCUAClientBase.prototype.toString.call(this);
console.log(" requestedSessionTimeout....... ", this.requestedSessionTimeout);
console.log(" endpointUrl................... ", this.endpointUrl);
console.log(" serverUri..................... ", this.serverUri);
};
exports.OPCUAClient = OPCUAClient;
exports.ClientSession = ClientSession;
/**
* @method withSession
* @param inner_func {function}
* @param inner_func.session {ClientSession}
* @param inner_func.callback {function}
* @param callback {function}
*/
OPCUAClient.prototype.withSession = function (endpointUrl, inner_func, callback) {
assert(_.isFunction(inner_func), "expecting inner function");
assert(_.isFunction(callback), "expecting callback function");
const client = this;
let the_session;
let the_error;
let need_disconnect = false;
async.series([
// step 1 : connect to
function (callback) {
client.connect(endpointUrl, function (err) {
need_disconnect = true;
if (err) {
console.log(" cannot connect to endpoint :", endpointUrl);
}
callback(err);
});
},
// step 2 : createSession
function (callback) {
client.createSession(function (err, session) {
if (!err) {
the_session = session;
}
callback(err);
});
},
function (callback) {
try {
inner_func(the_session, function (err) {
the_error = err;
callback();
});
}
catch (err) {
console.log("OPCUAClient#withClientSession", err.message);
the_error = err;
callback();
}
},
// close session
function (callback) {
the_session.close(/*deleteSubscriptions=*/true, function (err) {
if (err) {
console.log("OPCUAClient#withClientSession: session closed failed ?");
}
callback();
});
},
function (callback) {
client.disconnect(function (err) {
need_disconnect = false;
if (err) {
console.log("OPCUAClient#withClientSession: client disconnect failed ?");
}
callback();
});
}
], function (err1) {
if (need_disconnect) {
console.log("Disconnecting client after failure");
client.disconnect(function (err2) {
return callback(the_error || err1 || err2);
});
} else {
return callback(the_error || err1);
}
});
};
const thenify = require("thenify");
/**
* @method connect
* @param endpointUrl {string}
* @async
* @return {Promise}
*/
OPCUAClient.prototype.connect = thenify.withCallback(OPCUAClient.prototype.connect);
/**
* @method disconnect
* disconnect client from server
* @return {Promise}
* @async
*/
OPCUAClient.prototype.disconnect = thenify.withCallback(OPCUAClient.prototype.disconnect);
/**
* @method createSession
* @param [userIdentityInfo {Object} ] optional
* @param [userIdentityInfo.userName {String} ]
* @param [userIdentityInfo.password {String} ]
* @return {Promise}
* @async
*
* @example
* // create a anonymous session
* const session = await client.createSession();
*
* @example
* // create a session with a userName and password
* const userIdentityInfo = { userName: "JoeDoe", password:"secret"};
* const session = client.createSession(userIdentityInfo);
*
*/
OPCUAClient.prototype.createSession = thenify.withCallback(OPCUAClient.prototype.createSession);
/**
* @method changeSessionIdentity
* @param session
* @param userIdentityInfo
* @return {Promise}
* @async
*/
OPCUAClient.prototype.changeSessionIdentity = thenify.withCallback(OPCUAClient.prototype.changeSessionIdentity);
/**
* @method closeSession
* @param session {ClientSession}
* @param deleteSubscriptions {Boolean} - whether to delete
* @return {Promise}
* @async
* @example
* const session = await client.createSession();
* await client.closeSession(session);
*/
OPCUAClient.prototype.closeSession = thenify.withCallback(OPCUAClient.prototype.closeSession);
const ClientSubscription = require("./client_subscription").ClientSubscription;
OPCUAClient.prototype.withSubscription = function (endpointUrl, subscriptionParameters, innerFunc, callback) {
assert(_.isFunction(innerFunc));
assert(_.isFunction(callback));
this.withSession(endpointUrl, function (session, done) {
assert(_.isFunction(done));
const subscription = new ClientSubscription(session, subscriptionParameters);
try {
innerFunc(session, subscription, function () {
subscription.terminate(function (err) {
done(err);
});
});
}
catch (err) {
console.log(err);
done(err);
}
}, callback);
};
//xx OPCUAClient.prototype.withSubscription = thenify(OPCUAClient.prototype.withSubscription);
const nodeVersion = parseInt(process.version.match(/v([0-9]*)\./)[1]);
if (nodeVersion >= 8) {
require("./opcua_client_es2017_extensions");
}