node-opcua-client 2.99.0 → 2.101.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -69,13 +69,11 @@ import { performCertificateSanityCheck } from "../verify";
69
69
  import { ClientSessionImpl } from "./client_session_impl";
70
70
  import { IClientBase } from "./i_private_client";
71
71
 
72
- // tslint:disable-next-line:no-var-requires
73
- const once = require("once");
74
-
75
72
  const debugLog = make_debugLog(__filename);
76
73
  const doDebug = checkDebugFlag(__filename);
77
74
  const errorLog = make_errorLog(__filename);
78
75
  const warningLog = make_warningLog(__filename);
76
+ const traceInternalState = false;
79
77
 
80
78
  const defaultConnectionStrategy: ConnectionStrategyOptions = {
81
79
  initialDelay: 1000,
@@ -268,6 +266,7 @@ type InternalClientState =
268
266
  | "disconnected"
269
267
  | "connecting"
270
268
  | "connected"
269
+ | "panic"
271
270
  | "reconnecting"
272
271
  | "reconnecting_newchannel_connected"
273
272
  | "disconnecting";
@@ -389,9 +388,9 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
389
388
 
390
389
  protected _setInternalState(internalState: InternalClientState): void {
391
390
  const previousState = this._internalState;
392
- if (doDebug) {
393
- debugLog(
394
- chalk.cyan(` Client ${this._instanceNumber} ${this.clientName} from `),
391
+ if (doDebug || traceInternalState) {
392
+ (traceInternalState ? warningLog : debugLog)(
393
+ chalk.cyan(` Client ${this._instanceNumber} ${this.clientName} : _internalState from `),
395
394
  chalk.yellow(previousState),
396
395
  "to",
397
396
  chalk.yellow(internalState)
@@ -476,12 +475,17 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
476
475
 
477
476
  private _cancel_reconnection(callback: ErrorCallback) {
478
477
  // _cancel_reconnection is invoked during disconnection
479
- // when we detect that a reconnection is in progress..
478
+ // when we detect that a reconnection is in progress...
479
+
480
+ // istanbul ignore next
480
481
  if (!this.isReconnecting) {
481
- warningLog("internal error : _cancel_reconnection should be used when reconnecting is in progress");
482
+ warningLog("internal error: _cancel_reconnection should only be used when reconnecting is in progress");
482
483
  }
484
+
483
485
  debugLog("canceling reconnection");
486
+
484
487
  this._reconnectionIsCanceled = true;
488
+
485
489
  // istanbul ignore next
486
490
  if (!this._secureChannel) {
487
491
  debugLog("_cancel_reconnection: Nothing to do !");
@@ -502,7 +506,7 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
502
506
 
503
507
  if (!this.knowsServerEndpoint) {
504
508
  debugLog("Cannot reconnect , server endpoint is unknown");
505
- return callback(new Error("Cannot reconnect, server endpoint is unknown"));
509
+ return callback(new Error("Cannot reconnect, server endpoint is unknown - this.knowsServerEndpoint = false"));
506
510
  }
507
511
  assert(this.knowsServerEndpoint);
508
512
 
@@ -521,7 +525,7 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
521
525
  };
522
526
 
523
527
  const _when_reconnectionIsCanceled = (callback: ErrorCallback) => {
524
- warningLog("attempt to recreate a new secure channel : suspended becasue reconnection is canceled !");
528
+ warningLog("attempt to recreate a new secure channel : suspended because reconnection is canceled !");
525
529
  this.emit("reconnection_canceled");
526
530
  return callback(new Error("Reconnection has been canceled - " + this.clientName));
527
531
  };
@@ -573,7 +577,7 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
573
577
 
574
578
  if (
575
579
  err!.message.match("BadCertificateInvalid") ||
576
- err!.message.match(/_socket has been disconnected by third party/)
580
+ err!.message.match(/socket has been disconnected by third party/)
577
581
  ) {
578
582
  warningLog(
579
583
  "the server certificate has changed, we need to retrieve server certificate again: ",
@@ -695,7 +699,7 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
695
699
  ],
696
700
  (err) => {
697
701
  if (err) {
698
- debugLog("Inner create secure channel has failed", err.message);
702
+ errorLog("Inner create secure channel has failed", err.message);
699
703
  if (this._secureChannel) {
700
704
  this._secureChannel!.abortConnection(() => {
701
705
  this._destroy_secure_channel();
@@ -839,7 +843,7 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
839
843
 
840
844
  // istanbul ignore next
841
845
  if (this._internalState !== "disconnected") {
842
- callback(new Error("invalid internal state = " + this._internalState));
846
+ callback(new Error(`client#connect failed, as invalid internal state = ${this._internalState}`));
843
847
  return;
844
848
  }
845
849
 
@@ -941,6 +945,16 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
941
945
  return this._secureChannel ? this._secureChannel.getClientNonce() : null;
942
946
  }
943
947
 
948
+ public requestReconnection() {
949
+ if (this._secureChannel) {
950
+ this._secureChannel.abortConnection(() => {
951
+ errorLog("new connection requested");
952
+ /*
953
+ */
954
+ });
955
+ }
956
+ }
957
+
944
958
  public performMessageTransaction(request: Request, callback: ResponseCallback<Response>): void {
945
959
  if (!this._secureChannel) {
946
960
  // this may happen if the Server has closed the connection abruptly for some unknown reason
@@ -960,7 +974,7 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
960
974
  "performMessageTransaction: Invalid client state " +
961
975
  this._internalState +
962
976
  " while performing a transaction " +
963
- request.constructor.name
977
+ request.schema.name
964
978
  )
965
979
  );
966
980
  }
@@ -1035,10 +1049,10 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
1035
1049
  });
1036
1050
 
1037
1051
  this.performMessageTransaction(request, (err: Error | null, response?: Response) => {
1038
- this._serverEndpoints = [];
1039
1052
  if (err) {
1040
1053
  return callback(err);
1041
1054
  }
1055
+ this._serverEndpoints = [];
1042
1056
  // istanbul ignore next
1043
1057
  if (!response || !(response instanceof GetEndpointsResponse)) {
1044
1058
  return callback(new Error("Internal Error"));
@@ -1065,13 +1079,6 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
1065
1079
  public findServers(options: FindServersRequestLike, callback: ResponseCallback<ApplicationDescription[]>): void;
1066
1080
  public findServers(callback: ResponseCallback<ApplicationDescription[]>): void;
1067
1081
  public findServers(...args: any[]): any {
1068
- if (!this._secureChannel) {
1069
- setImmediate(() => {
1070
- callback(new Error("Invalid Secure Channel"));
1071
- });
1072
- return;
1073
- }
1074
-
1075
1082
  if (args.length === 1) {
1076
1083
  return this.findServers({}, args[0]);
1077
1084
  }
@@ -1107,14 +1114,6 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
1107
1114
  }
1108
1115
  const options = args[0] as FindServersOnNetworkRequestOptions;
1109
1116
  const callback = args[1] as ResponseCallback<ServerOnNetwork[]>;
1110
-
1111
- if (!this._secureChannel) {
1112
- setImmediate(() => {
1113
- callback(new Error("Invalid Secure Channel"));
1114
- });
1115
- return;
1116
- }
1117
-
1118
1117
  const request = new FindServersOnNetworkRequest(options);
1119
1118
 
1120
1119
  this.performMessageTransaction(request, (err: Error | null, response?: Response) => {
@@ -1325,6 +1324,10 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
1325
1324
  str += " applicationName............... " + this.applicationName + "\n";
1326
1325
  str += " applicationUri................ " + this._getBuiltApplicationUri() + "\n";
1327
1326
  str += " clientName.................... " + this.clientName + "\n";
1327
+ str += " reconnectOnFailure............ " + this.reconnectOnFailure + "\n";
1328
+ str += " isReconnecting................ " + this.isReconnecting + "\n";
1329
+ str += " (internal state).............. " + this._internalState + "\n";
1330
+ str += " sessions count................ " + this.getSessions().length + "\n";
1328
1331
 
1329
1332
  if (this._secureChannel) {
1330
1333
  str += "secureChannel:\n" + this._secureChannel.toString();
@@ -1432,19 +1435,11 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
1432
1435
  this._transactionsPerformed += this._secureChannel.transactionsPerformed;
1433
1436
  this._timedOutRequestCount += this._secureChannel.timedOutRequestCount;
1434
1437
  if (doDebug) {
1435
- debugLog(
1436
- chalk.cyan(` Client ${this._instanceNumber} ${this.clientName} byteWritten = `),
1437
- this._byteWritten
1438
- );
1439
- debugLog(chalk.cyan(` Client ${this._instanceNumber} ${this.clientName} byteRead = `), this._byteRead);
1440
- debugLog(
1441
- chalk.cyan(` Client ${this._instanceNumber} ${this.clientName} transactions = `),
1442
- this._transactionsPerformed
1443
- );
1444
- debugLog(
1445
- chalk.cyan(` Client ${this._instanceNumber} ${this.clientName} timedOutRequestCount = `),
1446
- this._timedOutRequestCount
1447
- );
1438
+ const h = `Client ${this._instanceNumber} ${this.clientName}`;
1439
+ debugLog(chalk.cyan(`${h} byteWritten = `), this._byteWritten);
1440
+ debugLog(chalk.cyan(`${h} byteRead = `), this._byteRead);
1441
+ debugLog(chalk.cyan(`${h} transactions = `), this._transactionsPerformed);
1442
+ debugLog(chalk.cyan(`${h} timedOutRequestCount = `), this._timedOutRequestCount);
1448
1443
  }
1449
1444
  }
1450
1445
  }
@@ -1563,6 +1558,9 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
1563
1558
  });
1564
1559
 
1565
1560
  secureChannel.on("close", (err?: Error | null) => {
1561
+ if (err) {
1562
+ this._setInternalState("panic");
1563
+ }
1566
1564
  debugLog(chalk.yellow.bold(" ClientBaseImpl emitting close"), err?.message);
1567
1565
  this._destroy_secure_channel();
1568
1566
  if (!err || !this.reconnectOnFailure) {
@@ -1687,7 +1685,7 @@ class TmpClient extends ClientBaseImpl {
1687
1685
 
1688
1686
  // istanbul ignore next
1689
1687
  if (this._internalState !== "disconnected") {
1690
- callback!(new Error("TmpClient#connect: invalid internal state " + this._internalState));
1688
+ callback!(new Error("TmpClient#connect: invalid internal state = " + this._internalState));
1691
1689
  return;
1692
1690
  }
1693
1691
 
@@ -2,6 +2,7 @@
2
2
  * @module node-opcua-client-private
3
3
  */
4
4
  import { EventEmitter } from "events";
5
+ import { callbackify } from "util";
5
6
  import * as chalk from "chalk";
6
7
  import { assert } from "node-opcua-assert";
7
8
  import { AggregateFunction } from "node-opcua-constants";
@@ -293,7 +294,6 @@ export class ClientSessionImpl extends EventEmitter implements ClientSession {
293
294
  this.timeout = 0;
294
295
  }
295
296
 
296
-
297
297
  getTransportSettings(): IBasicTransportSettings {
298
298
  return this._client!.getTransportSettings();
299
299
  }
@@ -1556,19 +1556,27 @@ export class ClientSessionImpl extends EventEmitter implements ClientSession {
1556
1556
  return this._client !== null && this._client._secureChannel !== null && this._client._secureChannel.isOpened();
1557
1557
  }
1558
1558
 
1559
+ private requestReconnection() {
1560
+ if (this._client) {
1561
+ this._client.requestReconnection();
1562
+ assert(this._client.isReconnecting === true, "expecting client to be reconnecting now");
1563
+ }
1564
+ }
1565
+
1559
1566
  public performMessageTransaction(request: Request, callback: (err: Error | null, response?: Response) => void): void {
1560
1567
  if (!this._client) {
1561
1568
  // session may have been closed by user ... but is still in used !!
1562
1569
  return callback(new Error("Session has been closed and should not be used to perform a transaction anymore"));
1563
1570
  }
1564
1571
 
1565
- if (!this.isChannelValid()) {
1566
- // the secure channel is broken, may be the server has crashed or the network cable has been disconnected
1567
- // for a long time
1568
- // we may need to queue this transaction, as a secure token may be being reprocessed
1569
- debugLog(chalk.bgWhite.red("!!! Performing transaction on invalid channel !!! ", request.constructor.name));
1570
- return callback(new Error("Invalid Channel after performing transaction on " + request.constructor.name));
1571
- }
1572
+ // if (!this.isChannelValid()) {
1573
+ // // the secure channel is broken, may be the server has crashed or the network cable has been disconnected
1574
+ // // for a long time
1575
+ // // we may need to queue this transaction, as a secure token may be being reprocessed
1576
+ // errorLog(chalk.bgWhite.red("!!! Performing transaction on invalid channel !!! ", request.schema.name));
1577
+ // // this.requestReconnection();
1578
+ // return callback(new Error("!!! Performing transaction on invalid channel with " + request.schema.name + ": starting reconnection process"));
1579
+ // }
1572
1580
 
1573
1581
  this._reconnecting.pendingTransactions = this._reconnecting.pendingTransactions || [];
1574
1582
  this._reconnecting.pendingTransactionsCount = this._reconnecting.pendingTransactionsCount || 0;
@@ -2014,7 +2022,7 @@ export class ClientSessionImpl extends EventEmitter implements ClientSession {
2014
2022
  });
2015
2023
  this._keepAliveManager.start();
2016
2024
  }
2017
-
2025
+
2018
2026
  public stopKeepAliveManager(): void {
2019
2027
  if (this._keepAliveManager) {
2020
2028
  this._keepAliveManager.stop();
@@ -2300,19 +2308,18 @@ async function promoteOpaqueStructure2(session: IBasicSession, callMethodResult:
2300
2308
  function countOpaqueStructures(callMethodResults: CallMethodResult[]): number {
2301
2309
  const x = (a: Variant[] | null): PseudoDataValue[] => {
2302
2310
  if (a === null) return [] as PseudoDataValue[];
2303
- return a.map((value) => { return { value: value } });
2304
- }
2305
- const opaqueStructureCount = callMethodResults.reduce(
2306
- (prev, callMethodResult) => {
2307
- return prev + extractDataValueToPromote(x(callMethodResult.outputArguments)).length;
2308
- }, 0);
2311
+ return a.map((value) => {
2312
+ return { value: value };
2313
+ });
2314
+ };
2315
+ const opaqueStructureCount = callMethodResults.reduce((prev, callMethodResult) => {
2316
+ return prev + extractDataValueToPromote(x(callMethodResult.outputArguments)).length;
2317
+ }, 0);
2309
2318
  return opaqueStructureCount;
2310
2319
  }
2311
2320
  async function promoteOpaqueStructure3(session: IBasicSession, callMethodResults: CallMethodResult[]): Promise<void> {
2312
-
2313
2321
  const opaqueStructureCount = countOpaqueStructures(callMethodResults);
2314
- if (0 === opaqueStructureCount)
2315
- return;
2322
+ if (0 === opaqueStructureCount) return;
2316
2323
 
2317
2324
  // construct dataTypeManager if not already present
2318
2325
  await getExtraDataTypeManager(session);
@@ -2324,11 +2331,11 @@ async function promoteOpaqueStructure3(session: IBasicSession, callMethodResults
2324
2331
  // tslint:disable:no-var-requires
2325
2332
  // tslint:disable:max-line-length
2326
2333
  const thenify = require("thenify");
2327
- const callbackify = require("callbackify");
2328
2334
  const opts = { multiArgs: false };
2329
2335
 
2330
2336
  const promoteOpaqueStructureWithCallback = callbackify(promoteOpaqueStructure);
2331
2337
  const promoteOpaqueStructure3WithCallback = callbackify(promoteOpaqueStructure3) as promoteOpaqueStructure3WithCallbackFunc;
2338
+ // ClientSessionImpl.prototype.constructExtensionObject = callbackify(ClientSessionImpl.prototype.constructExtensionObject) as any;
2332
2339
 
2333
2340
  ClientSessionImpl.prototype.browse = thenify.withCallback(ClientSessionImpl.prototype.browse, opts);
2334
2341
  ClientSessionImpl.prototype.browseNext = thenify.withCallback(ClientSessionImpl.prototype.browseNext, opts);
@@ -2367,5 +2374,4 @@ ClientSessionImpl.prototype.registerNodes = thenify.withCallback(ClientSessionIm
2367
2374
  ClientSessionImpl.prototype.unregisterNodes = thenify.withCallback(ClientSessionImpl.prototype.unregisterNodes, opts);
2368
2375
  ClientSessionImpl.prototype.readNamespaceArray = thenify.withCallback(ClientSessionImpl.prototype.readNamespaceArray, opts);
2369
2376
  ClientSessionImpl.prototype.getBuiltInDataType = thenify.withCallback(ClientSessionImpl.prototype.getBuiltInDataType, opts);
2370
- ClientSessionImpl.prototype.constructExtensionObject = callbackify(ClientSessionImpl.prototype.constructExtensionObject);
2371
2377
  ClientSessionImpl.prototype.changeUser = thenify.withCallback(ClientSessionImpl.prototype.changeUser, opts);
@@ -26,4 +26,6 @@ export interface IClientBase {
26
26
  _sessions: ClientSessionImpl[];
27
27
 
28
28
  getTransportSettings(): IBasicTransportSettings;
29
+
30
+ requestReconnection(): void;
29
31
  }
@@ -755,10 +755,10 @@ export class OPCUAClientImpl extends ClientBaseImpl implements OPCUAClient {
755
755
  }
756
756
  // istanbul ignore next
757
757
  if (!this.__resolveEndPoint() || !this.endpoint) {
758
- return callback!(
758
+ return callback!(
759
759
  new Error(
760
- " End point must exist " + this._secureChannel!.endpointUrl +
761
- " securityMode = " + MessageSecurityMode[this.securityMode] +
760
+ " End point must exist " + this._secureChannel!.endpointUrl +
761
+ " securityMode = " + MessageSecurityMode[this.securityMode] +
762
762
  " securityPolicy = " + this.securityPolicy));
763
763
  }
764
764
 
@@ -805,10 +805,7 @@ export class OPCUAClientImpl extends ClientBaseImpl implements OPCUAClient {
805
805
  * @private
806
806
  */
807
807
  public _on_connection_reestablished(callback: (err?: Error) => void): void {
808
- assert(typeof callback === "function");
809
-
810
- // call base class implementation first
811
- ClientBaseImpl.prototype._on_connection_reestablished.call(this, (/*err?: Error*/) => {
808
+ super._on_connection_reestablished((/*err?: Error*/) => {
812
809
  repair_client_sessions(this, callback);
813
810
  });
814
811
  }
@@ -849,7 +846,7 @@ export class OPCUAClientImpl extends ClientBaseImpl implements OPCUAClient {
849
846
  this.clientNonce = crypto.randomBytes(32);
850
847
 
851
848
  // recycle session name if already exists
852
- const sessionName = session.name
849
+ const sessionName = session.name
853
850
 
854
851
  const request = new CreateSessionRequest({
855
852
  clientCertificate: this.getCertificate(),
@@ -1163,15 +1160,15 @@ export class OPCUAClientImpl extends ClientBaseImpl implements OPCUAClient {
1163
1160
  this._serverEndpoints.map(
1164
1161
  (endpoint) =>
1165
1162
  endpoint.endpointUrl + " "
1166
- + MessageSecurityMode[endpoint.securityMode] + " "
1167
- + endpoint.securityPolicyUri + " "
1168
- + endpoint.userIdentityTokens?.map((u)=> UserTokenType[u.tokenType]).join(",")
1163
+ + MessageSecurityMode[endpoint.securityMode] + " "
1164
+ + endpoint.securityPolicyUri + " "
1165
+ + endpoint.userIdentityTokens?.map((u) => UserTokenType[u.tokenType]).join(",")
1169
1166
  ).join('\n')
1170
1167
  );
1171
1168
  }
1172
1169
  return callback(new Error(
1173
- " End point must exist " + this._secureChannel!.endpointUrl +
1174
- " securityMode = " + MessageSecurityMode[this.securityMode] +
1170
+ " End point must exist " + this._secureChannel!.endpointUrl +
1171
+ " securityMode = " + MessageSecurityMode[this.securityMode] +
1175
1172
  " securityPolicy = " + this.securityPolicy));
1176
1173
  }
1177
1174
  this.serverUri = this.endpoint.server.applicationUri || "invalid application uri";
@@ -139,7 +139,7 @@ function create_session_and_repeat_if_failed(
139
139
  callback: CallbackT<ClientSessionImpl>
140
140
  ) {
141
141
  if (session.hasBeenClosed()) {
142
- doDebug && debugLog("Cannot complete subscription republish due to session terminatio");
142
+ doDebug && debugLog("Cannot complete subscription republish due to session termination");
143
143
  return callback(new Error("Cannot complete subscription republish due to session termination"));
144
144
  }
145
145
  doDebug && debugLog(chalk.bgWhite.red(" => creating a new session ...."));
@@ -147,7 +147,7 @@ function create_session_and_repeat_if_failed(
147
147
  // so we can reuse subscriptions data
148
148
  client.__createSession_step2(session, (err: Error | null, session1?: ClientSessionImpl) => {
149
149
  if (err && session.hasBeenClosed()) {
150
- doDebug && debugLog("Cannot complete subscription republish due to session terminatio");
150
+ doDebug && debugLog("Cannot complete subscription republish due to session termination");
151
151
  return callback(new Error("Cannot complete subscription republish due to session termination"));
152
152
  }
153
153
  if (!err && session1) {
@@ -433,9 +433,14 @@ export function repair_client_session(client: IClientBase, session: ClientSessio
433
433
  session.sessionId.toString(),
434
434
  " => Let's retry"
435
435
  );
436
- setTimeout(() => {
437
- _repair_client_session(client, session, callback);
438
- }, 2000);
436
+ if (!session.hasBeenClosed()) {
437
+ setTimeout(() => {
438
+ _repair_client_session(client, session, callback);
439
+ }, 2000);
440
+ } else {
441
+ // session does not need to be repaired anymore
442
+ callback();
443
+ }
439
444
  return;
440
445
  }
441
446
  doDebug && debugLog(chalk.yellow("session has been restored"), session.sessionId.toString());
@@ -452,9 +457,8 @@ export function repair_client_session(client: IClientBase, session: ClientSessio
452
457
  }
453
458
 
454
459
  export function repair_client_sessions(client: IClientBase, callback: (err?: Error) => void): void {
455
- const self = client;
456
460
  // repair session
457
- const sessions = self.getSessions();
461
+ const sessions = client.getSessions();
458
462
  doDebug && debugLog(chalk.red.bgWhite(" Starting sessions reactivation", sessions.length));
459
463
  async.map(
460
464
  sessions,