node-opcua-client 2.72.2 → 2.74.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.
@@ -23,6 +23,8 @@ import {
23
23
  coerceSecurityPolicy,
24
24
  ConnectionStrategy,
25
25
  ConnectionStrategyOptions,
26
+ Request as Request1,
27
+ Response as Response1,
26
28
  SecurityPolicy,
27
29
  SecurityToken
28
30
  } from "node-opcua-secure-channel";
@@ -59,7 +61,8 @@ import {
59
61
  FindServersRequestLike,
60
62
  GetEndpointsOptions,
61
63
  OPCUAClientBase,
62
- OPCUAClientBaseOptions
64
+ OPCUAClientBaseOptions,
65
+ TransportSettings
63
66
  } from "../client_base";
64
67
  import { performCertificateSanityCheck } from "../verify";
65
68
  import { ClientSessionImpl } from "./client_session_impl";
@@ -259,7 +262,14 @@ class ClockAdjustment {
259
262
  }
260
263
  }
261
264
 
262
- type InternalClientState = "uninitialized" | "disconnected" | "connecting" | "connected" | "reconnecting" | "disconnecting";
265
+ type InternalClientState =
266
+ | "uninitialized"
267
+ | "disconnected"
268
+ | "connecting"
269
+ | "connected"
270
+ | "reconnecting"
271
+ | "reconnecting_newchannel_connected"
272
+ | "disconnecting";
263
273
 
264
274
  /*
265
275
  * "disconnected" ---[connect]----------------------> "connecting"
@@ -272,7 +282,9 @@ type InternalClientState = "uninitialized" | "disconnected" | "connecting" | "co
272
282
  *
273
283
  * "connecting" ---[lost of connection]-----------> "reconnecting" ->[reconnection]
274
284
  *
275
- * "reconnecting" ---[reconnection successful]------> "connected"
285
+ * "reconnecting" ---[reconnection successful]------> "reconnecting_newchannel_connected"
286
+ *
287
+ * "reconnecting_newchannel_connected" --(session failure) -->"reconnecting"
276
288
  *
277
289
  * "reconnecting" ---[reconnection failure]---------> [reconnection] ---> "reconnecting"
278
290
  *
@@ -280,6 +292,7 @@ type InternalClientState = "uninitialized" | "disconnected" | "connecting" | "co
280
292
  */
281
293
 
282
294
  let g_ClientCounter = 0;
295
+
283
296
  /**
284
297
  * @internal
285
298
  */
@@ -287,8 +300,6 @@ let g_ClientCounter = 0;
287
300
  export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase, IClientBase {
288
301
  /**
289
302
  * total number of requests that been canceled due to timeout
290
- * @property timedOutRequestCount
291
- * @type {Number}
292
303
  */
293
304
  public get timedOutRequestCount(): number {
294
305
  return this._timedOutRequestCount + (this._secureChannel ? this._secureChannel.timedOutRequestCount : 0);
@@ -296,17 +307,13 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
296
307
 
297
308
  /**
298
309
  * total number of transactions performed by the client
299
- * @property transactionsPerformed
300
- * @type {Number}
301
- */
310
+ x */
302
311
  public get transactionsPerformed(): number {
303
312
  return this._transactionsPerformed + (this._secureChannel ? this._secureChannel.transactionsPerformed : 0);
304
313
  }
305
314
 
306
315
  /**
307
316
  * is true when the client has already requested the server end points.
308
- * @property knowsServerEndpoint
309
- * @type boolean
310
317
  */
311
318
  get knowsServerEndpoint(): boolean {
312
319
  return this._serverEndpoints && this._serverEndpoints.length > 0;
@@ -314,8 +321,6 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
314
321
 
315
322
  /**
316
323
  * true if the client is trying to reconnect to the server after a connection break.
317
- * @property isReconnecting
318
- * @type {Boolean}
319
324
  */
320
325
  get isReconnecting(): boolean {
321
326
  return !!(this._secureChannel && this._secureChannel.isConnecting) || this._internalState !== "connected";
@@ -323,8 +328,6 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
323
328
 
324
329
  /**
325
330
  * true if the connection strategy is set to automatically try to reconnect in case of failure
326
- * @property reconnectOnFailure
327
- * @type {Boolean}
328
331
  */
329
332
  get reconnectOnFailure(): boolean {
330
333
  return this.connectionStrategy.maxRetry > 0 || this.connectionStrategy.maxRetry === -1;
@@ -332,8 +335,6 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
332
335
 
333
336
  /**
334
337
  * total number of bytes read by the client
335
- * @property bytesRead
336
- * @type {Number}
337
338
  */
338
339
  get bytesRead(): number {
339
340
  return this._byteRead + (this._secureChannel ? this._secureChannel.bytesRead : 0);
@@ -341,8 +342,6 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
341
342
 
342
343
  /**
343
344
  * total number of bytes written by the client
344
- * @property bytesWritten
345
- * @type {Number}
346
345
  */
347
346
  public get bytesWritten(): number {
348
347
  return this._byteWritten + (this._secureChannel ? this._secureChannel.bytesWritten : 0);
@@ -379,10 +378,11 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
379
378
  private _timedOutRequestCount: number;
380
379
 
381
380
  private _transactionsPerformed: number;
382
- private reconnectionIsCanceled: boolean;
381
+ private _reconnectionIsCanceled: boolean;
383
382
  private _clockAdjuster?: ClockAdjustment;
384
383
  private _tmpClient?: OPCUAClientBase;
385
384
  private _instanceNumber: number;
385
+ private _transportSettings: TransportSettings;
386
386
 
387
387
  public clientCertificateManager: OPCUACertificateManager;
388
388
 
@@ -430,7 +430,7 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
430
430
 
431
431
  this._secureChannel = null;
432
432
 
433
- this.reconnectionIsCanceled = false;
433
+ this._reconnectionIsCanceled = false;
434
434
 
435
435
  this.endpointUrl = "";
436
436
 
@@ -469,14 +469,18 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
469
469
  this.discoveryUrl = options.discoveryUrl || "";
470
470
 
471
471
  this._setInternalState("disconnected");
472
+
473
+ this._transportSettings = options.transportSettings || {};
472
474
  }
473
475
 
474
476
  private _cancel_reconnection(callback: ErrorCallback) {
475
477
  // _cancel_reconnection is invoked during disconnection
476
478
  // when we detect that a reconnection is in progress..
477
- assert(this.isReconnecting);
478
-
479
- this.reconnectionIsCanceled = true;
479
+ if (!this.isReconnecting) {
480
+ warningLog("internal error : _cancel_reconnection should be used when reconnecting is in progress");
481
+ }
482
+ debugLog("canceling reconnection");
483
+ this._reconnectionIsCanceled = true;
480
484
  // istanbul ignore next
481
485
  if (!this._secureChannel) {
482
486
  debugLog("_cancel_reconnection: Nothing to do !");
@@ -493,58 +497,79 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
493
497
  }
494
498
 
495
499
  public _recreate_secure_channel(callback: ErrorCallback): void {
496
- debugLog("_recreate_secure_channel...", this._internalState);
500
+ debugLog("_recreate_secure_channel... while internalState is", this._internalState);
497
501
 
498
502
  if (!this.knowsServerEndpoint) {
499
503
  debugLog("Cannot reconnect , server endpoint is unknown");
500
504
  return callback(new Error("Cannot reconnect, server endpoint is unknown"));
501
505
  }
502
506
  assert(this.knowsServerEndpoint);
503
- // xx ??? assert(!this.isReconnecting);
504
- /**
505
- * notifies the observer that the OPCUA is now trying to reestablish the connection
506
- * after having received a connection break...
507
- * @event start_reconnection
508
- *
509
- */
507
+
510
508
  this._setInternalState("reconnecting");
511
509
  this.emit("start_reconnection"); // send after callback
512
510
 
513
- this._destroy_secure_channel();
514
- assert(!this._secureChannel);
515
-
516
511
  const infiniteConnectionRetry: ConnectionStrategyOptions = {
517
512
  initialDelay: this.connectionStrategy.initialDelay,
518
513
  maxDelay: this.connectionStrategy.maxDelay,
519
514
  maxRetry: -1
520
515
  };
521
516
 
522
- const failAndRetry = (err: Error, message: string) => {
517
+ const _when_internal_error = (err: Error, callback: ErrorCallback) => {
518
+ errorLog("INTERNAL ERROR", err.message);
519
+ callback(err);
520
+ };
521
+
522
+ const _when_reconnectionIsCanceled = (callback: ErrorCallback) => {
523
+ warningLog("attempt to recreate a new secure channel : suspended becasue reconnection is canceled !");
524
+ this.emit("reconnection_canceled");
525
+ return callback(new Error("Reconnection has been canceled - " + this.clientName));
526
+ };
527
+
528
+ const _failAndRetry = (err: Error, message: string, callback: ErrorCallback) => {
523
529
  debugLog("failAndRetry; ", message);
524
- if (this.reconnectionIsCanceled) {
525
- this.emit("reconnection_canceled");
526
- return callback(new Error("Reconnection has been canceled - " + this.clientName));
530
+ if (this._reconnectionIsCanceled) {
531
+ return _when_reconnectionIsCanceled(callback);
527
532
  }
533
+ this._destroy_secure_channel();
528
534
  warningLog("client = ", this.clientName, message, err.message);
529
535
  // else
530
536
  // let retry a little bit later
531
537
  this.emit("reconnection_attempt_has_failed", err, message); // send after callback
532
- setTimeout(_attempt_to_recreate_secure_channel, 100);
538
+ setImmediate(_attempt_to_recreate_secure_channel, callback);
533
539
  };
534
540
 
535
- const _attempt_to_recreate_secure_channel = () => {
536
- if (this.reconnectionIsCanceled) {
537
- this.emit("reconnection_canceled");
538
- return callback(new Error("Reconnection has been canceled - " + this.clientName));
541
+ const _when_connected = (callback: ErrorCallback) => {
542
+ this.emit("after_reconnection", null); // send after callback
543
+ assert(this._secureChannel, "expecting a secureChannel here ");
544
+ // a new channel has be created and a new connection is established
545
+ debugLog(chalk.bgWhite.red("ClientBaseImpl: RECONNECTED !!!"));
546
+ this._setInternalState("reconnecting_newchannel_connected");
547
+ return callback();
548
+ };
549
+
550
+ const _attempt_to_recreate_secure_channel = (callback: ErrorCallback) => {
551
+ debugLog("attempt to recreate a new secure channel");
552
+ if (this._reconnectionIsCanceled) {
553
+ return _when_reconnectionIsCanceled(callback);
539
554
  }
555
+ assert(!this._secureChannel, "_attempt_to_recreate_secure_channel, expecting this._secureChannel not to exist");
556
+
540
557
  this._internal_create_secure_channel(infiniteConnectionRetry, (err?: Error | null) => {
541
558
  if (err) {
559
+ // istanbul ignore next
560
+ if (this._secureChannel) {
561
+ const err = new Error("_internal_create_secure_channel failed, expecting this._secureChannel not to exist");
562
+ return _when_internal_error(err, callback);
563
+ }
564
+
542
565
  if (err.message.match(/ECONNREFUSED|ECONNABORTED/)) {
543
- return callback(err);
566
+ return _failAndRetry(err, "create secure channel failed with ECONNREFUSED|ECONNABORTED", callback);
544
567
  }
568
+
545
569
  if (err.message.match("Backoff aborted.")) {
546
- return failAndRetry(err!, "cannot create secure channel (backoff aborted)");
570
+ return _failAndRetry(err!, "cannot create secure channel (backoff aborted)", callback);
547
571
  }
572
+
548
573
  if (
549
574
  err!.message.match("BadCertificateInvalid") ||
550
575
  err!.message.match(/_socket has been disconnected by third party/)
@@ -560,51 +585,30 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
560
585
  // the server may have shut down the channel because its certificate
561
586
  // has changed ....
562
587
  // let request the server certificate again ....
563
- debugLog(
564
- chalk.bgWhite.red(
565
- "ClientBaseImpl: Server Certificate has changed." + " we need to retrieve server certificate again"
566
- )
567
- );
568
-
569
588
  return this.fetchServerCertificate(this.endpointUrl, (err1?: Error | null) => {
570
589
  if (err1) {
571
- errorLog("Failing to fetch new server certificate: ", err1.message);
572
- return failAndRetry(err1, "trying to fetch new server certificate");
590
+ return _failAndRetry(err1, "Failing to fetch new server certificate", callback);
573
591
  }
574
592
  warningLog("new server certificate ", makeSHA1Thumbprint(this.serverCertificate!).toString("hex"));
575
593
  this._internal_create_secure_channel(infiniteConnectionRetry, (err3?: Error | null) => {
576
594
  if (err3) {
577
- return failAndRetry(err3, "trying to create new channel with new certificate");
595
+ return _failAndRetry(err3, "trying to create new channel with new certificate", callback);
578
596
  }
579
- this._setInternalState("connected");
580
- this.emit("connected");
581
- callback();
597
+ return _when_connected(callback);
582
598
  });
583
599
  });
600
+ } else {
601
+ return _failAndRetry(err, "cannot create secure channel", callback);
584
602
  }
585
- debugLog(chalk.bgWhite.red("ClientBaseImpl: cannot reconnect .."));
586
- failAndRetry(err!, "cannot create secure channel");
587
603
  } else {
588
- /**
589
- * notify the observers that the reconnection process has been completed
590
- * @event after_reconnection
591
- * @param err
592
- */
593
- this.emit("after_reconnection", err); // send after callback
594
- assert(this._secureChannel, "expecting a secureChannel here ");
595
- // a new channel has be created and a new connection is established
596
- debugLog(chalk.bgWhite.red("ClientBaseImpl: RECONNECTED !!!"));
597
- this._setInternalState("connected");
598
- return callback();
604
+ return _when_connected(callback);
599
605
  }
600
606
  });
601
607
  };
602
608
 
603
609
  // create a secure channel
604
610
  // a new secure channel must be established
605
- setImmediate(() => {
606
- _attempt_to_recreate_secure_channel();
607
- });
611
+ _attempt_to_recreate_secure_channel(callback);
608
612
  }
609
613
 
610
614
  public _internal_create_secure_channel(
@@ -614,7 +618,7 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
614
618
  assert(this._secureChannel === null);
615
619
  assert(typeof this.endpointUrl === "string");
616
620
 
617
- debugLog("_internal_create_secure_channel creating new ClientSecureChannelLayer");
621
+ debugLog("_internal_create_secure_channel creating new ClientSecureChannelLayer _internalState =", this._internalState);
618
622
  const secureChannel = new ClientSecureChannelLayer({
619
623
  connectionStrategy,
620
624
  defaultSecureTokenLifetime: this.defaultSecureTokenLifetime,
@@ -622,10 +626,18 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
622
626
  securityMode: this.securityMode,
623
627
  securityPolicy: this.securityPolicy,
624
628
  serverCertificate: this.serverCertificate,
625
- tokenRenewalInterval: this.tokenRenewalInterval
626
-
629
+ tokenRenewalInterval: this.tokenRenewalInterval,
630
+ transportSettings: this._transportSettings
627
631
  // transportTimeout:
628
632
  });
633
+ secureChannel.on("backoff", (count: number, delay: number) => {
634
+ this.emit("backoff", count, delay);
635
+ });
636
+
637
+ secureChannel.on("abort", () => {
638
+ this.emit("abort");
639
+ });
640
+
629
641
  secureChannel.protocolVersion = this.protocolVersion;
630
642
 
631
643
  this._secureChannel = secureChannel;
@@ -633,30 +645,20 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
633
645
  async.series(
634
646
  [
635
647
  // ------------------------------------------------- STEP 2 : OpenSecureChannel
636
- (_innerCallback: ErrorCallback) => {
648
+ (innerCallback: ErrorCallback) => {
637
649
  debugLog("_internal_create_secure_channel before secureChannel.create");
650
+
638
651
  secureChannel.create(this.endpointUrl, (err?: Error) => {
639
652
  debugLog("_internal_create_secure_channel after secureChannel.create");
640
653
  if (!this._secureChannel) {
641
654
  debugLog("_secureChannel has been closed during the transaction !");
642
- return _innerCallback(new Error("Secure Channel Closed"));
655
+ assert(this.disconnecting);
656
+ return innerCallback(new Error("Secure Channel Closed"));
643
657
  }
644
- if (err) {
645
- debugLog(chalk.yellow("Cannot create secureChannel"), err.message ? chalk.cyan(err.message) : "");
646
- this._destroy_secure_channel();
647
- } else {
648
- assert(this._secureChannel !== null);
658
+ if (!err) {
649
659
  this._install_secure_channel_event_handlers(secureChannel);
650
660
  }
651
- _innerCallback(err);
652
- });
653
-
654
- secureChannel.on("backoff", (count: number, delay: number) => {
655
- this.emit("backoff", count, delay);
656
- });
657
-
658
- secureChannel.on("abort", () => {
659
- this.emit("abort");
661
+ innerCallback(err);
660
662
  });
661
663
  },
662
664
  // ------------------------------------------------- STEP 3 : GetEndpointsRequest
@@ -664,9 +666,10 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
664
666
  assert(this._secureChannel !== null);
665
667
  if (!this.knowsServerEndpoint) {
666
668
  this.getEndpoints((err: Error | null /*, endpoints?: EndpointDescription[]*/) => {
667
- if (this._secureChannel === null) {
669
+ if (!this._secureChannel) {
670
+ debugLog("_secureChannel has been closed during the transaction ! (while getEndpoints)");
668
671
  assert(this.disconnecting);
669
- innerCallback(new Error("disconnecting"));
672
+ return innerCallback(new Error("Secure Channel Closed"));
670
673
  }
671
674
  innerCallback(err ? err : undefined);
672
675
  });
@@ -679,8 +682,14 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
679
682
  (err) => {
680
683
  if (err) {
681
684
  debugLog("Inner create secure channel has failed", err.message);
682
- this._secureChannel = null;
683
- callback(err);
685
+ if (this._secureChannel) {
686
+ this._secureChannel!.abortConnection(() => {
687
+ this._destroy_secure_channel();
688
+ callback(err);
689
+ });
690
+ } else {
691
+ callback(err);
692
+ }
684
693
  } else {
685
694
  assert(this._secureChannel !== null);
686
695
  callback(null, secureChannel);
@@ -922,10 +931,24 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
922
931
  if (!this._secureChannel) {
923
932
  // this may happen if the Server has closed the connection abruptly for some unknown reason
924
933
  // or if the tcp connection has been broken.
925
- return callback(new Error("No SecureChannel , connection may have been canceled abruptly by server"));
934
+ return callback(
935
+ new Error("performMessageTransaction: No SecureChannel , connection may have been canceled abruptly by server")
936
+ );
926
937
  }
927
- if (this._internalState !== "connected" && this._internalState !== "connecting") {
928
- return callback(new Error("Invalid client state " + this._internalState));
938
+ if (
939
+ this._internalState !== "connected" &&
940
+ this._internalState !== "reconnecting_newchannel_connected" &&
941
+ this._internalState !== "connecting" &&
942
+ this._internalState !== "reconnecting"
943
+ ) {
944
+ return callback(
945
+ new Error(
946
+ "performMessageTransaction: Invalid client state " +
947
+ this._internalState +
948
+ " while performing a transaction " +
949
+ request.constructor.name
950
+ )
951
+ );
929
952
  }
930
953
  assert(this._secureChannel);
931
954
  assert(request);
@@ -1180,8 +1203,8 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
1180
1203
  }
1181
1204
  return callback();
1182
1205
  }
1183
-
1184
- this.reconnectionIsCanceled = true;
1206
+ debugLog("disconnecting client! (will set reconnectionIsCanceled to true");
1207
+ this._reconnectionIsCanceled = true;
1185
1208
  this.disconnecting = true;
1186
1209
 
1187
1210
  if (this._tmpClient) {
@@ -1194,7 +1217,7 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
1194
1217
  return;
1195
1218
  }
1196
1219
  debugLog("ClientBaseImpl#disconnect", this.endpointUrl);
1197
- if (this.isReconnecting && !this.reconnectionIsCanceled) {
1220
+ if (this.isReconnecting && !this._reconnectionIsCanceled) {
1198
1221
  debugLog("ClientBaseImpl#disconnect called while reconnection is in progress");
1199
1222
  // let's abort the reconnection process
1200
1223
  this._cancel_reconnection((err?: Error) => {
@@ -1416,12 +1439,11 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
1416
1439
  ")"
1417
1440
  );
1418
1441
  }
1419
-
1420
1442
  this._accumulate_statistics();
1421
-
1422
- this._secureChannel.dispose();
1423
- this._secureChannel.removeAllListeners();
1443
+ const secureChannel = this._secureChannel;
1424
1444
  this._secureChannel = null;
1445
+ secureChannel.dispose();
1446
+ secureChannel.removeAllListeners();
1425
1447
  }
1426
1448
  }
1427
1449
 
@@ -1486,7 +1508,7 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
1486
1508
  this.emit("receive_chunk", chunk);
1487
1509
  });
1488
1510
 
1489
- secureChannel.on("send_request", (message: Request) => {
1511
+ secureChannel.on("send_request", (message: Request1) => {
1490
1512
  /**
1491
1513
  * notify the observer that a request has been sent to the server.
1492
1514
  * @event send_request
@@ -1495,7 +1517,7 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
1495
1517
  this.emit("send_request", message);
1496
1518
  });
1497
1519
 
1498
- secureChannel.on("receive_response", (message: Response) => {
1520
+ secureChannel.on("receive_response", (message: Response1) => {
1499
1521
  /**
1500
1522
  * notify the observer that a response has been received from the server.
1501
1523
  * @event receive_response
@@ -1522,8 +1544,9 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
1522
1544
  this.emit("security_token_renewed", secureChannel);
1523
1545
  });
1524
1546
 
1525
- secureChannel.on("close", (err?: Error) => {
1547
+ secureChannel.on("close", (err?: Error | null) => {
1526
1548
  debugLog(chalk.yellow.bold(" ClientBaseImpl emitting close"), err?.message);
1549
+ this._destroy_secure_channel();
1527
1550
  if (!err || !this.reconnectOnFailure) {
1528
1551
  // this is a normal close operation initiated by us
1529
1552
  /**
@@ -1531,17 +1554,20 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
1531
1554
  * @param error
1532
1555
  */
1533
1556
  this.emit("close", err);
1534
- setImmediate(() => this._destroy_secure_channel());
1535
1557
  } else {
1536
1558
  /**
1537
1559
  * @event connection_lost
1538
1560
  */
1539
- this.emit("connection_lost", err?.message); // instead of "close"
1540
- this._repairConnection();
1561
+ // this.emit("close", err);
1562
+ if (this.reconnectOnFailure && this._internalState !== "reconnecting") {
1563
+ debugLog(" ClientBaseImpl emitting connection_lost");
1564
+ this.emit("connection_lost", err?.message); // instead of "close"
1565
+ this._repairConnection();
1566
+ }
1541
1567
  }
1542
1568
  });
1543
1569
 
1544
- secureChannel.on("timed_out_request", (request: Request) => {
1570
+ secureChannel.on("timed_out_request", (request: Request1) => {
1545
1571
  /**
1546
1572
  * send when a request has timed out without receiving a response
1547
1573
  * @event timed_out_request
@@ -1550,46 +1576,51 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
1550
1576
  this.emit("timed_out_request", request);
1551
1577
  });
1552
1578
  }
1553
- private _repairConnection() {
1554
- setImmediate(() => {
1555
- debugLog("recreating new secure channel ");
1556
- this._recreate_secure_channel((err1?: Error) => {
1557
- debugLog("secureChannel#on(close) => _recreate_secure_channel returns ", err1 ? err1.message : "OK");
1558
-
1559
- if (err1) {
1560
- // xx assert(!this._secureChannel);
1561
- debugLog("_recreate_secure_channel has failed");
1562
1579
 
1563
- this.emit("close", err1);
1564
- this._setInternalState("disconnected");
1580
+ public _inside_repairConnection = false;
1565
1581
 
1566
- return;
1567
- } else {
1568
- this._finalReconnectionStep((err2?: Error | null) => {
1569
- if (err2) {
1570
- // istanbul ignore next
1571
- if (doDebug) {
1572
- debugLog("connection_reestablished has failed");
1573
- debugLog("err= ", err2);
1574
- }
1575
- this.disconnect(() => {
1576
- // we still need to retry connecting here !!!
1577
- warningLog("Disconnected following reconnection failure", err2.message);
1578
- warningLog("I will retry OPCUA client reconnection in 5 seconds");
1579
- setTimeout(() => this._repairConnection(), 5000);
1580
- });
1581
- return;
1582
- } else {
1583
- /**
1584
- * @event connection_reestablished
1585
- * send when the connection is reestablished after a connection break
1586
- */
1587
- this.emit("connection_reestablished");
1588
- this._setInternalState("connected");
1582
+ private _repairConnection() {
1583
+ if (this._inside_repairConnection) {
1584
+ errorLog("_repairConnection already in progress ", this._internalState);
1585
+ return;
1586
+ }
1587
+ this._inside_repairConnection = true;
1588
+ debugLog("recreating new secure channel ", this._internalState);
1589
+ this._recreate_secure_channel((err1?: Error) => {
1590
+ debugLog("secureChannel#on(close) => _recreate_secure_channel returns ", err1 ? err1.message : "OK");
1591
+
1592
+ if (err1) {
1593
+ errorLog("_recreate_secure_channel has failed: err = ", err1.message);
1594
+ this.emit("close", err1);
1595
+ this._setInternalState("disconnected");
1596
+ this._inside_repairConnection = false;
1597
+ return;
1598
+ } else {
1599
+ this._finalReconnectionStep((err2?: Error | null) => {
1600
+ if (err2) {
1601
+ // istanbul ignore next
1602
+ if (doDebug) {
1603
+ debugLog("connection_reestablished has failed");
1604
+ debugLog("err= ", err2.message);
1589
1605
  }
1590
- });
1591
- }
1592
- });
1606
+ // we still need to retry connecting here !!!
1607
+ debugLog("Disconnected following reconnection failure", err2.message);
1608
+ debugLog(`I will retry OPCUA client reconnection in ${OPCUAClientBase.retryDelay / 1000} seconds`);
1609
+ this._inside_repairConnection = false;
1610
+ this._destroy_secure_channel();
1611
+ setTimeout(() => this._repairConnection(), OPCUAClientBase.retryDelay);
1612
+ return;
1613
+ } else {
1614
+ /**
1615
+ * @event connection_reestablished
1616
+ * send when the connection is reestablished after a connection break
1617
+ */
1618
+ this.emit("connection_reestablished");
1619
+ this._setInternalState("connected");
1620
+ this._inside_repairConnection = false;
1621
+ }
1622
+ });
1623
+ }
1593
1624
  });
1594
1625
  }
1595
1626
  private _finalReconnectionStep(callback: ErrorCallback) {