opcjs-client 0.1.39-alpha → 0.1.41-alpha

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.
package/dist/index.cjs CHANGED
@@ -13,6 +13,18 @@ var SessionInvalidError = class extends Error {
13
13
  };
14
14
 
15
15
  // src/services/serviceBase.ts
16
+ var requestHandleCounter = 0;
17
+ function nextRequestHandle() {
18
+ if (requestHandleCounter >= 2147483647) {
19
+ requestHandleCounter = 1;
20
+ } else {
21
+ requestHandleCounter++;
22
+ }
23
+ return requestHandleCounter;
24
+ }
25
+ function lastAssignedHandle() {
26
+ return requestHandleCounter;
27
+ }
16
28
  var ServiceBase = class {
17
29
  constructor(authToken, secureChannel) {
18
30
  this.authToken = authToken;
@@ -35,18 +47,43 @@ var ServiceBase = class {
35
47
  }
36
48
  throw new Error(`${context} failed: ${opcjsBase.StatusCodeToString(result)} (${opcjsBase.StatusCodeToStringNumber(result)})`);
37
49
  }
38
- createRequestHeader() {
50
+ /**
51
+ * Builds a RequestHeader for an outgoing service request.
52
+ *
53
+ * @param returnDiagnostics - Bitmask of diagnostic fields to request from the
54
+ * server (OPC UA Part 4, §7.15). Use {@link ReturnDiagnosticsMask} constants
55
+ * to compose the value. Default `0` = no diagnostics.
56
+ */
57
+ createRequestHeader(returnDiagnostics = 0, preAllocatedHandle) {
39
58
  const requestHeader = new opcjsBase.RequestHeader();
40
59
  requestHeader.authenticationToken = this.authToken;
41
60
  requestHeader.timestamp = /* @__PURE__ */ new Date();
42
- requestHeader.requestHandle = 0;
43
- requestHeader.returnDiagnostics = 0;
61
+ requestHeader.requestHandle = preAllocatedHandle ?? nextRequestHandle();
62
+ requestHeader.returnDiagnostics = returnDiagnostics;
44
63
  requestHeader.auditEntryId = "";
45
64
  requestHeader.timeoutHint = 6e4;
46
65
  requestHeader.additionalHeader = opcjsBase.ExtensionObject.newEmpty();
47
66
  return requestHeader;
48
67
  }
49
68
  };
69
+ var CertificateRequiredError = class extends Error {
70
+ statusCode;
71
+ constructor(statusCode) {
72
+ super(
73
+ `Server requires a client certificate: ${opcjsBase.StatusCodeToString(statusCode)} (0x${statusCode.toString(16).toUpperCase().padStart(8, "0")})`
74
+ );
75
+ this.name = "CertificateRequiredError";
76
+ this.statusCode = statusCode;
77
+ }
78
+ };
79
+ var CERTIFICATE_REQUIRED_STATUS_CODES = /* @__PURE__ */ new Set([
80
+ 2148663296,
81
+ // BadCertificateInvalid
82
+ 2148728832,
83
+ // BadSecurityChecksFailed
84
+ 2153316352
85
+ // BadNoValidCertificates
86
+ ]);
50
87
 
51
88
  // src/services/sessionService.ts
52
89
  var SessionService = class _SessionService extends ServiceBase {
@@ -57,9 +94,16 @@ var SessionService = class _SessionService extends ServiceBase {
57
94
  logger = opcjsBase.getLogger("services.SessionService");
58
95
  /**
59
96
  * Creates a new session on the server (OPC UA Part 4, Section 5.7.2).
97
+ *
98
+ * @param clientCertificate - Optional DER-encoded client certificate to include in
99
+ * the request. Pass `null` (default) for SecurityPolicy None without a cert.
100
+ * When the server rejects a `null`-cert request with a certificate-related status
101
+ * code, the caller should retry with a `Uint8Array` certificate (OPC UA 1.0
102
+ * fallback — see `CertificateRequiredError`).
60
103
  * @returns The session ID, authentication token, and selected server endpoint.
104
+ * @throws {CertificateRequiredError} when the server demands a client certificate.
61
105
  */
62
- async createSession() {
106
+ async createSession(clientCertificate = null) {
63
107
  this.logger.debug("Creating session...");
64
108
  const clientDescription = new opcjsBase.ApplicationDescription();
65
109
  clientDescription.applicationUri = this.configuration.applicationUri;
@@ -76,7 +120,7 @@ var SessionService = class _SessionService extends ServiceBase {
76
120
  request.endpointUrl = this.secureChannel.getEndpointUrl();
77
121
  request.sessionName = "";
78
122
  request.clientNonce = null;
79
- request.clientCertificate = null;
123
+ request.clientCertificate = clientCertificate ?? null;
80
124
  request.requestedSessionTimeout = 6e4;
81
125
  request.maxResponseMessageSize = 0;
82
126
  this.logger.debug("Sending CreateSessionRequest...");
@@ -90,6 +134,9 @@ var SessionService = class _SessionService extends ServiceBase {
90
134
  }
91
135
  const serviceResult = castedResponse.responseHeader?.serviceResult;
92
136
  if (serviceResult !== void 0 && serviceResult !== opcjsBase.StatusCode.Good) {
137
+ if (CERTIFICATE_REQUIRED_STATUS_CODES.has(serviceResult)) {
138
+ throw new CertificateRequiredError(serviceResult);
139
+ }
93
140
  throw new Error(`CreateSessionRequest failed: ${opcjsBase.StatusCodeToString(serviceResult)}`);
94
141
  }
95
142
  const clientConnectionUrl = new URL("opc." + this.secureChannel.getEndpointUrl());
@@ -139,6 +186,33 @@ var SessionService = class _SessionService extends ServiceBase {
139
186
  }
140
187
  this.logger.debug("Session activated.");
141
188
  }
189
+ /**
190
+ * Sends a CancelRequest to the server asking it to abandon the pending
191
+ * service request identified by `requestHandle` (OPC UA Part 4, Section 5.7.5).
192
+ *
193
+ * The server makes a best-effort attempt to cancel matching requests.
194
+ * Cancelled requests complete with a status of `BadRequestCancelledByClient`.
195
+ *
196
+ * @param requestHandle - The `requestHeader.requestHandle` value of the pending
197
+ * request to cancel. Pass `0` to attempt to cancel all outstanding requests
198
+ * (server behaviour for handle 0 is implementation-specific).
199
+ * @returns The number of pending requests that were actually cancelled
200
+ * (`CancelResponse.cancelCount`).
201
+ */
202
+ async cancel(requestHandle) {
203
+ this.logger.debug(`Sending CancelRequest for requestHandle ${requestHandle}...`);
204
+ const request = new opcjsBase.CancelRequest();
205
+ request.requestHeader = this.createRequestHeader();
206
+ request.requestHandle = requestHandle;
207
+ const response = await this.secureChannel.issueServiceRequest(request);
208
+ const result = response?.responseHeader?.serviceResult;
209
+ if (result !== void 0 && result !== opcjsBase.StatusCode.Good) {
210
+ throw new Error(`CancelRequest failed: ${opcjsBase.StatusCodeToString(result)}`);
211
+ }
212
+ const cancelCount = response?.cancelCount ?? 0;
213
+ this.logger.debug(`CancelRequest completed; cancelCount = ${cancelCount}.`);
214
+ return cancelCount;
215
+ }
142
216
  /**
143
217
  * Closes the current session on the server (OPC UA Part 4, Section 5.7.4).
144
218
  * @param deleteSubscriptions - When true the server deletes all Subscriptions
@@ -224,6 +298,22 @@ var Session = class {
224
298
  getEndpoint() {
225
299
  return this.endpoint;
226
300
  }
301
+ /**
302
+ * Switches the active user identity for this session by calling ActivateSession with
303
+ * a new identity token (OPC UA Part 4, Section 5.7.3 — Session Client Impersonate
304
+ * conformance unit).
305
+ *
306
+ * The server re-evaluates authorisation for the session under the new identity.
307
+ * All existing Subscriptions and MonitoredItems are preserved; only the security
308
+ * context changes.
309
+ *
310
+ * @param identity - The new user identity to apply to the session.
311
+ * @throws When the server returns a non-Good ServiceResult (e.g. `BadIdentityTokenRejected`
312
+ * or `BadUserAccessDenied`).
313
+ */
314
+ async impersonate(identity) {
315
+ await this.activateSession(identity);
316
+ }
227
317
  /**
228
318
  * Closes the session on the server (OPC UA Part 4, Section 5.7.4).
229
319
  * @param deleteSubscriptions - When true the server deletes all Subscriptions
@@ -243,10 +333,30 @@ var SessionHandler = class {
243
333
  sessionServices;
244
334
  logger = opcjsBase.getLogger("sessions.SessionHandler");
245
335
  async createNewSession(identity) {
246
- const ret = await this.sessionServices.createSession();
247
- this.sessionServices = this.sessionServices.recreate(ret.authToken);
248
- const session = new Session(ret.sessionId, ret.authToken, ret.endpoint, this.sessionServices);
249
- this.validateUserTokenPolicy(identity, ret.endpoint);
336
+ let sessionResult;
337
+ try {
338
+ sessionResult = await this.sessionServices.createSession(null);
339
+ } catch (err) {
340
+ if (err instanceof CertificateRequiredError) {
341
+ const fallbackCert = this.configuration.securityConfiguration?.applicationInstanceCertificate;
342
+ if (fallbackCert) {
343
+ this.logger.info(
344
+ "Server requires a client certificate (OPC UA 1.0 fallback); retrying CreateSession with applicationInstanceCertificate."
345
+ );
346
+ sessionResult = await this.sessionServices.createSession(fallbackCert);
347
+ } else {
348
+ this.logger.warn(
349
+ "Server requires a client certificate but no applicationInstanceCertificate is configured in securityConfiguration. Cannot complete the 1.0 fallback."
350
+ );
351
+ throw err;
352
+ }
353
+ } else {
354
+ throw err;
355
+ }
356
+ }
357
+ this.sessionServices = this.sessionServices.recreate(sessionResult.authToken);
358
+ const session = new Session(sessionResult.sessionId, sessionResult.authToken, sessionResult.endpoint, this.sessionServices);
359
+ this.validateUserTokenPolicy(identity, sessionResult.endpoint);
250
360
  await session.activateSession(identity);
251
361
  return session;
252
362
  }
@@ -278,6 +388,17 @@ var SessionHandler = class {
278
388
  async closeSession(deleteSubscriptions = true) {
279
389
  await this.sessionServices.closeSession(deleteSubscriptions);
280
390
  }
391
+ /**
392
+ * Sends a CancelRequest to the server to abandon a pending service call
393
+ * (OPC UA Part 4, Section 5.7.5).
394
+ *
395
+ * @param requestHandle - The `requestHandle` from the `RequestHeader` of the
396
+ * pending request to cancel.
397
+ * @returns The number of requests the server actually cancelled.
398
+ */
399
+ async cancel(requestHandle) {
400
+ return this.sessionServices.cancel(requestHandle);
401
+ }
281
402
  /**
282
403
  * Validates the requested user-identity token type against:
283
404
  * 1. The `allowedUserTokenTypes` from the client security configuration — the
@@ -320,9 +441,10 @@ var AttributeService = class extends ServiceBase {
320
441
  * @param nodeIds - NodeIds of the Nodes to read.
321
442
  * @param maxAge - Maximum age of the cached value in milliseconds the server may return. 0 = always current value.
322
443
  * @param timestampsToReturn - Which timestamps to include in results. Default: Source.
323
- * @returns Array of results containing value and raw status code number, one per requested NodeId.
444
+ * @param returnDiagnostics - Bitmask of diagnostic fields to request (OPC UA Part 4, §7.15). Default: 0.
445
+ * @returns Array of results containing value, raw status code, and optional diagnostic info, one per requested NodeId.
324
446
  */
325
- async ReadValue(nodeIds, maxAge = 0, timestampsToReturn = opcjsBase.TimestampsToReturnEnum.Source) {
447
+ async ReadValue(nodeIds, maxAge = 0, timestampsToReturn = opcjsBase.TimestampsToReturnEnum.Source, returnDiagnostics = 0, requestHandle) {
326
448
  const readValueIds = nodeIds.map((ni) => {
327
449
  const readValueId = new opcjsBase.ReadValueId();
328
450
  readValueId.nodeId = ni;
@@ -332,7 +454,7 @@ var AttributeService = class extends ServiceBase {
332
454
  return readValueId;
333
455
  });
334
456
  const request = new opcjsBase.ReadRequest();
335
- request.requestHeader = this.createRequestHeader();
457
+ request.requestHeader = this.createRequestHeader(returnDiagnostics, requestHandle);
336
458
  request.maxAge = maxAge;
337
459
  request.timestampsToReturn = timestampsToReturn;
338
460
  request.nodesToRead = readValueIds;
@@ -340,10 +462,13 @@ var AttributeService = class extends ServiceBase {
340
462
  const response = await this.secureChannel.issueServiceRequest(request);
341
463
  this.checkServiceResult(response.responseHeader?.serviceResult, "ReadRequest");
342
464
  const results = new Array();
343
- for (const dataValue of response.results ?? []) {
465
+ const diagInfos = response.diagnosticInfos ?? [];
466
+ for (let i = 0; i < (response.results ?? []).length; i++) {
467
+ const dataValue = response.results[i];
344
468
  results.push({
345
469
  statusCode: dataValue.statusCode ?? opcjsBase.StatusCode.Good,
346
- value: dataValue.value
470
+ value: dataValue.value,
471
+ diagnosticInfo: diagInfos[i]
347
472
  });
348
473
  }
349
474
  return results;
@@ -355,9 +480,10 @@ var AttributeService = class extends ServiceBase {
355
480
 
356
481
  // src/readValueResult.ts
357
482
  var ReadValueResult = class {
358
- constructor(value, statusCode) {
483
+ constructor(value, statusCode, diagnosticInfo) {
359
484
  this.value = value;
360
485
  this.statusCode = statusCode;
486
+ this.diagnosticInfo = diagnosticInfo;
361
487
  }
362
488
  };
363
489
 
@@ -383,6 +509,17 @@ var SubscriptionHandler = class {
383
509
  entries = new Array();
384
510
  nextHandle = 0;
385
511
  isRunning = false;
512
+ /** Guards against multiple concurrent publish loops. */
513
+ publishInFlight = false;
514
+ /**
515
+ * Optional callback invoked when the server announces a shutdown via a
516
+ * `StatusChangeNotification` with status `BadShutdown` or `BadServerHalted`
517
+ * (OPC UA Part 4, §5.13.6.2 — Session Client Detect Shutdown).
518
+ *
519
+ * Assign this before calling `subscribe()`. The client sets it automatically
520
+ * in `initServices()` to trigger a reconnect.
521
+ */
522
+ onShutdown;
386
523
  /** Returns true when at least one subscription is active and the publish loop is running. */
387
524
  hasActiveSubscription() {
388
525
  return this.isRunning && this.entries.length > 0;
@@ -405,14 +542,18 @@ var SubscriptionHandler = class {
405
542
  // https://reference.opcfoundation.org/Core/Part4/v105/docs/5.14.5
406
543
  async publishLoop(pendingAcknowledgements) {
407
544
  if (!this.isRunning) return;
545
+ if (this.publishInFlight) return;
546
+ this.publishInFlight = true;
408
547
  let response;
409
548
  try {
410
549
  response = await this.subscriptionService.publish(pendingAcknowledgements);
411
550
  } catch (err) {
412
551
  this.logger.error(`Publish failed, stopping publish loop: ${err}`);
413
552
  this.isRunning = false;
553
+ this.publishInFlight = false;
414
554
  return;
415
555
  }
556
+ this.publishInFlight = false;
416
557
  const { subscriptionId, availableSequenceNumbers, moreNotifications, notificationMessage } = response;
417
558
  const notificationDatas = notificationMessage?.notificationData ?? [];
418
559
  const seqNumber = notificationMessage?.sequenceNumber;
@@ -443,6 +584,11 @@ var SubscriptionHandler = class {
443
584
  `Subscription ${subscriptionId} status changed: 0x${statusChange.status?.toString(16).toUpperCase()}`
444
585
  );
445
586
  this.isRunning = false;
587
+ const status = statusChange.status;
588
+ if (status === opcjsBase.StatusCode.BadShutdown || status === opcjsBase.StatusCode.BadServerHalted) {
589
+ this.logger.warn("Server shutdown announced via StatusChangeNotification \u2014 triggering reconnect.");
590
+ this.onShutdown?.();
591
+ }
446
592
  return;
447
593
  } else {
448
594
  this.logger.warn(
@@ -560,18 +706,22 @@ var MethodService = class extends ServiceBase {
560
706
  /**
561
707
  * Calls one or more methods on the server (OPC UA Part 4, Section 5.11.2).
562
708
  * @param methodsToCall - Array of CallMethodRequest describing each method to invoke.
563
- * @returns Array of results containing output argument values and raw status code number, one per requested method.
709
+ * @param returnDiagnostics - Bitmask of diagnostic fields to request (OPC UA Part 4, §7.15). Default: 0.
710
+ * @returns Array of results containing output argument values, raw status code, and optional diagnostic info,
711
+ * one per requested method.
564
712
  */
565
- async call(methodsToCall) {
713
+ async call(methodsToCall, returnDiagnostics = 0, requestHandle) {
566
714
  const request = new opcjsBase.CallRequest();
567
- request.requestHeader = this.createRequestHeader();
715
+ request.requestHeader = this.createRequestHeader(returnDiagnostics, requestHandle);
568
716
  request.methodsToCall = methodsToCall;
569
717
  this.logger.debug("Sending CallRequest...");
570
718
  const response = await this.secureChannel.issueServiceRequest(request);
571
719
  this.checkServiceResult(response.responseHeader?.serviceResult, "CallRequest");
572
- return response.results.map((result) => ({
720
+ const diagInfos = response.diagnosticInfos ?? [];
721
+ return response.results.map((result, i) => ({
573
722
  statusCode: result.statusCode ?? opcjsBase.StatusCode.Good,
574
- value: result.outputArguments.map((arg) => arg.value)
723
+ value: result.outputArguments.map((arg) => arg.value),
724
+ diagnosticInfo: diagInfos[i]
575
725
  }));
576
726
  }
577
727
  constructor(authToken, secureChannel) {
@@ -581,9 +731,10 @@ var MethodService = class extends ServiceBase {
581
731
 
582
732
  // src/method/callMethodResult.ts
583
733
  var CallMethodResult = class {
584
- constructor(values, statusCode) {
734
+ constructor(values, statusCode, diagnosticInfo) {
585
735
  this.values = values;
586
736
  this.statusCode = statusCode;
737
+ this.diagnosticInfo = diagnosticInfo;
587
738
  }
588
739
  };
589
740
  var BrowseService = class extends ServiceBase {
@@ -591,15 +742,16 @@ var BrowseService = class extends ServiceBase {
591
742
  /**
592
743
  * Browses one or more Nodes and returns their References (OPC UA Part 4, Section 5.9.2).
593
744
  * @param nodesToBrowse - Array of BrowseDescriptions specifying nodes and filter criteria.
745
+ * @param returnDiagnostics - Bitmask of diagnostic fields to request (OPC UA Part 4, §7.15). Default: 0.
594
746
  * @returns Array of BrowseResult, one per requested node.
595
747
  */
596
- async browse(nodesToBrowse) {
748
+ async browse(nodesToBrowse, returnDiagnostics = 0, requestHandle) {
597
749
  const view = new opcjsBase.ViewDescription();
598
750
  view.viewId = opcjsBase.NodeId.newNumeric(0, 0);
599
751
  view.timestamp = /* @__PURE__ */ new Date(-116444736e5);
600
752
  view.viewVersion = 0;
601
753
  const request = new opcjsBase.BrowseRequest();
602
- request.requestHeader = this.createRequestHeader();
754
+ request.requestHeader = this.createRequestHeader(returnDiagnostics, requestHandle);
603
755
  request.view = view;
604
756
  request.requestedMaxReferencesPerNode = 0;
605
757
  request.nodesToBrowse = nodesToBrowse;
@@ -612,11 +764,12 @@ var BrowseService = class extends ServiceBase {
612
764
  * Continues a Browse operation using continuation points (OPC UA Part 4, Section 5.9.3).
613
765
  * @param continuationPoints - Continuation points returned by a prior Browse or BrowseNext call.
614
766
  * @param releaseContinuationPoints - If true, releases the continuation points without returning results.
767
+ * @param returnDiagnostics - Bitmask of diagnostic fields to request (OPC UA Part 4, §7.15). Default: 0.
615
768
  * @returns Array of BrowseResult, one per continuation point.
616
769
  */
617
- async browseNext(continuationPoints, releaseContinuationPoints) {
770
+ async browseNext(continuationPoints, releaseContinuationPoints, returnDiagnostics = 0) {
618
771
  const request = new opcjsBase.BrowseNextRequest();
619
- request.requestHeader = this.createRequestHeader();
772
+ request.requestHeader = this.createRequestHeader(returnDiagnostics);
620
773
  request.releaseContinuationPoints = releaseContinuationPoints;
621
774
  request.continuationPoints = continuationPoints;
622
775
  this.logger.debug("Sending BrowseNextRequest...");
@@ -641,10 +794,67 @@ var BrowseNodeResult = class {
641
794
  this.typeDefinition = typeDefinition;
642
795
  }
643
796
  };
797
+ var NamespaceTable = class {
798
+ uris;
799
+ constructor(uris = []) {
800
+ this.uris = Object.freeze([...uris]);
801
+ }
802
+ /** Returns the namespace URI at the given index, or `undefined` if out of range. */
803
+ getUri(index) {
804
+ return this.uris[index];
805
+ }
806
+ /** Returns the namespace index for the given URI, or `undefined` if not found. */
807
+ getIndex(uri) {
808
+ const idx = this.uris.indexOf(uri);
809
+ return idx >= 0 ? idx : void 0;
810
+ }
811
+ /** Returns the full ordered array of namespace URIs. */
812
+ getUris() {
813
+ return this.uris;
814
+ }
815
+ /** Returns `true` when both tables contain the same URIs in the same order. */
816
+ equals(other) {
817
+ if (this.uris.length !== other.uris.length) return false;
818
+ return this.uris.every((uri, i) => uri === other.uris[i]);
819
+ }
820
+ /**
821
+ * Remaps the namespace index of `nodeId` from this table to the equivalent
822
+ * index in `newTable` by matching namespace URIs.
823
+ *
824
+ * - Namespace index `0` (OPC UA base namespace) is always returned unchanged.
825
+ * - Returns the **same** `NodeId` instance when the index has not changed.
826
+ * - Returns a **new** `NodeId` instance with the updated index when remapping
827
+ * is required.
828
+ *
829
+ * @throws {Error} When the namespace URI at `nodeId.namespace` is not present
830
+ * in this (old) table or when the URI is not present in `newTable`.
831
+ */
832
+ remapNodeId(nodeId, newTable) {
833
+ if (nodeId.namespace === 0) return nodeId;
834
+ const uri = this.getUri(nodeId.namespace);
835
+ if (uri === void 0) {
836
+ throw new Error(
837
+ `Cannot remap ${nodeId.toString()}: namespace index ${nodeId.namespace} is not present in the old NamespaceTable (length ${this.uris.length}).`
838
+ );
839
+ }
840
+ const newIndex = newTable.getIndex(uri);
841
+ if (newIndex === void 0) {
842
+ throw new Error(
843
+ `Cannot remap ${nodeId.toString()}: namespace URI '${uri}' is not present in the new NamespaceTable.`
844
+ );
845
+ }
846
+ if (newIndex === nodeId.namespace) return nodeId;
847
+ return new opcjsBase.NodeId(newIndex, nodeId.identifier);
848
+ }
849
+ };
644
850
 
645
851
  // src/client.ts
646
852
  var SERVER_STATUS_NODE_ID = opcjsBase.NodeId.newNumeric(0, 2256);
853
+ var NAMESPACE_ARRAY_NODE_ID = opcjsBase.NodeId.newNumeric(0, 2255);
854
+ var ESTIMATED_RETURN_TIME_NODE_ID = opcjsBase.NodeId.newNumeric(0, 2992);
855
+ var HAS_PROPERTY_REF_TYPE_ID = opcjsBase.NodeId.newNumeric(0, 46);
647
856
  var KEEP_ALIVE_INTERVAL_MS = 25e3;
857
+ var OPC_UA_MIN_DATE_TIME_MS = -116444736e5;
648
858
  var Client = class {
649
859
  constructor(endpointUrl, configuration, identity) {
650
860
  this.configuration = configuration;
@@ -666,6 +876,40 @@ var Client = class {
666
876
  ws;
667
877
  sessionHandler;
668
878
  keepAliveTimer;
879
+ /** Set to true while a shutdown-triggered reconnect is pending to avoid duplicate attempts. */
880
+ shutdownReconnectPending = false;
881
+ /** Most recently read NamespaceArray from the server (Session Client Renew NodeIds). */
882
+ namespaceTable;
883
+ /**
884
+ * Called whenever the server's NamespaceArray changes after a session (re-)establishment
885
+ * (OPC UA Part 4, Section 5.7.1 — Session Client Renew NodeIds conformance unit).
886
+ *
887
+ * Use `oldTable.remapNodeId(nodeId, newTable)` to recalculate cached NodeIds.
888
+ *
889
+ * @example
890
+ * ```ts
891
+ * client.onNamespaceTableChanged = (oldTable, newTable) => {
892
+ * cachedNodeId = oldTable.remapNodeId(cachedNodeId, newTable)
893
+ * }
894
+ * ```
895
+ */
896
+ onNamespaceTableChanged;
897
+ /**
898
+ * Called when the server sends `EstimatedReturnTime = MinDateTime`, indicating it does not
899
+ * expect to restart (OPC UA Part 5, Section 12.6 — Base Info Client Estimated Return Time
900
+ * conformance unit).
901
+ *
902
+ * When this fires the automatic reconnect is suppressed. The application is responsible for
903
+ * deciding whether to keep the `Client` instance or dispose it.
904
+ *
905
+ * @example
906
+ * ```ts
907
+ * client.onPermanentShutdown = () => {
908
+ * console.warn('Server will not restart — closing client.')
909
+ * }
910
+ * ```
911
+ */
912
+ onPermanentShutdown;
669
913
  getSession() {
670
914
  if (!this.session) {
671
915
  throw new Error("No session available");
@@ -686,6 +930,47 @@ var Client = class {
686
930
  new SubscriptionService(authToken, sc),
687
931
  new MonitoredItemService(authToken, sc)
688
932
  );
933
+ this.subscriptionHandler.onShutdown = () => this.handleServerShutdownDetected();
934
+ void this.refreshNamespaceTable();
935
+ }
936
+ /**
937
+ * Reads `Server.NamespaceArray` (ns=0, i=2255) and updates the stored
938
+ * `NamespaceTable`. When the table changes compared to the previous read the
939
+ * `onNamespaceTableChanged` callback is fired so the application can remap
940
+ * any cached NodeIds (OPC UA Part 4, Section 5.7.1 — Session Client Renew
941
+ * NodeIds conformance unit).
942
+ *
943
+ * Errors are logged as warnings and do not propagate to the caller.
944
+ */
945
+ async refreshNamespaceTable() {
946
+ if (!this.attributeService) return;
947
+ try {
948
+ const results = await this.attributeService.ReadValue([NAMESPACE_ARRAY_NODE_ID]);
949
+ const variant = results[0]?.value;
950
+ const uris = variant?.value;
951
+ if (!Array.isArray(uris)) {
952
+ this.logger.warn("NamespaceArray read returned an unexpected value; skipping table update.");
953
+ return;
954
+ }
955
+ const newTable = new NamespaceTable(uris);
956
+ const oldTable = this.namespaceTable;
957
+ this.namespaceTable = newTable;
958
+ if (oldTable !== void 0 && !oldTable.equals(newTable)) {
959
+ this.logger.info("NamespaceArray changed; notifying application to renew NodeIds.");
960
+ this.onNamespaceTableChanged?.(oldTable, newTable);
961
+ }
962
+ } catch (err) {
963
+ this.logger.warn("NamespaceArray read failed; namespace table not updated:", err);
964
+ }
965
+ }
966
+ /**
967
+ * Returns the most recently read `NamespaceTable` for this session.
968
+ *
969
+ * Available after `connect()` completes (the table is read as part of session
970
+ * establishment). Returns `undefined` before the first successful read.
971
+ */
972
+ getNamespaceTable() {
973
+ return this.namespaceTable;
689
974
  }
690
975
  /**
691
976
  * Executes `fn` and, if it throws a `SessionInvalidError`, creates a fresh
@@ -727,6 +1012,11 @@ var Client = class {
727
1012
  * Starts a periodic keep-alive timer that reads Server_ServerStatus when no subscription is
728
1013
  * active. OPC UA Part 4, Section 5.7.1 requires clients to keep the session alive; when no
729
1014
  * subscription Publish loop is running this is the only mechanism that does so.
1015
+ *
1016
+ * The keep-alive read also serves as the **Detect Shutdown** mechanism (Session Client Detect
1017
+ * Shutdown conformance unit): when the returned `ServerStatusDataType.state` equals
1018
+ * `ServerStateEnum.Shutdown` the client schedules a reconnect after
1019
+ * `SHUTDOWN_RECONNECT_DELAY_MS` to let the server finish its shutdown sequence.
730
1020
  */
731
1021
  startKeepAlive() {
732
1022
  this.keepAliveTimer = setInterval(() => {
@@ -734,7 +1024,12 @@ var Client = class {
734
1024
  return;
735
1025
  }
736
1026
  if (this.attributeService) {
737
- void this.attributeService.ReadValue([SERVER_STATUS_NODE_ID]).catch((err) => {
1027
+ void this.attributeService.ReadValue([SERVER_STATUS_NODE_ID]).then((results) => {
1028
+ const statusData = results[0]?.value;
1029
+ if (statusData?.state === opcjsBase.ServerStateEnum.Shutdown) {
1030
+ this.handleServerShutdownDetected();
1031
+ }
1032
+ }).catch((err) => {
738
1033
  this.logger.warn("Keep-alive read failed:", err);
739
1034
  });
740
1035
  }
@@ -744,6 +1039,87 @@ var Client = class {
744
1039
  clearInterval(this.keepAliveTimer);
745
1040
  this.keepAliveTimer = void 0;
746
1041
  }
1042
+ /**
1043
+ * Called when a server-shutdown announcement is detected — either via the keep-alive read
1044
+ * returning `ServerStateEnum.Shutdown` or via a subscription `StatusChangeNotification`
1045
+ * with status `BadShutdown` / `BadServerHalted`.
1046
+ *
1047
+ * Reads `Server/ServerStatus/EstimatedReturnTime` (ns=0, i=2992) to decide how long to
1048
+ * wait before reconnecting (Base Info Client Estimated Return Time conformance unit).
1049
+ * Falls back to `configuration.shutdownReconnectDelayMs` when the read fails or the
1050
+ * attributed service is not yet available. Fires `onPermanentShutdown` and suppresses the
1051
+ * reconnect when the server sends `MinDateTime`.
1052
+ *
1053
+ * Only one reconnect attempt is scheduled at a time; a second detection while one is already
1054
+ * pending is silently ignored.
1055
+ */
1056
+ handleServerShutdownDetected() {
1057
+ if (this.shutdownReconnectPending) {
1058
+ return;
1059
+ }
1060
+ this.shutdownReconnectPending = true;
1061
+ this.stopKeepAlive();
1062
+ this.logger.warn("Server shutdown detected; reading EstimatedReturnTime...");
1063
+ void this.computeReconnectDelayMs().then((delayMs) => {
1064
+ if (delayMs === null) {
1065
+ this.logger.warn(
1066
+ "Server indicated it will not restart (MinDateTime). Firing onPermanentShutdown."
1067
+ );
1068
+ this.onPermanentShutdown?.();
1069
+ this.shutdownReconnectPending = false;
1070
+ return;
1071
+ }
1072
+ this.logger.warn(`Scheduling reconnect in ${delayMs} ms...`);
1073
+ setTimeout(() => {
1074
+ void this.reconnectAndReactivate().then(() => {
1075
+ this.initServices();
1076
+ this.startKeepAlive();
1077
+ this.logger.info("Reconnected after server shutdown.");
1078
+ }).catch((err) => {
1079
+ this.logger.warn("Reconnect after server shutdown failed:", err);
1080
+ }).finally(() => {
1081
+ this.shutdownReconnectPending = false;
1082
+ });
1083
+ }, delayMs);
1084
+ });
1085
+ }
1086
+ /**
1087
+ * Reads `Server/ServerStatus/EstimatedReturnTime` (ns=0, i=2992) and returns the reconnect
1088
+ * delay in milliseconds (Base Info Client Estimated Return Time — OPC UA Part 5, §12.6):
1089
+ *
1090
+ * - Valid future `DateTime` → delay = `estimatedReturnTime − now`, clamped to at least
1091
+ * `MIN_RECONNECT_DELAY_MS`.
1092
+ * - Past `DateTime` (server should already be available) → `MIN_RECONNECT_DELAY_MS`.
1093
+ * - OPC UA `MinDateTime` (server will not restart) → `null`.
1094
+ * - Unreadable / unavailable → falls back to `configuration.shutdownReconnectDelayMs`.
1095
+ */
1096
+ async computeReconnectDelayMs() {
1097
+ if (!this.attributeService) {
1098
+ return this.configuration.shutdownReconnectDelayMs;
1099
+ }
1100
+ try {
1101
+ const results = await this.attributeService.ReadValue([ESTIMATED_RETURN_TIME_NODE_ID]);
1102
+ const variant = results[0]?.value;
1103
+ const estimatedReturnTime = variant?.value;
1104
+ if (estimatedReturnTime instanceof Date && !isNaN(estimatedReturnTime.getTime())) {
1105
+ if (estimatedReturnTime.getTime() <= OPC_UA_MIN_DATE_TIME_MS) {
1106
+ return null;
1107
+ }
1108
+ const delayMs = estimatedReturnTime.getTime() - Date.now();
1109
+ if (delayMs > 0) {
1110
+ this.logger.info(
1111
+ `EstimatedReturnTime: ${estimatedReturnTime.toISOString()} (reconnect in ${delayMs} ms).`
1112
+ );
1113
+ return delayMs;
1114
+ }
1115
+ this.logger.info("EstimatedReturnTime is in the past; reconnecting immediately.");
1116
+ return this.configuration.minReconnectDelayMs;
1117
+ }
1118
+ } catch (err) {
1119
+ this.logger.debug("Failed to read EstimatedReturnTime; using configured delay:", err);
1120
+ }
1121
+ return this.configuration.shutdownReconnectDelayMs;
1122
+ }
747
1123
  async connect() {
748
1124
  const { ws, sc } = await this.openTransportAndChannel();
749
1125
  this.secureChannel = sc;
@@ -908,37 +1284,78 @@ var Client = class {
908
1284
  this.ws = void 0;
909
1285
  this.logger.info("Disconnected.");
910
1286
  }
911
- async read(ids) {
912
- return this.withSessionRefresh(async () => {
913
- const result = await this.attributeService?.ReadValue(ids);
914
- return result?.map((r) => new ReadValueResult(r.value, r.statusCode)) ?? [];
1287
+ /**
1288
+ * Reads the Value attribute of one or more Nodes.
1289
+ *
1290
+ * The returned object is a `Promise` that also exposes `requestHandle` — the
1291
+ * OPC UA `requestHandle` assigned to the underlying `ReadRequest`. The handle
1292
+ * is available synchronously (before `await`) so it can be passed to
1293
+ * `cancel()` to abort the in-flight request.
1294
+ *
1295
+ * @example
1296
+ * ```ts
1297
+ * const req = client.read([nodeId])
1298
+ * await client.cancel(req.requestHandle) // abort before response
1299
+ * const results = await req // ReadValueResult[]
1300
+ * ```
1301
+ */
1302
+ read(ids, options) {
1303
+ const requestHandle = nextRequestHandle();
1304
+ const promise = this.withSessionRefresh(async () => {
1305
+ const result = await this.attributeService?.ReadValue(ids, 0, void 0, options?.returnDiagnostics, requestHandle);
1306
+ return result?.map((r) => new ReadValueResult(r.value, r.statusCode, r.diagnosticInfo)) ?? [];
915
1307
  });
1308
+ return Object.assign(promise, { requestHandle });
916
1309
  }
917
1310
  /**
918
1311
  * Method for calling a single method on the server.
1312
+ *
1313
+ * The returned object is a `Promise` that also exposes `requestHandle` — the
1314
+ * OPC UA `requestHandle` assigned to the underlying `CallRequest`. The handle
1315
+ * is available synchronously (before `await`) so it can be passed to
1316
+ * `cancel()` to abort the in-flight request.
1317
+ *
919
1318
  * @param objectId - NodeId of the Object that owns the method.
920
1319
  * @param methodId - NodeId of the Method to invoke.
921
1320
  * @param inputArguments - Input argument Variants (default: empty).
922
- * @returns The CallMethodResult for the invoked method.
1321
+ * @param options - Request options (e.g. `returnDiagnostics`).
1322
+ * @returns A promise resolving to the CallMethodResult, with `requestHandle` available synchronously.
923
1323
  */
924
- async callMethod(objectId, methodId, inputArguments = []) {
925
- return this.withSessionRefresh(async () => {
1324
+ callMethod(objectId, methodId, inputArguments = [], options) {
1325
+ const requestHandle = nextRequestHandle();
1326
+ const promise = this.withSessionRefresh(async () => {
926
1327
  const request = new opcjsBase.CallMethodRequest();
927
1328
  request.objectId = objectId;
928
1329
  request.methodId = methodId;
929
1330
  request.inputArguments = inputArguments.map((arg) => opcjsBase.Variant.newFrom(arg));
930
- const responses = await this.methodService.call([request]);
1331
+ const responses = await this.methodService.call([request], options?.returnDiagnostics, requestHandle);
931
1332
  const response = responses[0];
932
- return new CallMethodResult(response.value, response.statusCode);
1333
+ return new CallMethodResult(response.value, response.statusCode, response.diagnosticInfo);
933
1334
  });
1335
+ return Object.assign(promise, { requestHandle });
934
1336
  }
935
- async browse(nodeId, recursive = false) {
936
- return this.withSessionRefresh(() => {
1337
+ /**
1338
+ * Browses the Address Space starting from `nodeId`.
1339
+ *
1340
+ * The returned object is a `Promise` that also exposes `requestHandle` — the
1341
+ * OPC UA `requestHandle` assigned to the initial `BrowseRequest`. The handle
1342
+ * is available synchronously (before `await`) so it can be passed to
1343
+ * `cancel()` to abort the in-flight request.
1344
+ *
1345
+ * @param nodeId - Starting node.
1346
+ * @param recursive - When true, recursively follows HierarchicalReferences.
1347
+ * @param options - Request options (e.g. `returnDiagnostics`).
1348
+ * @returns A promise resolving to the list of referenced nodes, with `requestHandle` available synchronously.
1349
+ */
1350
+ browse(nodeId, recursive = false, options) {
1351
+ const requestHandle = nextRequestHandle();
1352
+ const promise = this.withSessionRefresh(() => {
937
1353
  const visited = /* @__PURE__ */ new Set();
938
- return this.browseRecursive(nodeId, recursive, visited);
1354
+ return this.browseRecursive(nodeId, recursive, visited, options?.returnDiagnostics ?? 0, requestHandle);
939
1355
  });
1356
+ return Object.assign(promise, { requestHandle });
940
1357
  }
941
- async browseRecursive(nodeId, recursive, visited) {
1358
+ async browseRecursive(nodeId, recursive, visited, returnDiagnostics, requestHandle) {
942
1359
  const nodeKey = `${nodeId.namespace}:${nodeId.identifier}`;
943
1360
  if (visited.has(nodeKey)) {
944
1361
  return [];
@@ -951,12 +1368,12 @@ var Client = class {
951
1368
  description.includeSubtypes = true;
952
1369
  description.nodeClassMask = 0;
953
1370
  description.resultMask = opcjsBase.BrowseResultMaskEnum.All;
954
- const browseResults = await this.browseService.browse([description]);
1371
+ const browseResults = await this.browseService.browse([description], returnDiagnostics, requestHandle);
955
1372
  const browseResult = browseResults[0];
956
1373
  const allReferences = [...browseResult.references ?? []];
957
1374
  let continuationPoint = browseResult.continuationPoint;
958
1375
  while (continuationPoint && continuationPoint.byteLength > 0) {
959
- const nextResults = await this.browseService.browseNext([continuationPoint], false);
1376
+ const nextResults = await this.browseService.browseNext([continuationPoint], false, returnDiagnostics);
960
1377
  const nextResult = nextResults[0];
961
1378
  allReferences.push(...nextResult.references ?? []);
962
1379
  continuationPoint = nextResult.continuationPoint;
@@ -981,7 +1398,8 @@ var Client = class {
981
1398
  const childResults = await this.browseRecursive(
982
1399
  childNodeId,
983
1400
  true,
984
- visited
1401
+ visited,
1402
+ returnDiagnostics
985
1403
  );
986
1404
  results.push(...childResults);
987
1405
  }
@@ -991,6 +1409,170 @@ var Client = class {
991
1409
  async subscribe(ids, callback, options) {
992
1410
  this.subscriptionHandler?.subscribe(ids, callback, options);
993
1411
  }
1412
+ /**
1413
+ * Asks the server to cancel a pending service request
1414
+ * (OPC UA Part 4, Section 5.7.5 — Session Client Cancel conformance unit).
1415
+ *
1416
+ * The `requestHandle` uniquely identifies the pending request. It is the value
1417
+ * assigned to `RequestHeader.requestHandle` when the request was initially sent.
1418
+ * Service calls made through this client automatically assign monotonically
1419
+ * increasing handles, so the caller can capture the handle before or after issuing
1420
+ * Each method (`read`, `browse`, `callMethod`) returns a `Promise` with a
1421
+ * `requestHandle` property that is available synchronously. Pass that handle
1422
+ * here to abort the corresponding in-flight request.
1423
+ *
1424
+ * The server makes a best-effort attempt to cancel the matching request. Cancelled
1425
+ * requests complete with status `BadRequestCancelledByClient`. Not all servers
1426
+ * guarantee that a request in flight can be cancelled.
1427
+ *
1428
+ * @param requestHandle - Handle of the pending request to cancel.
1429
+ * @returns The number of pending requests the server actually cancelled.
1430
+ * @throws If no session is active or the server returns a non-Good status.
1431
+ *
1432
+ * @example
1433
+ * ```ts
1434
+ * // Issue a potentially slow operation and immediately cancel it.
1435
+ * const req = client.read([nodeId])
1436
+ * const cancelled = await client.cancel(req.requestHandle)
1437
+ * console.log(`Cancelled ${cancelled} request(s)`)
1438
+ * const results = await req // resolves with BadRequestCancelledByClient
1439
+ * ```
1440
+ */
1441
+ async cancel(requestHandle) {
1442
+ if (!this.sessionHandler) {
1443
+ throw new Error("Not connected: call connect() before cancel()");
1444
+ }
1445
+ return this.sessionHandler.cancel(requestHandle);
1446
+ }
1447
+ /**
1448
+ * Switches the active user identity for the current session by calling ActivateSession
1449
+ * with a new identity token (OPC UA Part 4, Section 5.7.3 — Session Client Impersonate
1450
+ * conformance unit).
1451
+ *
1452
+ * The server re-evaluates authorisation under the new identity while keeping all existing
1453
+ * Subscriptions and MonitoredItems intact. The new identity is also stored so that any
1454
+ * subsequent auto-reconnect or session refresh uses it instead of the original identity.
1455
+ *
1456
+ * @param identity - The new user identity to apply to the session.
1457
+ * @throws {Error} When not connected (call `connect()` first).
1458
+ * @throws {Error} When the server rejects the identity (e.g. `BadIdentityTokenRejected`
1459
+ * or `BadUserAccessDenied`).
1460
+ *
1461
+ * @example
1462
+ * ```ts
1463
+ * await client.connect()
1464
+ * // ... work as the original user ...
1465
+ * await client.impersonate(UserIdentity.newWithUserName('admin', 'secret'))
1466
+ * // ... subsequent calls run under the admin identity ...
1467
+ * ```
1468
+ */
1469
+ async impersonate(identity) {
1470
+ if (!this.session) {
1471
+ throw new Error("Not connected: call connect() before impersonate()");
1472
+ }
1473
+ await this.session.impersonate(identity);
1474
+ this.identity = identity;
1475
+ }
1476
+ /**
1477
+ * Reads the `SelectionListType` metadata for a Variable
1478
+ * (OPC UA Part 5, §7.18 — Base Info Client Selection List conformance unit).
1479
+ *
1480
+ * The client browses the node's `HasProperty` references for `Selections`,
1481
+ * `SelectionDescriptions`, and `RestrictToList`, then reads their values in a
1482
+ * single batch Read request.
1483
+ *
1484
+ * Works transparently for instances of `SelectionListType` (ns=0; i=19726) and
1485
+ * any of its subtypes, because all subtypes inherit the `Selections` mandatory
1486
+ * property.
1487
+ *
1488
+ * @param nodeId - NodeId of the Variable to inspect.
1489
+ * @returns `SelectionList` when the node has a `Selections` property, `null` otherwise.
1490
+ * @throws When not connected or if the server returns a non-Good service status.
1491
+ *
1492
+ * @example
1493
+ * ```ts
1494
+ * const list = await client.getSelectionList(nodeId)
1495
+ * if (list) {
1496
+ * list.selectionDescriptions.forEach((desc, i) =>
1497
+ * console.log(`[${i}] ${desc.text}:`, list.selections[i])
1498
+ * )
1499
+ * }
1500
+ * ```
1501
+ */
1502
+ getSelectionList(nodeId) {
1503
+ return this.withSessionRefresh(() => this.fetchSelectionList(nodeId));
1504
+ }
1505
+ /**
1506
+ * Internal implementation of `getSelectionList`. Browses the node's
1507
+ * HasProperty references to locate Selections/SelectionDescriptions/RestrictToList,
1508
+ * then batch-reads their values.
1509
+ */
1510
+ async fetchSelectionList(nodeId) {
1511
+ if (!this.browseService || !this.attributeService) {
1512
+ throw new Error("Not connected: call connect() before getSelectionList()");
1513
+ }
1514
+ const description = new opcjsBase.BrowseDescription();
1515
+ description.nodeId = nodeId;
1516
+ description.browseDirection = opcjsBase.BrowseDirectionEnum.Forward;
1517
+ description.referenceTypeId = HAS_PROPERTY_REF_TYPE_ID;
1518
+ description.includeSubtypes = false;
1519
+ description.nodeClassMask = 0;
1520
+ description.resultMask = opcjsBase.BrowseResultMaskEnum.All;
1521
+ const browseResults = await this.browseService.browse([description]);
1522
+ const refs = browseResults[0]?.references ?? [];
1523
+ let selectionsNodeId;
1524
+ let selectionsDescNodeId;
1525
+ let restrictToListNodeId;
1526
+ for (const ref of refs) {
1527
+ const name = ref.browseName?.name;
1528
+ if (name === "Selections") {
1529
+ selectionsNodeId = ref.nodeId.nodeId;
1530
+ } else if (name === "SelectionDescriptions") {
1531
+ selectionsDescNodeId = ref.nodeId.nodeId;
1532
+ } else if (name === "RestrictToList") {
1533
+ restrictToListNodeId = ref.nodeId.nodeId;
1534
+ }
1535
+ }
1536
+ if (!selectionsNodeId) {
1537
+ return null;
1538
+ }
1539
+ const nodeIdsToRead = [selectionsNodeId];
1540
+ if (selectionsDescNodeId) nodeIdsToRead.push(selectionsDescNodeId);
1541
+ if (restrictToListNodeId) nodeIdsToRead.push(restrictToListNodeId);
1542
+ const readResults = await this.attributeService.ReadValue(nodeIdsToRead);
1543
+ const selectionsVariant = readResults[0]?.value;
1544
+ const selections = Array.isArray(selectionsVariant?.value) ? selectionsVariant.value : [];
1545
+ let selectionDescriptions = [];
1546
+ let descReadIdx = 1;
1547
+ if (selectionsDescNodeId) {
1548
+ const descVariant = readResults[descReadIdx]?.value;
1549
+ if (Array.isArray(descVariant?.value)) {
1550
+ selectionDescriptions = descVariant.value;
1551
+ }
1552
+ descReadIdx++;
1553
+ }
1554
+ let restrictToList = false;
1555
+ if (restrictToListNodeId) {
1556
+ const rtlVariant = readResults[descReadIdx]?.value;
1557
+ if (typeof rtlVariant?.value === "boolean") {
1558
+ restrictToList = rtlVariant.value;
1559
+ }
1560
+ }
1561
+ return { nodeId, selections, selectionDescriptions, restrictToList };
1562
+ }
1563
+ /**
1564
+ * The `requestHandle` value that was assigned to the most recently issued
1565
+ * service request in this session.
1566
+ *
1567
+ * @deprecated Prefer accessing `requestHandle` directly on the promise returned
1568
+ * by `read()`, `browse()`, or `callMethod()`, which is available synchronously
1569
+ * before `await` and avoids relying on shared module state.
1570
+ *
1571
+ * Returns `0` before any request has been sent.
1572
+ */
1573
+ get lastRequestHandle() {
1574
+ return lastAssignedHandle();
1575
+ }
994
1576
  };
995
1577
  var ConfigurationClient = class _ConfigurationClient extends opcjsBase.Configuration {
996
1578
  /**
@@ -1001,6 +1583,26 @@ var ConfigurationClient = class _ConfigurationClient extends opcjsBase.Configura
1001
1583
  * @see SecurityConfiguration
1002
1584
  */
1003
1585
  securityConfiguration;
1586
+ /**
1587
+ * How long to wait (ms) before attempting a reconnect after a server-shutdown
1588
+ * is detected via `ServerStatus/State = Shutdown` or a subscription
1589
+ * `StatusChangeNotification` with `BadShutdown` / `BadServerHalted`.
1590
+ *
1591
+ * Gives the server process time to exit fully before the client tries to
1592
+ * re-connect. Defaults to 5 000 ms.
1593
+ */
1594
+ shutdownReconnectDelayMs = 5e3;
1595
+ /**
1596
+ * Minimum reconnect delay in milliseconds used when
1597
+ * `Server/ServerStatus/EstimatedReturnTime` is already in the past (the server
1598
+ * should already be available again).
1599
+ *
1600
+ * Also acts as the lower bound for the ERT-derived delay, ensuring the client
1601
+ * always waits at least this long before retrying.
1602
+ *
1603
+ * Defaults to 1 000 ms.
1604
+ */
1605
+ minReconnectDelayMs = 1e3;
1004
1606
  static getSimple(name, company, loggerFactory) {
1005
1607
  if (!loggerFactory) {
1006
1608
  loggerFactory = new opcjsBase.LoggerFactory({
@@ -1090,10 +1692,44 @@ var UserIdentity = class _UserIdentity {
1090
1692
  }
1091
1693
  };
1092
1694
 
1695
+ // src/requestOptions.ts
1696
+ var ReturnDiagnosticsMask = {
1697
+ /** All service-level diagnostic fields. */
1698
+ ServiceLevel: 31,
1699
+ /** All operation-level diagnostic fields. */
1700
+ OperationLevel: 992,
1701
+ /** All diagnostic fields (service level + operation level). */
1702
+ All: 1023,
1703
+ /** Service-level: index to SymbolicId in the server string table. */
1704
+ ServiceSymbolicId: 1,
1705
+ /** Service-level: index to LocalizedText in the server string table. */
1706
+ ServiceLocalizedText: 2,
1707
+ /** Service-level: additional info string. */
1708
+ ServiceAdditionalInfo: 4,
1709
+ /** Service-level: inner status code. */
1710
+ ServiceInnerStatusCode: 8,
1711
+ /** Service-level: inner diagnostic info. */
1712
+ ServiceInnerDiagnostics: 16,
1713
+ /** Operation-level: index to SymbolicId in the server string table. */
1714
+ OperationSymbolicId: 32,
1715
+ /** Operation-level: index to LocalizedText in the server string table. */
1716
+ OperationLocalizedText: 64,
1717
+ /** Operation-level: additional info string. */
1718
+ OperationAdditionalInfo: 128,
1719
+ /** Operation-level: inner status code. */
1720
+ OperationInnerStatusCode: 256,
1721
+ /** Operation-level: inner diagnostic info. */
1722
+ OperationInnerDiagnostics: 512
1723
+ };
1724
+
1093
1725
  exports.BrowseNodeResult = BrowseNodeResult;
1726
+ exports.CERTIFICATE_REQUIRED_STATUS_CODES = CERTIFICATE_REQUIRED_STATUS_CODES;
1094
1727
  exports.CallMethodResult = CallMethodResult;
1728
+ exports.CertificateRequiredError = CertificateRequiredError;
1095
1729
  exports.Client = Client;
1096
1730
  exports.ConfigurationClient = ConfigurationClient;
1731
+ exports.NamespaceTable = NamespaceTable;
1732
+ exports.ReturnDiagnosticsMask = ReturnDiagnosticsMask;
1097
1733
  exports.SECURITY_POLICY_NONE_URI = SECURITY_POLICY_NONE_URI;
1098
1734
  exports.SessionInvalidError = SessionInvalidError;
1099
1735
  exports.UserIdentity = UserIdentity;