node-opcua-client 2.177.0 → 2.179.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.
Files changed (83) hide show
  1. package/dist/alarms_and_conditions/client_alarm_tools.js +7 -5
  2. package/dist/alarms_and_conditions/client_alarm_tools.js.map +1 -1
  3. package/dist/alarms_and_conditions/client_tools.js +38 -28
  4. package/dist/alarms_and_conditions/client_tools.js.map +1 -1
  5. package/dist/client_base.d.ts +5 -0
  6. package/dist/client_base.js.map +1 -1
  7. package/dist/client_monitored_item.d.ts +1 -1
  8. package/dist/client_monitored_item.js +4 -2
  9. package/dist/client_monitored_item.js.map +1 -1
  10. package/dist/client_monitored_item_group.d.ts +4 -3
  11. package/dist/client_monitored_item_group.js +8 -5
  12. package/dist/client_monitored_item_group.js.map +1 -1
  13. package/dist/client_monitored_item_toolbox.js +4 -2
  14. package/dist/client_monitored_item_toolbox.js.map +1 -1
  15. package/dist/client_session.d.ts +9 -2
  16. package/dist/client_session_keepalive_manager.d.ts +1 -1
  17. package/dist/client_session_keepalive_manager.js +50 -29
  18. package/dist/client_session_keepalive_manager.js.map +1 -1
  19. package/dist/client_subscription.d.ts +2 -2
  20. package/dist/client_subscription.js +3 -1
  21. package/dist/client_subscription.js.map +1 -1
  22. package/dist/common.d.ts +3 -3
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +1 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/opcua_client.js +1 -0
  27. package/dist/opcua_client.js.map +1 -1
  28. package/dist/private/client_base_impl.js +178 -77
  29. package/dist/private/client_base_impl.js.map +1 -1
  30. package/dist/private/client_monitored_item_group_impl.d.ts +1 -0
  31. package/dist/private/client_monitored_item_group_impl.js +6 -8
  32. package/dist/private/client_monitored_item_group_impl.js.map +1 -1
  33. package/dist/private/client_monitored_item_impl.js +0 -4
  34. package/dist/private/client_monitored_item_impl.js.map +1 -1
  35. package/dist/private/client_publish_engine.js +62 -28
  36. package/dist/private/client_publish_engine.js.map +1 -1
  37. package/dist/private/client_session_impl.d.ts +49 -17
  38. package/dist/private/client_session_impl.js +240 -143
  39. package/dist/private/client_session_impl.js.map +1 -1
  40. package/dist/private/client_subscription_impl.js +15 -12
  41. package/dist/private/client_subscription_impl.js.map +1 -1
  42. package/dist/private/i_private_client.d.ts +1 -1
  43. package/dist/private/opcua_client_impl.js +44 -9
  44. package/dist/private/opcua_client_impl.js.map +1 -1
  45. package/dist/private/reconnection/client_publish_engine_reconnection.js +17 -7
  46. package/dist/private/reconnection/client_publish_engine_reconnection.js.map +1 -1
  47. package/dist/private/reconnection/client_subscription_reconnection.js +11 -7
  48. package/dist/private/reconnection/client_subscription_reconnection.js.map +1 -1
  49. package/dist/private/reconnection/reconnection.js +31 -14
  50. package/dist/private/reconnection/reconnection.js.map +1 -1
  51. package/dist/reverse/client_reverse_connect.js.map +1 -1
  52. package/dist/tools/findservers.js.map +1 -1
  53. package/dist/tools/read_history_server_capabilities.d.ts +2 -1
  54. package/dist/tools/read_history_server_capabilities.js +6 -3
  55. package/dist/tools/read_history_server_capabilities.js.map +1 -1
  56. package/package.json +50 -50
  57. package/source/alarms_and_conditions/client_alarm_tools.ts +10 -8
  58. package/source/alarms_and_conditions/client_tools.ts +71 -30
  59. package/source/client_base.ts +5 -1
  60. package/source/client_monitored_item.ts +10 -9
  61. package/source/client_monitored_item_base.ts +2 -8
  62. package/source/client_monitored_item_group.ts +13 -10
  63. package/source/client_monitored_item_toolbox.ts +4 -2
  64. package/source/client_session.ts +10 -3
  65. package/source/client_session_keepalive_manager.ts +72 -55
  66. package/source/client_subscription.ts +6 -3
  67. package/source/common.ts +4 -0
  68. package/source/index.ts +1 -1
  69. package/source/opcua_client.ts +1 -0
  70. package/source/private/client_base_impl.ts +244 -113
  71. package/source/private/client_monitored_item_group_impl.ts +17 -18
  72. package/source/private/client_monitored_item_impl.ts +0 -4
  73. package/source/private/client_publish_engine.ts +78 -40
  74. package/source/private/client_session_impl.ts +393 -236
  75. package/source/private/client_subscription_impl.ts +20 -25
  76. package/source/private/i_private_client.ts +1 -1
  77. package/source/private/opcua_client_impl.ts +51 -9
  78. package/source/private/reconnection/client_publish_engine_reconnection.ts +22 -21
  79. package/source/private/reconnection/client_subscription_reconnection.ts +12 -8
  80. package/source/private/reconnection/reconnection.ts +35 -18
  81. package/source/reverse/client_reverse_connect.ts +11 -3
  82. package/source/tools/findservers.ts +0 -1
  83. package/source/tools/read_history_server_capabilities.ts +9 -7
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * @module node-opcua-client-private
3
3
  */
4
- // tslint:disable:unified-signatures
5
4
 
6
5
  import { EventEmitter } from "node:events";
7
6
  import chalk from "chalk";
@@ -51,6 +50,7 @@ import type { ClientSessionImpl } from "./client_session_impl";
51
50
  interface ModifySubscriptionOptionsEx extends ModifySubscriptionOptions {
52
51
  subscriptionId?: SubscriptionId;
53
52
  }
53
+
54
54
  import { detectLongOperation } from "./performance";
55
55
 
56
56
  const debugLog = make_debugLog("CLIENT_SUBSCRIPTION");
@@ -249,12 +249,10 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
249
249
  public terminate(): Promise<void>;
250
250
  public terminate(callback: ErrorCallback): void;
251
251
  public terminate(callback?: ErrorCallback): Promise<void> | void {
252
- debugLog("Terminating client subscription ", this.subscriptionId);
252
+ // c8 ignore next
253
+ doDebug && debugLog("Terminating client subscription ", this.subscriptionId);
253
254
 
254
- if (
255
- this.subscriptionId === TERMINATED_SUBSCRIPTION_ID ||
256
- this.subscriptionId === TERMINATING_SUBSCRIPTION_ID
257
- ) {
255
+ if (this.subscriptionId === TERMINATED_SUBSCRIPTION_ID || this.subscriptionId === TERMINATING_SUBSCRIPTION_ID) {
258
256
  // already terminated... just ignore
259
257
  callback?.();
260
258
  return;
@@ -279,7 +277,8 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
279
277
  },
280
278
  (err: Error | null, response?: DeleteSubscriptionsResponse) => {
281
279
  if (response && response?.results?.[0] !== StatusCodes.Good) {
282
- debugLog("warning: deleteSubscription returned ", response.results);
280
+ // c8 ignore next
281
+ doDebug && debugLog("warning: deleteSubscription returned ", response.results);
283
282
  }
284
283
  if (err) {
285
284
  /**
@@ -293,7 +292,8 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
293
292
  }
294
293
  );
295
294
  } else {
296
- debugLog("subscriptionId is not value ", this.subscriptionId);
295
+ // c8 ignore next
296
+ doDebug && debugLog("subscriptionId is not value ", this.subscriptionId);
297
297
  assert(this.subscriptionId === PENDING_SUBSCRIPTION_ID);
298
298
  this._terminate_step2(callback as ErrorCallback);
299
299
  }
@@ -332,7 +332,6 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
332
332
  monitoringMode?: MonitoringMode | Callback<ClientMonitoredItemBase>,
333
333
  callback?: Callback<ClientMonitoredItemBase>
334
334
  ): Promise<ClientMonitoredItemBase> | undefined {
335
-
336
335
  // When called with 3 args via the promise API, thenify
337
336
  // injects its callback as the 4th argument (the
338
337
  // monitoringMode slot). Detect this and shift.
@@ -378,7 +377,6 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
378
377
  timestampsToReturn: TimestampsToReturn,
379
378
  callback?: Callback<ClientMonitoredItemGroup>
380
379
  ): Promise<ClientMonitoredItemGroup> | undefined {
381
-
382
380
  const monitoredItemGroup = new ClientMonitoredItemGroupImpl(this, itemsToMonitor, requestedParameters, timestampsToReturn);
383
381
 
384
382
  this._wait_for_subscription_to_be_ready((err?: Error) => {
@@ -397,10 +395,7 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
397
395
  return undefined;
398
396
  }
399
397
 
400
- public _delete_monitored_items(
401
- monitoredItems: ClientMonitoredItemBase[],
402
- callback: ErrorCallback
403
- ): void {
398
+ public _delete_monitored_items(monitoredItems: ClientMonitoredItemBase[], callback: ErrorCallback): void {
404
399
  assert(typeof callback === "function");
405
400
  assert(Array.isArray(monitoredItems));
406
401
 
@@ -423,10 +418,7 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
423
418
 
424
419
  public async setPublishingMode(publishingEnabled: boolean): Promise<StatusCode>;
425
420
  public setPublishingMode(publishingEnabled: boolean, callback: Callback<StatusCode>): void;
426
- public setPublishingMode(
427
- publishingEnabled: boolean, callback?: Callback<StatusCode>
428
- ): Promise<StatusCode> | undefined {
429
-
421
+ public setPublishingMode(publishingEnabled: boolean, callback?: Callback<StatusCode>): Promise<StatusCode> | undefined {
430
422
  const session = this.session as ClientSessionImpl;
431
423
  if (!session) {
432
424
  callback?.(new Error("no session"));
@@ -703,14 +695,16 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
703
695
  private __on_publish_response_StatusChangeNotification(notification: StatusChangeNotification) {
704
696
  assert(notification.schema.name === "StatusChangeNotification");
705
697
 
706
- debugLog("Client has received a Status Change Notification ", notification.status.toString());
698
+ // c8 ignore next
699
+ doDebug && debugLog("Client has received a Status Change Notification ", notification.status.toString());
707
700
 
708
701
  if (notification.status === StatusCodes.GoodSubscriptionTransferred) {
709
702
  // OPCUA UA Spec 1.0.3 : part 3 - page 82 - 5.13.7 TransferSubscriptions:
710
703
  // If the Server transfers the Subscription to the new Session, the Server shall issue
711
704
  // a StatusChangeNotification notificationMessage with the status code
712
705
  // Good_SubscriptionTransferred to the old Session.
713
- debugLog("ClientSubscription#__on_publish_response_StatusChangeNotification : GoodSubscriptionTransferred");
706
+ // c8 ignore next
707
+ doDebug && debugLog("ClientSubscription#__on_publish_response_StatusChangeNotification : GoodSubscriptionTransferred");
714
708
 
715
709
  // may be it has been transferred after a reconnection.... in this case should do nothing about it
716
710
  }
@@ -765,7 +759,8 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
765
759
 
766
760
  if (notificationData.length === 0) {
767
761
  // this is a keep alive message
768
- debugLog(chalk.yellow("Client : received a keep alive notification from client"));
762
+ // c8 ignore next
763
+ doDebug && debugLog(chalk.yellow("Client : received a keep alive notification from client"));
769
764
  /**
770
765
  * notify the observers that a keep alive Publish Response has been received from the server.
771
766
  * @event keepalive
@@ -818,7 +813,8 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
818
813
  if (s(this).$_slowNotifCount > 0 && s(this).$_slowNotifCount % 1000 !== 0) return;
819
814
  s(this).$_slowNotifCount++;
820
815
  warningLog(
821
- `[NODE-OPCUA-W32]}: monitored.item event handler takes too much time : operation duration ${duration} ms [repeated ${s(this).$_slowNotifCount
816
+ `[NODE-OPCUA-W32]}: monitored.item event handler takes too much time : operation duration ${duration} ms [repeated ${
817
+ s(this).$_slowNotifCount
822
818
  } times]\n please ensure that your monitoredItem event handler is not blocking the event loop.`
823
819
  );
824
820
  }
@@ -935,8 +931,6 @@ export function ClientMonitoredItem_create(
935
931
  return monitoredItem;
936
932
  }
937
933
 
938
- // tslint:disable:no-var-requires
939
- // tslint:disable:max-line-length
940
934
  import { withCallback } from "thenify-ex";
941
935
 
942
936
  const _opts = { multiArgs: false };
@@ -960,7 +954,8 @@ export function __create_subscription(subscription: ClientSubscriptionImpl, call
960
954
  }
961
955
  const session = subscription.session;
962
956
 
963
- debugLog(chalk.yellow.bold("ClientSubscription created "));
957
+ // c8 ignore next
958
+ doDebug && debugLog(chalk.yellow.bold("ClientSubscription created "));
964
959
 
965
960
  const request = new CreateSubscriptionRequest({
966
961
  maxNotificationsPerPublish: subscription.maxNotificationsPerPublish,
@@ -15,7 +15,7 @@ export interface IClientBase {
15
15
  callback: (err: Error | null, session?: ClientSessionImpl) => void
16
16
  ): void;
17
17
  _removeSession(session: ClientSessionImpl): void;
18
- closeSession(session: ClientSession, deleteSubscription: any, arg2: (err?: Error | undefined) => void): void;
18
+ closeSession(session: ClientSession, deleteSubscription: boolean, arg2: (err?: Error | undefined) => void): void;
19
19
 
20
20
  performMessageTransaction(request: Request, callback: ResponseCallback<Response>): void;
21
21
  endpoint?: EndpointDescription;
@@ -7,10 +7,10 @@ import { callbackify } from "node:util";
7
7
  import chalk from "chalk";
8
8
 
9
9
  import { assert } from "node-opcua-assert";
10
- import { createFastUninitializedBuffer } from "node-opcua-buffer-utils";
11
10
  import { DataTypeExtractStrategy } from "node-opcua-client-dynamic-extension-object";
12
11
  import {
13
12
  type Certificate,
13
+ combine_der,
14
14
  exploreCertificate,
15
15
  extractPublicKeyFromCertificateSync,
16
16
  makePrivateKeyFromPem,
@@ -351,7 +351,7 @@ function createUserNameIdentityToken(
351
351
  });
352
352
 
353
353
  // now encrypt password as requested
354
- const lenBuf = createFastUninitializedBuffer(4);
354
+ const lenBuf = Buffer.allocUnsafe(4);
355
355
  lenBuf.writeUInt32LE(identityToken.password.length + serverNonce.length, 0);
356
356
  const block = Buffer.concat([lenBuf, identityToken.password, serverNonce]);
357
357
  identityToken.password = cryptoFactory.asymmetricEncrypt(block, publicKey);
@@ -607,7 +607,6 @@ export class OPCUAClientImpl extends ClientBaseImpl<OPCUAClientBaseEvents> {
607
607
  *
608
608
  * const create
609
609
  */
610
- // biome-ignore lint/suspicious/useAdjacentOverloadSignatures: static vs instance method
611
610
  public static async createSession(
612
611
  endpointUrl: string,
613
612
  userIdentity?: UserIdentityInfo,
@@ -745,7 +744,8 @@ export class OPCUAClientImpl extends ClientBaseImpl<OPCUAClientBaseEvents> {
745
744
 
746
745
  const old_client = internalSession._client;
747
746
 
748
- debugLog("OPCUAClientImpl#reactivateSession");
747
+ // c8 ignore next
748
+ doDebug && debugLog("OPCUAClientImpl#reactivateSession");
749
749
 
750
750
  this._activateSession(
751
751
  internalSession,
@@ -839,7 +839,8 @@ export class OPCUAClientImpl extends ClientBaseImpl<OPCUAClientBaseEvents> {
839
839
  this.performMessageTransaction(request, (err: Error | null, response?: Response) => {
840
840
  /* c8 ignore next */
841
841
  if (err) {
842
- debugLog("__createSession_step3 has failed", err.message);
842
+ // c8 ignore next
843
+ doDebug && debugLog("__createSession_step3 has failed", err.message);
843
844
  return callback(err);
844
845
  // // we could have an invalid state here or a connection error
845
846
  // errorLog("error: ", err.message, " retrying in ... 5 secondes");
@@ -882,7 +883,8 @@ export class OPCUAClientImpl extends ClientBaseImpl<OPCUAClientBaseEvents> {
882
883
  session.serverCertificate = response.serverCertificate;
883
884
  session.serverSignature = response.serverSignature;
884
885
 
885
- debugLog("revised session timeout = ", session.timeout, response.revisedSessionTimeout);
886
+ // c8 ignore next
887
+ doDebug && debugLog("revised session timeout = ", session.timeout, response.revisedSessionTimeout);
886
888
 
887
889
  response.serverEndpoints = response.serverEndpoints || [];
888
890
 
@@ -928,6 +930,21 @@ export class OPCUAClientImpl extends ClientBaseImpl<OPCUAClientBaseEvents> {
928
930
  return;
929
931
  }
930
932
 
933
+ // `session.serverCertificate` is captured once, at CreateSession. If the
934
+ // server has rotated its certificate since (push certificate management,
935
+ // OPC UA Part 12), the client has already re-learned the current one in
936
+ // fetchServerCertificate() while reconnecting — but the session still
937
+ // holds the old one. Both the clientSignature below and the encryption
938
+ // of a password-bearing user identity token are computed against it,
939
+ // so re-activating the existing session on the new channel would fail
940
+ // (BadApplicationSignatureInvalid on the server) and force a needless
941
+ // CreateSession. Refresh it from the client's current knowledge first.
942
+ // Only overwrite when the client actually knows a certificate: over a
943
+ // SecurityMode.None channel this.serverCertificate may be unset while
944
+ // the session's copy (from CreateSessionResponse) is still needed to
945
+ // encrypt the user token.
946
+ this.#refreshSessionServerCertificate(session);
947
+
931
948
  const serverCertificate = session.serverCertificate;
932
949
  // If the securityPolicyUri is None and none of the UserTokenPolicies requires encryption,
933
950
  // the Client shall ignore the ApplicationInstanceCertificate (serverCertificate)
@@ -1011,6 +1028,11 @@ export class OPCUAClientImpl extends ClientBaseImpl<OPCUAClientBaseEvents> {
1011
1028
  session.lastRequestSentTime = new Date();
1012
1029
 
1013
1030
  this.performMessageTransaction(request, (err1: Error | null, response?: Response) => {
1031
+ // this path bypasses ClientSessionImpl._performMessageTransaction, so it has to
1032
+ // record the contact itself - and under the same rule, not a stricter one: a
1033
+ // server that answers and rejects the activation is still a server we reached.
1034
+ session.noteServerAnswer(err1, response);
1035
+
1014
1036
  if (!err1 && response && response.responseHeader.serviceResult.isGoodish()) {
1015
1037
  /* c8 ignore next */
1016
1038
  if (!(response instanceof ActivateSessionResponse)) {
@@ -1025,7 +1047,6 @@ export class OPCUAClientImpl extends ClientBaseImpl<OPCUAClientBaseEvents> {
1025
1047
  // OPC 10000-18 §5.2.8) so a Client can react without server access.
1026
1048
  session.lastActivateSessionStatusCode = response.responseHeader.serviceResult;
1027
1049
  session.serverNonce = response.serverNonce;
1028
- session.lastResponseReceivedTime = new Date();
1029
1050
  if (this.keepSessionAlive) {
1030
1051
  session.startKeepAliveManager(this.keepAliveInterval);
1031
1052
  }
@@ -1045,6 +1066,29 @@ export class OPCUAClientImpl extends ClientBaseImpl<OPCUAClientBaseEvents> {
1045
1066
  });
1046
1067
  });
1047
1068
  }
1069
+ /**
1070
+ * Bring `session.serverCertificate` in line with the server certificate the
1071
+ * client currently knows (see the comment in {@link _activateSession}).
1072
+ * No-op when the client has no certificate of its own to offer.
1073
+ * @private
1074
+ */
1075
+ #refreshSessionServerCertificate(session: ClientSessionImpl): void {
1076
+ const current = this.serverCertificate;
1077
+ if (!current || current.length === 0) {
1078
+ return;
1079
+ }
1080
+ const currentDer = Array.isArray(current) ? combine_der(current) : current;
1081
+ if (session.serverCertificate?.equals(currentDer)) {
1082
+ return;
1083
+ }
1084
+ doDebug &&
1085
+ debugLog(
1086
+ "refreshing session.serverCertificate before ActivateSession (server certificate changed since CreateSession)",
1087
+ session.sessionId.toString()
1088
+ );
1089
+ session.serverCertificate = currentDer;
1090
+ }
1091
+
1048
1092
  /**
1049
1093
  *
1050
1094
  * @private
@@ -1256,8 +1300,6 @@ export class OPCUAClientImpl extends ClientBaseImpl<OPCUAClientBaseEvents> {
1256
1300
  }
1257
1301
  }
1258
1302
 
1259
- // tslint:disable:no-var-requires
1260
- // tslint:disable:max-line-length
1261
1303
  import { withCallback } from "thenify-ex";
1262
1304
 
1263
1305
  /**
@@ -39,10 +39,7 @@ function extractStatusCode(err: Error | null | undefined, response?: RepublishRe
39
39
  return StatusCodes.Bad;
40
40
  }
41
41
 
42
- function _sendRepublish(
43
- session: ClientSessionImpl,
44
- subscription: ClientSubscriptionImpl
45
- ): Promise<{ isDone: boolean }> {
42
+ function _sendRepublish(session: ClientSessionImpl, subscription: ClientSubscriptionImpl): Promise<{ isDone: boolean }> {
46
43
  return new Promise<{ isDone: boolean }>((resolve, reject) => {
47
44
  assert(Number.isFinite(subscription.lastSequenceNumber) && subscription.lastSequenceNumber + 1 >= 0);
48
45
 
@@ -63,7 +60,8 @@ function _sendRepublish(
63
60
  }
64
61
 
65
62
  if (!session || session._closeEventHasBeenEmitted) {
66
- debugLog("ClientPublishEngine#_republish aborted ");
63
+ // c8 ignore next
64
+ doDebug && debugLog("ClientPublishEngine#_republish aborted ");
67
65
  return resolve({ isDone: true });
68
66
  }
69
67
 
@@ -89,16 +87,14 @@ function _sendRepublish(
89
87
  if (!err) {
90
88
  err = new Error(response.responseHeader.serviceResult.toString());
91
89
  }
92
- debugLog(" _send_republish ends with ", err.message);
90
+ // c8 ignore next
91
+ doDebug && debugLog(" _send_republish ends with ", err.message);
93
92
  reject(err);
94
93
  });
95
94
  });
96
95
  }
97
96
 
98
- async function _republish(
99
- engine: ClientSidePublishEngine,
100
- subscription: ClientSubscriptionImpl
101
- ): Promise<void> {
97
+ async function _republish(engine: ClientSidePublishEngine, subscription: ClientSubscriptionImpl): Promise<void> {
102
98
  const session = engine.session as ClientSessionImpl;
103
99
 
104
100
  // loop until done (all messages re-published or error)
@@ -110,14 +106,14 @@ async function _republish(
110
106
  isDone = result.isDone;
111
107
  }
112
108
 
113
- debugLog("nbPendingPublishRequest = ", engine.nbPendingPublishRequests);
114
- debugLog(" _republish ends with ", "null");
109
+ // c8 ignore next
110
+ if (doDebug) {
111
+ debugLog("nbPendingPublishRequest = ", engine.nbPendingPublishRequests);
112
+ debugLog(" _republish ends with ", "null");
113
+ }
115
114
  }
116
115
 
117
- async function __askSubscriptionRepublish(
118
- engine: ClientSidePublishEngine,
119
- subscription: ClientSubscriptionImpl
120
- ): Promise<void> {
116
+ async function __askSubscriptionRepublish(engine: ClientSidePublishEngine, subscription: ClientSubscriptionImpl): Promise<void> {
121
117
  try {
122
118
  await _republish(engine, subscription);
123
119
  } catch (err) {
@@ -131,7 +127,8 @@ async function __askSubscriptionRepublish(
131
127
 
132
128
  const error = err as Error;
133
129
 
134
- debugLog("__askSubscriptionRepublish--------------------- err =", error.message);
130
+ // c8 ignore next
131
+ doDebug && debugLog("__askSubscriptionRepublish--------------------- err =", error.message);
135
132
 
136
133
  if (error.message.match(/BadSessionInvalid/)) {
137
134
  // _republish failed because session is not valid anymore on server side.
@@ -147,9 +144,11 @@ async function __askSubscriptionRepublish(
147
144
  // In this case, Client must recreate a subscription and recreate monitored item without altering
148
145
  // the event handlers
149
146
  //
150
- debugLog(
151
- chalk.bgWhite.red("__askSubscriptionRepublish failed " + " subscriptionId is not valid anymore on server side.")
152
- );
147
+ // c8 ignore next
148
+ doDebug &&
149
+ debugLog(
150
+ chalk.bgWhite.red("__askSubscriptionRepublish failed " + " subscriptionId is not valid anymore on server side.")
151
+ );
153
152
  await recreateSubscriptionAndMonitoredItem(subscription);
154
153
  return;
155
154
  }
@@ -185,5 +184,7 @@ export function republish(engine: ClientSidePublishEngine, callback: () => void)
185
184
  }
186
185
  };
187
186
 
188
- processAll().then(() => callback()).catch(() => callback());
187
+ processAll()
188
+ .then(() => callback())
189
+ .catch(() => callback());
189
190
  }
@@ -13,7 +13,7 @@ import {
13
13
  type CreateMonitoredItemsResponse,
14
14
  type MonitoredItemCreateRequestOptions
15
15
  } from "node-opcua-types";
16
- import type { ClientSubscription } from "../../client_subscription";
16
+ import type { ClientMonitoredItemBaseMap, ClientSubscription } from "../../client_subscription";
17
17
  import type { ClientMonitoredItemImpl } from "../client_monitored_item_impl";
18
18
  import { __create_subscription, type ClientSubscriptionImpl, TERMINATED_SUBSCRIPTION_ID } from "../client_subscription_impl";
19
19
  import { _shouldNotContinue } from "./reconnection";
@@ -35,7 +35,7 @@ async function createMonitoredItemsAndRespectOperationalLimits(
35
35
  return createMonitoredItemResponse;
36
36
  }
37
37
 
38
- async function adjustMonitoredItemNodeIds(subscription: ClientSubscription, oldMonitoredItems: any) {
38
+ async function adjustMonitoredItemNodeIds(_subscription: ClientSubscription, _oldMonitoredItems: ClientMonitoredItemBaseMap) {
39
39
  // to Do
40
40
  }
41
41
  /**
@@ -43,11 +43,13 @@ async function adjustMonitoredItemNodeIds(subscription: ClientSubscription, oldM
43
43
 
44
44
  */
45
45
  export async function recreateSubscriptionAndMonitoredItem(_subscription: ClientSubscription): Promise<void> {
46
- debugLog("recreateSubscriptionAndMonitoredItem", _subscription.subscriptionId.toString());
46
+ // c8 ignore next
47
+ doDebug && debugLog("recreateSubscriptionAndMonitoredItem", _subscription.subscriptionId.toString());
47
48
 
48
49
  const subscription = _subscription as ClientSubscriptionImpl;
49
50
  if (subscription.subscriptionId === TERMINATED_SUBSCRIPTION_ID) {
50
- debugLog("Subscription is not in a valid state");
51
+ // c8 ignore next
52
+ doDebug && debugLog("Subscription is not in a valid state");
51
53
  return;
52
54
  }
53
55
 
@@ -63,9 +65,10 @@ export async function recreateSubscriptionAndMonitoredItem(_subscription: Client
63
65
  throw _err;
64
66
  }
65
67
 
66
- const test = subscription.publishEngine.getSubscription(subscription.subscriptionId);
68
+ const _test = subscription.publishEngine.getSubscription(subscription.subscriptionId);
67
69
 
68
- debugLog("recreating ", Object.keys(oldMonitoredItems).length, " monitored Items");
70
+ // c8 ignore next
71
+ doDebug && debugLog("recreating ", Object.keys(oldMonitoredItems).length, " monitored Items");
69
72
  // re-create monitored items
70
73
  const itemsToCreate: MonitoredItemCreateRequestOptions[] = [];
71
74
 
@@ -92,7 +95,8 @@ export async function recreateSubscriptionAndMonitoredItem(_subscription: Client
92
95
  throw new Error("no session");
93
96
  }
94
97
 
95
- debugLog("Recreating ", itemsToCreate.length, " monitored items");
98
+ // c8 ignore next
99
+ doDebug && debugLog("Recreating ", itemsToCreate.length, " monitored items");
96
100
 
97
101
  const response = await createMonitoredItemsAndRespectOperationalLimits(session, createMonitorItemsRequest);
98
102
  const monitoredItemResults = response.results || [];
@@ -101,7 +105,7 @@ export async function recreateSubscriptionAndMonitoredItem(_subscription: Client
101
105
  monitoredItemResults.forEach((monitoredItemResult, index) => {
102
106
  const itemToCreate = itemsToCreate[index];
103
107
  /* c8 ignore next */
104
- if (!itemToCreate || !itemToCreate.requestedParameters) {
108
+ if (!itemToCreate?.requestedParameters) {
105
109
  _errCount++;
106
110
  return;
107
111
  }
@@ -2,7 +2,6 @@
2
2
  * @module node-opcua-client-private
3
3
  */
4
4
 
5
- // tslint:disable:only-arrow-functions
6
5
  import chalk from "chalk";
7
6
  import { assert } from "node-opcua-assert";
8
7
  import { invalidateExtraDataTypeManager } from "node-opcua-client-dynamic-extension-object";
@@ -158,7 +157,10 @@ function _ask_for_subscription_republish(session: ClientSessionImpl, callback: (
158
157
  if (err) {
159
158
  warningLog("republish has failed with error :", err.message);
160
159
  doDebug && debugLog("_ask_for_subscription_republish has : recreating subscription");
161
- return repair_client_session_by_recreating_a_new_session(session._client!, session, callback);
160
+ if (!session._client) {
161
+ return callback(new Error("_ask_for_subscription_republish: session has no client"));
162
+ }
163
+ return repair_client_session_by_recreating_a_new_session(session._client, session, callback);
162
164
  }
163
165
 
164
166
  // Republish has completed: resume normal Publish handling. During the connection break the
@@ -206,7 +208,11 @@ function activate_session(client: IClientBase, session: ClientSessionImpl, newSe
206
208
 
207
209
  doDebug && debugLog(chalk.bgWhite.red(" => activating a new session ...."));
208
210
 
209
- client._activateSession(newSession, newSession.userIdentityInfo!, (err: Error | null, _session1?: ClientSessionImpl) => {
211
+ if (!newSession.userIdentityInfo) {
212
+ reject(new Error("activate_session: newSession has no userIdentityInfo"));
213
+ return;
214
+ }
215
+ client._activateSession(newSession, newSession.userIdentityInfo, (err: Error | null, _session1?: ClientSessionImpl) => {
210
216
  // c8 ignore next
211
217
  doDebug && debugLog(" => activating a new session .... Done err=", err ? err.message : "null");
212
218
  if (err) {
@@ -467,11 +473,16 @@ function _repair_client_session(client: IClientBase, session: ClientSessionImpl,
467
473
  {
468
474
  const err = _shouldNotContinue(session);
469
475
  if (err) {
470
- return callback(err);
476
+ callback(err);
477
+ return;
471
478
  }
472
479
  }
473
480
 
474
- client._activateSession(session, session.userIdentityInfo!, (err: Error | null, _session2?: ClientSessionImpl) => {
481
+ if (!session.userIdentityInfo) {
482
+ callback(new Error("_repair_client_session: session has no userIdentityInfo"));
483
+ return;
484
+ }
485
+ client._activateSession(session, session.userIdentityInfo, (err: Error | null, _session2?: ClientSessionImpl) => {
475
486
  // prettier-ignore
476
487
  {
477
488
  const err = _shouldNotContinue(session);
@@ -499,17 +510,24 @@ type EmptyCallback = (err?: Error) => void;
499
510
  export function repair_client_session(client: IClientBase, session: ClientSessionImpl, callback: EmptyCallback): void {
500
511
  if (!client) {
501
512
  doDebug && debugLog("Aborting reactivation of old session because user requested session to be close");
502
- return callback();
513
+ callback();
514
+ return;
503
515
  }
504
516
  doDebug && debugLog(chalk.yellow("Starting client session repair"));
505
517
 
506
518
  const privateSession = session as unknown as Reconnectable;
507
- privateSession._reconnecting = privateSession._reconnecting || { reconnecting: false, pendingCallbacks: [] };
519
+ privateSession._reconnecting = privateSession._reconnecting || {
520
+ reconnecting: false,
521
+ pendingCallbacks: [],
522
+ pendingTransactions: []
523
+ };
508
524
 
509
525
  if (session.hasBeenClosed()) {
510
526
  privateSession._reconnecting.reconnecting = false;
511
527
  doDebug && debugLog("Aborting reactivation of old session because session has been closed");
512
- return callback();
528
+ session.flushPendingTransactions(new Error("Session has been closed while the transaction was waiting for reconnection"));
529
+ callback();
530
+ return;
513
531
  }
514
532
  if (privateSession._reconnecting.reconnecting) {
515
533
  doDebug && debugLog(chalk.bgCyan("Reconnection is already happening for session"), session.sessionId.toString());
@@ -519,9 +537,6 @@ export function repair_client_session(client: IClientBase, session: ClientSessio
519
537
 
520
538
  privateSession._reconnecting.reconnecting = true;
521
539
 
522
- // get old transaction queue ...
523
- const transactionQueue = privateSession._reconnecting.pendingTransactions.splice(0);
524
-
525
540
  const repeatedAction = (callback: EmptyCallback) => {
526
541
  // prettier-ignore
527
542
  {
@@ -579,15 +594,17 @@ export function repair_client_session(client: IClientBase, session: ClientSessio
579
594
  repeatedAction((err) => {
580
595
  privateSession._reconnecting.reconnecting = false;
581
596
  const otherCallbacks = privateSession._reconnecting.pendingCallbacks.splice(0);
582
- // re-inject element in queue
583
597
 
584
- // c8 ignore next
585
- if (transactionQueue.length > 0) {
586
- doDebug && debugLog(chalk.yellow("re-injecting transaction queue"), transactionQueue.length);
587
- transactionQueue.forEach((e) => privateSession._reconnecting.pendingTransactions.push(e));
588
- }
589
- otherCallbacks.forEach((c: EmptyCallback) => c(err));
598
+ otherCallbacks.forEach((c: EmptyCallback) => {
599
+ c(err);
600
+ });
590
601
  callback(err);
602
+
603
+ // Resolve what the application asked for while the channel was down - after the repair
604
+ // callbacks have run, so the session is fully usable by the time anything is replayed.
605
+ // On failure they are failed rather than held across the client's retry delay: a caller
606
+ // waiting without bound is worse than an error it can act on.
607
+ session.flushPendingTransactions(err ?? null);
591
608
  });
592
609
  }
593
610
 
@@ -23,6 +23,7 @@
23
23
  import { createServer, type Server, type Socket } from "node:net";
24
24
 
25
25
  import { checkDebugFlag, make_debugLog, make_warningLog } from "node-opcua-debug";
26
+ import type { StatusCode } from "node-opcua-status-code";
26
27
  import {
27
28
  decodeReverseHello,
28
29
  type IAcceptedReverseConnection,
@@ -33,7 +34,6 @@ import {
33
34
  StatusCodes2,
34
35
  TCPErrorMessage
35
36
  } from "node-opcua-transport";
36
- import type { StatusCode } from "node-opcua-status-code";
37
37
 
38
38
  const doDebug = checkDebugFlag("ReverseConnect");
39
39
  const debugLog = make_debugLog("ReverseConnect");
@@ -193,7 +193,10 @@ export class ClientReverseConnect {
193
193
  * Register interest in the next reverse connection matching `expectation`.
194
194
  * The callback fires once with an accepted, RHE-validated connection (or an error).
195
195
  */
196
- public waitForConnection(expectation: ReverseConnectExpectation | undefined, callback: AcceptedCallback): ICancelableRegistration {
196
+ public waitForConnection(
197
+ expectation: ReverseConnectExpectation | undefined,
198
+ callback: AcceptedCallback
199
+ ): ICancelableRegistration {
197
200
  // a validated connection may already be waiting
198
201
  for (let i = 0; i < this.#held.length; i++) {
199
202
  const h = this.#held[i];
@@ -341,7 +344,12 @@ export class ClientReverseConnect {
341
344
  doDebug && debugLog(`reverse connection from ${serverUri} held (no matching client yet)`);
342
345
  timer = setTimeout(() => {
343
346
  timer = undefined;
344
- this.#rejectSocket(socket, forget, StatusCodes2.BadTcpServerTooBusy, "no client is waiting for this reverse connection");
347
+ this.#rejectSocket(
348
+ socket,
349
+ forget,
350
+ StatusCodes2.BadTcpServerTooBusy,
351
+ "no client is waiting for this reverse connection"
352
+ );
345
353
  }, this.#matchTimeout);
346
354
  timer.unref?.();
347
355
  this.#held.push({ accepted, handOff });
@@ -5,7 +5,6 @@
5
5
  import type { ServerOnNetwork } from "node-opcua-service-discovery";
6
6
  import type { ApplicationDescription, EndpointDescription } from "node-opcua-service-endpoints";
7
7
 
8
-
9
8
  import { ClientBaseImpl } from "../private/client_base_impl";
10
9
 
11
10
  export interface FindServerResults {