opcjs-client 0.1.18-alpha → 0.1.26-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.js CHANGED
@@ -1,11 +1,38 @@
1
- import { initLoggerProvider, getLogger, WebSocketFascade, WebSocketReadableStream, WebSocketWritableStream, SecureChannelContext, TcpMessageInjector, TcpConnectionHandler, TcpMessageDecoupler, SecureChannelMesssageEncoder, SecureChannelTypeDecoder, SecureChannelMessageDecoder, SecureChannelTypeEncoder, SecureChannelChunkWriter, SecureChannelChunkReader, SecureChannelFacade, CallMethodRequest, Variant, BrowseDescription, BrowseDirectionEnum, NodeId, BrowseResultMaskEnum, Configuration, LoggerFactory, Encoder, BinaryWriter, registerEncoders, Decoder, BinaryReader, registerTypeDecoders, registerBinaryDecoders, UserTokenTypeEnum, AnonymousIdentityToken, UserNameIdentityToken, IssuedIdentityToken, ReadValueId, QualifiedName, ReadRequest, TimestampsToReturnEnum, CallRequest, ViewDescription, BrowseRequest, BrowseNextRequest, SubscriptionAcknowledgement, CreateSubscriptionRequest, PublishRequest, MonitoringParameters, ExtensionObject, MonitoredItemCreateRequest, MonitoringModeEnum, CreateMonitoredItemsRequest, ApplicationDescription, LocalizedText, ApplicationTypeEnum, CreateSessionRequest, CreateSessionResponse, SignatureData, ActivateSessionRequest, RequestHeader } from 'opcjs-base';
1
+ import { StatusCodeToString, initLoggerProvider, getLogger, WebSocketFascade, WebSocketReadableStream, WebSocketWritableStream, SecureChannelContext, TcpMessageInjector, TcpConnectionHandler, TcpMessageDecoupler, SecureChannelMesssageEncoder, SecureChannelTypeDecoder, SecureChannelMessageDecoder, SecureChannelTypeEncoder, SecureChannelChunkWriter, SecureChannelChunkReader, SecureChannelFacade, CallMethodRequest, Variant, BrowseDescription, BrowseDirectionEnum, NodeId, BrowseResultMaskEnum, Configuration, LoggerFactory, Encoder, BinaryWriter, registerEncoders, Decoder, BinaryReader, registerTypeDecoders, registerBinaryDecoders, UserTokenTypeEnum, AnonymousIdentityToken, UserNameIdentityToken, IssuedIdentityToken, TimestampsToReturnEnum, ReadValueId, QualifiedName, ReadRequest, StatusCode, CallRequest, ViewDescription, BrowseRequest, BrowseNextRequest, SubscriptionAcknowledgement, CreateSubscriptionRequest, PublishRequest, MonitoringParameters, ExtensionObject, MonitoredItemCreateRequest, MonitoringModeEnum, CreateMonitoredItemsRequest, StatusCodeToStringNumber, RequestHeader, ApplicationDescription, LocalizedText, ApplicationTypeEnum, CreateSessionRequest, CreateSessionResponse, SignatureData, ActivateSessionRequest } from 'opcjs-base';
2
2
 
3
3
  // src/client.ts
4
+ var SessionInvalidError = class extends Error {
5
+ statusCode;
6
+ constructor(statusCode) {
7
+ super(`Session is no longer valid: ${StatusCodeToString(statusCode)} (0x${statusCode.toString(16).toUpperCase()})`);
8
+ this.name = "SessionInvalidError";
9
+ this.statusCode = statusCode;
10
+ }
11
+ };
12
+
13
+ // src/services/serviceBase.ts
4
14
  var ServiceBase = class {
5
15
  constructor(authToken, secureChannel) {
6
16
  this.authToken = authToken;
7
17
  this.secureChannel = secureChannel;
8
18
  }
19
+ /**
20
+ * Validates the `serviceResult` value from a response header.
21
+ *
22
+ * Throws `SessionInvalidError` for session-related status codes so callers
23
+ * can detect a dropped session and act accordingly (e.g. reconnect).
24
+ * Throws a generic `Error` for all other non-Good codes.
25
+ *
26
+ * @param result - The `serviceResult` value from the response header.
27
+ * @param context - Short description used in the error message (e.g. "ReadRequest").
28
+ */
29
+ checkServiceResult(result, context) {
30
+ if (result === void 0 || result === StatusCode.Good) return;
31
+ if (result === StatusCode.BadSessionIdInvalid || result === StatusCode.BadSessionClosed) {
32
+ throw new SessionInvalidError(result);
33
+ }
34
+ throw new Error(`${context} failed: ${StatusCodeToString(result)} (${StatusCodeToStringNumber(result)})`);
35
+ }
9
36
  createRequestHeader() {
10
37
  const requestHeader = new RequestHeader();
11
38
  requestHeader.authenticationToken = this.authToken;
@@ -26,6 +53,10 @@ var SessionService = class _SessionService extends ServiceBase {
26
53
  this.configuration = configuration;
27
54
  }
28
55
  logger = getLogger("services.SessionService");
56
+ /**
57
+ * Creates a new session on the server (OPC UA Part 4, Section 5.7.2).
58
+ * @returns The session ID, authentication token, and selected server endpoint.
59
+ */
29
60
  async createSession() {
30
61
  this.logger.debug("Creating session...");
31
62
  const clientDescription = new ApplicationDescription();
@@ -55,6 +86,10 @@ var SessionService = class _SessionService extends ServiceBase {
55
86
  if (!castedResponse || !castedResponse.sessionId || !castedResponse.authenticationToken) {
56
87
  throw new Error("CreateSessionResponse missing SessionId or AuthenticationToken");
57
88
  }
89
+ const serviceResult = castedResponse.responseHeader?.serviceResult;
90
+ if (serviceResult !== void 0 && serviceResult !== StatusCode.Good) {
91
+ throw new Error(`CreateSessionRequest failed: ${StatusCodeToString(serviceResult)}`);
92
+ }
58
93
  const endpoint = "opc." + this.secureChannel.getEndpointUrl();
59
94
  const endpointUrl = new URL(endpoint);
60
95
  const securityMode = this.secureChannel.getSecurityMode();
@@ -73,6 +108,10 @@ var SessionService = class _SessionService extends ServiceBase {
73
108
  endpoint: serverEndpoint
74
109
  };
75
110
  }
111
+ /**
112
+ * Activates an existing session using the supplied identity token (OPC UA Part 4, Section 5.7.3).
113
+ * @param identityToken - User identity token (anonymous, username/password, certificate, or issued token).
114
+ */
76
115
  async activateSession(identityToken) {
77
116
  const signatureData = new SignatureData();
78
117
  signatureData.algorithm = this.secureChannel.getSecurityPolicy();
@@ -85,7 +124,11 @@ var SessionService = class _SessionService extends ServiceBase {
85
124
  request.userIdentityToken = ExtensionObject.newBinary(identityToken);
86
125
  request.userTokenSignature = signatureData;
87
126
  this.logger.debug("Sending ActivateSessionRequest...");
88
- await this.secureChannel.issueServiceRequest(request);
127
+ const activateResponse = await this.secureChannel.issueServiceRequest(request);
128
+ const activateResult = activateResponse?.responseHeader?.serviceResult;
129
+ if (activateResult !== void 0 && activateResult !== StatusCode.Good) {
130
+ throw new Error(`ActivateSessionRequest failed: ${StatusCodeToString(activateResult)}`);
131
+ }
89
132
  this.logger.debug("Session activated.");
90
133
  }
91
134
  recreate(authToken) {
@@ -169,268 +212,17 @@ var SessionHandler = class {
169
212
  // src/services/attributeServiceAttributes.ts
170
213
  var AttrIdValue = 13;
171
214
 
172
- // ../base/src/types/statusCode.ts
173
- function StatusCodeToString(statusCode) {
174
- if (statusCode === void 0) {
175
- return "Unknown";
176
- }
177
- const name = Object.entries(StatusCode).find(([, v]) => v === statusCode)?.[0];
178
- return name ?? `0x${statusCode.toString(16).toUpperCase().padStart(8, "0")}`;
179
- }
180
- var StatusCode = /* @__PURE__ */ ((StatusCode2) => {
181
- StatusCode2[StatusCode2["Good"] = 0] = "Good";
182
- StatusCode2[StatusCode2["Uncertain"] = 1073741824] = "Uncertain";
183
- StatusCode2[StatusCode2["Bad"] = 2147483648] = "Bad";
184
- StatusCode2[StatusCode2["BadUnexpectedError"] = 2147549184] = "BadUnexpectedError";
185
- StatusCode2[StatusCode2["BadInternalError"] = 2147614720] = "BadInternalError";
186
- StatusCode2[StatusCode2["BadOutOfMemory"] = 2147680256] = "BadOutOfMemory";
187
- StatusCode2[StatusCode2["BadResourceUnavailable"] = 2147745792] = "BadResourceUnavailable";
188
- StatusCode2[StatusCode2["BadCommunicationError"] = 2147811328] = "BadCommunicationError";
189
- StatusCode2[StatusCode2["BadEncodingError"] = 2147876864] = "BadEncodingError";
190
- StatusCode2[StatusCode2["BadDecodingError"] = 2147942400] = "BadDecodingError";
191
- StatusCode2[StatusCode2["BadEncodingLimitsExceeded"] = 2148007936] = "BadEncodingLimitsExceeded";
192
- StatusCode2[StatusCode2["BadRequestTooLarge"] = 2159542272] = "BadRequestTooLarge";
193
- StatusCode2[StatusCode2["BadResponseTooLarge"] = 2159607808] = "BadResponseTooLarge";
194
- StatusCode2[StatusCode2["BadUnknownResponse"] = 2148073472] = "BadUnknownResponse";
195
- StatusCode2[StatusCode2["BadTimeout"] = 2148139008] = "BadTimeout";
196
- StatusCode2[StatusCode2["BadServiceUnsupported"] = 2148204544] = "BadServiceUnsupported";
197
- StatusCode2[StatusCode2["BadShutdown"] = 2148270080] = "BadShutdown";
198
- StatusCode2[StatusCode2["BadServerNotConnected"] = 2148335616] = "BadServerNotConnected";
199
- StatusCode2[StatusCode2["BadServerHalted"] = 2148401152] = "BadServerHalted";
200
- StatusCode2[StatusCode2["BadNothingToDo"] = 2148466688] = "BadNothingToDo";
201
- StatusCode2[StatusCode2["BadTooManyOperations"] = 2148532224] = "BadTooManyOperations";
202
- StatusCode2[StatusCode2["BadTooManyMonitoredItems"] = 2161836032] = "BadTooManyMonitoredItems";
203
- StatusCode2[StatusCode2["BadDataTypeIdUnknown"] = 2148597760] = "BadDataTypeIdUnknown";
204
- StatusCode2[StatusCode2["BadCertificateInvalid"] = 2148663296] = "BadCertificateInvalid";
205
- StatusCode2[StatusCode2["BadSecurityChecksFailed"] = 2148728832] = "BadSecurityChecksFailed";
206
- StatusCode2[StatusCode2["BadCertificatePolicyCheckFailed"] = 2165571584] = "BadCertificatePolicyCheckFailed";
207
- StatusCode2[StatusCode2["BadCertificateTimeInvalid"] = 2148794368] = "BadCertificateTimeInvalid";
208
- StatusCode2[StatusCode2["BadCertificateIssuerTimeInvalid"] = 2148859904] = "BadCertificateIssuerTimeInvalid";
209
- StatusCode2[StatusCode2["BadCertificateHostNameInvalid"] = 2148925440] = "BadCertificateHostNameInvalid";
210
- StatusCode2[StatusCode2["BadCertificateUriInvalid"] = 2148990976] = "BadCertificateUriInvalid";
211
- StatusCode2[StatusCode2["BadCertificateUseNotAllowed"] = 2149056512] = "BadCertificateUseNotAllowed";
212
- StatusCode2[StatusCode2["BadCertificateIssuerUseNotAllowed"] = 2149122048] = "BadCertificateIssuerUseNotAllowed";
213
- StatusCode2[StatusCode2["BadCertificateUntrusted"] = 2149187584] = "BadCertificateUntrusted";
214
- StatusCode2[StatusCode2["BadCertificateRevocationUnknown"] = 2149253120] = "BadCertificateRevocationUnknown";
215
- StatusCode2[StatusCode2["BadCertificateIssuerRevocationUnknown"] = 2149318656] = "BadCertificateIssuerRevocationUnknown";
216
- StatusCode2[StatusCode2["BadCertificateRevoked"] = 2149384192] = "BadCertificateRevoked";
217
- StatusCode2[StatusCode2["BadCertificateIssuerRevoked"] = 2149449728] = "BadCertificateIssuerRevoked";
218
- StatusCode2[StatusCode2["BadCertificateChainIncomplete"] = 2165112832] = "BadCertificateChainIncomplete";
219
- StatusCode2[StatusCode2["BadUserAccessDenied"] = 2149515264] = "BadUserAccessDenied";
220
- StatusCode2[StatusCode2["BadIdentityTokenInvalid"] = 2149580800] = "BadIdentityTokenInvalid";
221
- StatusCode2[StatusCode2["BadIdentityTokenRejected"] = 2149646336] = "BadIdentityTokenRejected";
222
- StatusCode2[StatusCode2["BadSecureChannelIdInvalid"] = 2149711872] = "BadSecureChannelIdInvalid";
223
- StatusCode2[StatusCode2["BadInvalidTimestamp"] = 2149777408] = "BadInvalidTimestamp";
224
- StatusCode2[StatusCode2["BadNonceInvalid"] = 2149842944] = "BadNonceInvalid";
225
- StatusCode2[StatusCode2["BadSessionIdInvalid"] = 2149908480] = "BadSessionIdInvalid";
226
- StatusCode2[StatusCode2["BadSessionClosed"] = 2149974016] = "BadSessionClosed";
227
- StatusCode2[StatusCode2["BadSessionNotActivated"] = 2150039552] = "BadSessionNotActivated";
228
- StatusCode2[StatusCode2["BadSubscriptionIdInvalid"] = 2150105088] = "BadSubscriptionIdInvalid";
229
- StatusCode2[StatusCode2["BadRequestHeaderInvalid"] = 2150236160] = "BadRequestHeaderInvalid";
230
- StatusCode2[StatusCode2["BadTimestampsToReturnInvalid"] = 2150301696] = "BadTimestampsToReturnInvalid";
231
- StatusCode2[StatusCode2["BadRequestCancelledByClient"] = 2150367232] = "BadRequestCancelledByClient";
232
- StatusCode2[StatusCode2["BadTooManyArguments"] = 2162491392] = "BadTooManyArguments";
233
- StatusCode2[StatusCode2["BadLicenseExpired"] = 2165178368] = "BadLicenseExpired";
234
- StatusCode2[StatusCode2["BadLicenseLimitsExceeded"] = 2165243904] = "BadLicenseLimitsExceeded";
235
- StatusCode2[StatusCode2["BadLicenseNotAvailable"] = 2165309440] = "BadLicenseNotAvailable";
236
- StatusCode2[StatusCode2["GoodSubscriptionTransferred"] = 2949120] = "GoodSubscriptionTransferred";
237
- StatusCode2[StatusCode2["GoodCompletesAsynchronously"] = 3014656] = "GoodCompletesAsynchronously";
238
- StatusCode2[StatusCode2["GoodOverload"] = 3080192] = "GoodOverload";
239
- StatusCode2[StatusCode2["GoodClamped"] = 3145728] = "GoodClamped";
240
- StatusCode2[StatusCode2["BadNoCommunication"] = 2150694912] = "BadNoCommunication";
241
- StatusCode2[StatusCode2["BadWaitingForInitialData"] = 2150760448] = "BadWaitingForInitialData";
242
- StatusCode2[StatusCode2["BadNodeIdInvalid"] = 2150825984] = "BadNodeIdInvalid";
243
- StatusCode2[StatusCode2["BadNodeIdUnknown"] = 2150891520] = "BadNodeIdUnknown";
244
- StatusCode2[StatusCode2["BadAttributeIdInvalid"] = 2150957056] = "BadAttributeIdInvalid";
245
- StatusCode2[StatusCode2["BadIndexRangeInvalid"] = 2151022592] = "BadIndexRangeInvalid";
246
- StatusCode2[StatusCode2["BadIndexRangeNoData"] = 2151088128] = "BadIndexRangeNoData";
247
- StatusCode2[StatusCode2["BadDataEncodingInvalid"] = 2151153664] = "BadDataEncodingInvalid";
248
- StatusCode2[StatusCode2["BadDataEncodingUnsupported"] = 2151219200] = "BadDataEncodingUnsupported";
249
- StatusCode2[StatusCode2["BadNotReadable"] = 2151284736] = "BadNotReadable";
250
- StatusCode2[StatusCode2["BadNotWritable"] = 2151350272] = "BadNotWritable";
251
- StatusCode2[StatusCode2["BadOutOfRange"] = 2151415808] = "BadOutOfRange";
252
- StatusCode2[StatusCode2["BadNotSupported"] = 2151481344] = "BadNotSupported";
253
- StatusCode2[StatusCode2["BadNotFound"] = 2151546880] = "BadNotFound";
254
- StatusCode2[StatusCode2["BadObjectDeleted"] = 2151612416] = "BadObjectDeleted";
255
- StatusCode2[StatusCode2["BadNotImplemented"] = 2151677952] = "BadNotImplemented";
256
- StatusCode2[StatusCode2["BadMonitoringModeInvalid"] = 2151743488] = "BadMonitoringModeInvalid";
257
- StatusCode2[StatusCode2["BadMonitoredItemIdInvalid"] = 2151809024] = "BadMonitoredItemIdInvalid";
258
- StatusCode2[StatusCode2["BadMonitoredItemFilterInvalid"] = 2151874560] = "BadMonitoredItemFilterInvalid";
259
- StatusCode2[StatusCode2["BadMonitoredItemFilterUnsupported"] = 2151940096] = "BadMonitoredItemFilterUnsupported";
260
- StatusCode2[StatusCode2["BadFilterNotAllowed"] = 2152005632] = "BadFilterNotAllowed";
261
- StatusCode2[StatusCode2["BadStructureMissing"] = 2152071168] = "BadStructureMissing";
262
- StatusCode2[StatusCode2["BadEventFilterInvalid"] = 2152136704] = "BadEventFilterInvalid";
263
- StatusCode2[StatusCode2["BadContentFilterInvalid"] = 2152202240] = "BadContentFilterInvalid";
264
- StatusCode2[StatusCode2["BadFilterOperatorInvalid"] = 2160132096] = "BadFilterOperatorInvalid";
265
- StatusCode2[StatusCode2["BadFilterOperatorUnsupported"] = 2160197632] = "BadFilterOperatorUnsupported";
266
- StatusCode2[StatusCode2["BadFilterOperandCountMismatch"] = 2160263168] = "BadFilterOperandCountMismatch";
267
- StatusCode2[StatusCode2["BadFilterOperandInvalid"] = 2152267776] = "BadFilterOperandInvalid";
268
- StatusCode2[StatusCode2["BadFilterElementInvalid"] = 2160328704] = "BadFilterElementInvalid";
269
- StatusCode2[StatusCode2["BadFilterLiteralInvalid"] = 2160394240] = "BadFilterLiteralInvalid";
270
- StatusCode2[StatusCode2["BadContinuationPointInvalid"] = 2152333312] = "BadContinuationPointInvalid";
271
- StatusCode2[StatusCode2["BadNoContinuationPoints"] = 2152398848] = "BadNoContinuationPoints";
272
- StatusCode2[StatusCode2["BadReferenceTypeIdInvalid"] = 2152464384] = "BadReferenceTypeIdInvalid";
273
- StatusCode2[StatusCode2["BadBrowseDirectionInvalid"] = 2152529920] = "BadBrowseDirectionInvalid";
274
- StatusCode2[StatusCode2["BadNodeNotInView"] = 2152595456] = "BadNodeNotInView";
275
- StatusCode2[StatusCode2["BadNumericOverflow"] = 2165440512] = "BadNumericOverflow";
276
- StatusCode2[StatusCode2["BadServerUriInvalid"] = 2152660992] = "BadServerUriInvalid";
277
- StatusCode2[StatusCode2["BadServerNameMissing"] = 2152726528] = "BadServerNameMissing";
278
- StatusCode2[StatusCode2["BadDiscoveryUrlMissing"] = 2152792064] = "BadDiscoveryUrlMissing";
279
- StatusCode2[StatusCode2["BadSempahoreFileMissing"] = 2152857600] = "BadSempahoreFileMissing";
280
- StatusCode2[StatusCode2["BadRequestTypeInvalid"] = 2152923136] = "BadRequestTypeInvalid";
281
- StatusCode2[StatusCode2["BadSecurityModeRejected"] = 2152988672] = "BadSecurityModeRejected";
282
- StatusCode2[StatusCode2["BadSecurityPolicyRejected"] = 2153054208] = "BadSecurityPolicyRejected";
283
- StatusCode2[StatusCode2["BadTooManySessions"] = 2153119744] = "BadTooManySessions";
284
- StatusCode2[StatusCode2["BadUserSignatureInvalid"] = 2153185280] = "BadUserSignatureInvalid";
285
- StatusCode2[StatusCode2["BadApplicationSignatureInvalid"] = 2153250816] = "BadApplicationSignatureInvalid";
286
- StatusCode2[StatusCode2["BadNoValidCertificates"] = 2153316352] = "BadNoValidCertificates";
287
- StatusCode2[StatusCode2["BadIdentityChangeNotSupported"] = 2160459776] = "BadIdentityChangeNotSupported";
288
- StatusCode2[StatusCode2["BadRequestCancelledByRequest"] = 2153381888] = "BadRequestCancelledByRequest";
289
- StatusCode2[StatusCode2["BadParentNodeIdInvalid"] = 2153447424] = "BadParentNodeIdInvalid";
290
- StatusCode2[StatusCode2["BadReferenceNotAllowed"] = 2153512960] = "BadReferenceNotAllowed";
291
- StatusCode2[StatusCode2["BadNodeIdRejected"] = 2153578496] = "BadNodeIdRejected";
292
- StatusCode2[StatusCode2["BadNodeIdExists"] = 2153644032] = "BadNodeIdExists";
293
- StatusCode2[StatusCode2["BadNodeClassInvalid"] = 2153709568] = "BadNodeClassInvalid";
294
- StatusCode2[StatusCode2["BadBrowseNameInvalid"] = 2153775104] = "BadBrowseNameInvalid";
295
- StatusCode2[StatusCode2["BadBrowseNameDuplicated"] = 2153840640] = "BadBrowseNameDuplicated";
296
- StatusCode2[StatusCode2["BadNodeAttributesInvalid"] = 2153906176] = "BadNodeAttributesInvalid";
297
- StatusCode2[StatusCode2["BadTypeDefinitionInvalid"] = 2153971712] = "BadTypeDefinitionInvalid";
298
- StatusCode2[StatusCode2["BadSourceNodeIdInvalid"] = 2154037248] = "BadSourceNodeIdInvalid";
299
- StatusCode2[StatusCode2["BadTargetNodeIdInvalid"] = 2154102784] = "BadTargetNodeIdInvalid";
300
- StatusCode2[StatusCode2["BadDuplicateReferenceNotAllowed"] = 2154168320] = "BadDuplicateReferenceNotAllowed";
301
- StatusCode2[StatusCode2["BadInvalidSelfReference"] = 2154233856] = "BadInvalidSelfReference";
302
- StatusCode2[StatusCode2["BadReferenceLocalOnly"] = 2154299392] = "BadReferenceLocalOnly";
303
- StatusCode2[StatusCode2["BadNoDeleteRights"] = 2154364928] = "BadNoDeleteRights";
304
- StatusCode2[StatusCode2["UncertainReferenceNotDeleted"] = 1086062592] = "UncertainReferenceNotDeleted";
305
- StatusCode2[StatusCode2["BadServerIndexInvalid"] = 2154430464] = "BadServerIndexInvalid";
306
- StatusCode2[StatusCode2["BadViewIdUnknown"] = 2154496e3] = "BadViewIdUnknown";
307
- StatusCode2[StatusCode2["BadViewTimestampInvalid"] = 2160656384] = "BadViewTimestampInvalid";
308
- StatusCode2[StatusCode2["BadViewParameterMismatch"] = 2160721920] = "BadViewParameterMismatch";
309
- StatusCode2[StatusCode2["BadViewVersionInvalid"] = 2160787456] = "BadViewVersionInvalid";
310
- StatusCode2[StatusCode2["UncertainNotAllNodesAvailable"] = 1086324736] = "UncertainNotAllNodesAvailable";
311
- StatusCode2[StatusCode2["GoodResultsMayBeIncomplete"] = 12189696] = "GoodResultsMayBeIncomplete";
312
- StatusCode2[StatusCode2["BadNotTypeDefinition"] = 2160590848] = "BadNotTypeDefinition";
313
- StatusCode2[StatusCode2["UncertainReferenceOutOfServer"] = 1080819712] = "UncertainReferenceOutOfServer";
314
- StatusCode2[StatusCode2["BadTooManyMatches"] = 2154627072] = "BadTooManyMatches";
315
- StatusCode2[StatusCode2["BadQueryTooComplex"] = 2154692608] = "BadQueryTooComplex";
316
- StatusCode2[StatusCode2["BadNoMatch"] = 2154758144] = "BadNoMatch";
317
- StatusCode2[StatusCode2["BadMaxAgeInvalid"] = 2154823680] = "BadMaxAgeInvalid";
318
- StatusCode2[StatusCode2["BadSecurityModeInsufficient"] = 2162556928] = "BadSecurityModeInsufficient";
319
- StatusCode2[StatusCode2["BadHistoryOperationInvalid"] = 2154889216] = "BadHistoryOperationInvalid";
320
- StatusCode2[StatusCode2["BadHistoryOperationUnsupported"] = 2154954752] = "BadHistoryOperationUnsupported";
321
- StatusCode2[StatusCode2["BadInvalidTimestampArgument"] = 2159869952] = "BadInvalidTimestampArgument";
322
- StatusCode2[StatusCode2["BadWriteNotSupported"] = 2155020288] = "BadWriteNotSupported";
323
- StatusCode2[StatusCode2["BadTypeMismatch"] = 2155085824] = "BadTypeMismatch";
324
- StatusCode2[StatusCode2["BadMethodInvalid"] = 2155151360] = "BadMethodInvalid";
325
- StatusCode2[StatusCode2["BadArgumentsMissing"] = 2155216896] = "BadArgumentsMissing";
326
- StatusCode2[StatusCode2["BadNotExecutable"] = 2165374976] = "BadNotExecutable";
327
- StatusCode2[StatusCode2["BadTooManySubscriptions"] = 2155282432] = "BadTooManySubscriptions";
328
- StatusCode2[StatusCode2["BadTooManyPublishRequests"] = 2155347968] = "BadTooManyPublishRequests";
329
- StatusCode2[StatusCode2["BadNoSubscription"] = 2155413504] = "BadNoSubscription";
330
- StatusCode2[StatusCode2["BadSequenceNumberUnknown"] = 2155479040] = "BadSequenceNumberUnknown";
331
- StatusCode2[StatusCode2["BadMessageNotAvailable"] = 2155544576] = "BadMessageNotAvailable";
332
- StatusCode2[StatusCode2["BadInsufficientClientProfile"] = 2155610112] = "BadInsufficientClientProfile";
333
- StatusCode2[StatusCode2["BadStateNotActive"] = 2160001024] = "BadStateNotActive";
334
- StatusCode2[StatusCode2["BadAlreadyExists"] = 2165637120] = "BadAlreadyExists";
335
- StatusCode2[StatusCode2["BadTcpServerTooBusy"] = 2155675648] = "BadTcpServerTooBusy";
336
- StatusCode2[StatusCode2["BadTcpMessageTypeInvalid"] = 2155741184] = "BadTcpMessageTypeInvalid";
337
- StatusCode2[StatusCode2["BadTcpSecureChannelUnknown"] = 2155806720] = "BadTcpSecureChannelUnknown";
338
- StatusCode2[StatusCode2["BadTcpMessageTooLarge"] = 2155872256] = "BadTcpMessageTooLarge";
339
- StatusCode2[StatusCode2["BadTcpNotEnoughResources"] = 2155937792] = "BadTcpNotEnoughResources";
340
- StatusCode2[StatusCode2["BadTcpInternalError"] = 2156003328] = "BadTcpInternalError";
341
- StatusCode2[StatusCode2["BadTcpEndpointUrlInvalid"] = 2156068864] = "BadTcpEndpointUrlInvalid";
342
- StatusCode2[StatusCode2["BadRequestInterrupted"] = 2156134400] = "BadRequestInterrupted";
343
- StatusCode2[StatusCode2["BadRequestTimeout"] = 2156199936] = "BadRequestTimeout";
344
- StatusCode2[StatusCode2["BadSecureChannelClosed"] = 2156265472] = "BadSecureChannelClosed";
345
- StatusCode2[StatusCode2["BadSecureChannelTokenUnknown"] = 2156331008] = "BadSecureChannelTokenUnknown";
346
- StatusCode2[StatusCode2["BadSequenceNumberInvalid"] = 2156396544] = "BadSequenceNumberInvalid";
347
- StatusCode2[StatusCode2["BadProtocolVersionUnsupported"] = 2159935488] = "BadProtocolVersionUnsupported";
348
- StatusCode2[StatusCode2["BadConfigurationError"] = 2156462080] = "BadConfigurationError";
349
- StatusCode2[StatusCode2["BadNotConnected"] = 2156527616] = "BadNotConnected";
350
- StatusCode2[StatusCode2["BadDeviceFailure"] = 2156593152] = "BadDeviceFailure";
351
- StatusCode2[StatusCode2["BadSensorFailure"] = 2156658688] = "BadSensorFailure";
352
- StatusCode2[StatusCode2["BadOutOfService"] = 2156724224] = "BadOutOfService";
353
- StatusCode2[StatusCode2["BadDeadbandFilterInvalid"] = 2156789760] = "BadDeadbandFilterInvalid";
354
- StatusCode2[StatusCode2["UncertainNoCommunicationLastUsableValue"] = 1083113472] = "UncertainNoCommunicationLastUsableValue";
355
- StatusCode2[StatusCode2["UncertainLastUsableValue"] = 1083179008] = "UncertainLastUsableValue";
356
- StatusCode2[StatusCode2["UncertainSubstituteValue"] = 1083244544] = "UncertainSubstituteValue";
357
- StatusCode2[StatusCode2["UncertainInitialValue"] = 1083310080] = "UncertainInitialValue";
358
- StatusCode2[StatusCode2["UncertainSensorNotAccurate"] = 1083375616] = "UncertainSensorNotAccurate";
359
- StatusCode2[StatusCode2["UncertainEngineeringUnitsExceeded"] = 1083441152] = "UncertainEngineeringUnitsExceeded";
360
- StatusCode2[StatusCode2["UncertainSubNormal"] = 1083506688] = "UncertainSubNormal";
361
- StatusCode2[StatusCode2["GoodLocalOverride"] = 9830400] = "GoodLocalOverride";
362
- StatusCode2[StatusCode2["BadRefreshInProgress"] = 2157379584] = "BadRefreshInProgress";
363
- StatusCode2[StatusCode2["BadConditionAlreadyDisabled"] = 2157445120] = "BadConditionAlreadyDisabled";
364
- StatusCode2[StatusCode2["BadConditionAlreadyEnabled"] = 2160852992] = "BadConditionAlreadyEnabled";
365
- StatusCode2[StatusCode2["BadConditionDisabled"] = 2157510656] = "BadConditionDisabled";
366
- StatusCode2[StatusCode2["BadEventIdUnknown"] = 2157576192] = "BadEventIdUnknown";
367
- StatusCode2[StatusCode2["BadEventNotAcknowledgeable"] = 2159738880] = "BadEventNotAcknowledgeable";
368
- StatusCode2[StatusCode2["BadDialogNotActive"] = 2160918528] = "BadDialogNotActive";
369
- StatusCode2[StatusCode2["BadDialogResponseInvalid"] = 2160984064] = "BadDialogResponseInvalid";
370
- StatusCode2[StatusCode2["BadConditionBranchAlreadyAcked"] = 2161049600] = "BadConditionBranchAlreadyAcked";
371
- StatusCode2[StatusCode2["BadConditionBranchAlreadyConfirmed"] = 2161115136] = "BadConditionBranchAlreadyConfirmed";
372
- StatusCode2[StatusCode2["BadConditionAlreadyShelved"] = 2161180672] = "BadConditionAlreadyShelved";
373
- StatusCode2[StatusCode2["BadConditionNotShelved"] = 2161246208] = "BadConditionNotShelved";
374
- StatusCode2[StatusCode2["BadShelvingTimeOutOfRange"] = 2161311744] = "BadShelvingTimeOutOfRange";
375
- StatusCode2[StatusCode2["BadNoData"] = 2157641728] = "BadNoData";
376
- StatusCode2[StatusCode2["BadBoundNotFound"] = 2161573888] = "BadBoundNotFound";
377
- StatusCode2[StatusCode2["BadBoundNotSupported"] = 2161639424] = "BadBoundNotSupported";
378
- StatusCode2[StatusCode2["BadDataLost"] = 2157772800] = "BadDataLost";
379
- StatusCode2[StatusCode2["BadDataUnavailable"] = 2157838336] = "BadDataUnavailable";
380
- StatusCode2[StatusCode2["BadEntryExists"] = 2157903872] = "BadEntryExists";
381
- StatusCode2[StatusCode2["BadNoEntryExists"] = 2157969408] = "BadNoEntryExists";
382
- StatusCode2[StatusCode2["BadTimestampNotSupported"] = 2158034944] = "BadTimestampNotSupported";
383
- StatusCode2[StatusCode2["GoodEntryInserted"] = 10616832] = "GoodEntryInserted";
384
- StatusCode2[StatusCode2["GoodEntryReplaced"] = 10682368] = "GoodEntryReplaced";
385
- StatusCode2[StatusCode2["UncertainDataSubNormal"] = 1084489728] = "UncertainDataSubNormal";
386
- StatusCode2[StatusCode2["GoodNoData"] = 10813440] = "GoodNoData";
387
- StatusCode2[StatusCode2["GoodMoreData"] = 10878976] = "GoodMoreData";
388
- StatusCode2[StatusCode2["BadAggregateListMismatch"] = 2161377280] = "BadAggregateListMismatch";
389
- StatusCode2[StatusCode2["BadAggregateNotSupported"] = 2161442816] = "BadAggregateNotSupported";
390
- StatusCode2[StatusCode2["BadAggregateInvalidInputs"] = 2161508352] = "BadAggregateInvalidInputs";
391
- StatusCode2[StatusCode2["BadAggregateConfigurationRejected"] = 2161770496] = "BadAggregateConfigurationRejected";
392
- StatusCode2[StatusCode2["GoodDataIgnored"] = 14221312] = "GoodDataIgnored";
393
- StatusCode2[StatusCode2["BadRequestNotAllowed"] = 2162425856] = "BadRequestNotAllowed";
394
- StatusCode2[StatusCode2["BadRequestNotComplete"] = 2165506048] = "BadRequestNotComplete";
395
- StatusCode2[StatusCode2["GoodEdited"] = 14417920] = "GoodEdited";
396
- StatusCode2[StatusCode2["GoodPostActionFailed"] = 14483456] = "GoodPostActionFailed";
397
- StatusCode2[StatusCode2["UncertainDominantValueChanged"] = 1088290816] = "UncertainDominantValueChanged";
398
- StatusCode2[StatusCode2["GoodDependentValueChanged"] = 14680064] = "GoodDependentValueChanged";
399
- StatusCode2[StatusCode2["BadDominantValueChanged"] = 2162229248] = "BadDominantValueChanged";
400
- StatusCode2[StatusCode2["UncertainDependentValueChanged"] = 1088552960] = "UncertainDependentValueChanged";
401
- StatusCode2[StatusCode2["BadDependentValueChanged"] = 2162360320] = "BadDependentValueChanged";
402
- StatusCode2[StatusCode2["GoodEditedDependentValueChanged"] = 18219008] = "GoodEditedDependentValueChanged";
403
- StatusCode2[StatusCode2["GoodEditedDominantValueChanged"] = 18284544] = "GoodEditedDominantValueChanged";
404
- StatusCode2[StatusCode2["GoodEditedDominantValueChangedDependentValueChanged"] = 18350080] = "GoodEditedDominantValueChangedDependentValueChanged";
405
- StatusCode2[StatusCode2["BadEditedOutOfRange"] = 2165899264] = "BadEditedOutOfRange";
406
- StatusCode2[StatusCode2["BadInitialValueOutOfRange"] = 2165964800] = "BadInitialValueOutOfRange";
407
- StatusCode2[StatusCode2["BadOutOfRangeDominantValueChanged"] = 2166030336] = "BadOutOfRangeDominantValueChanged";
408
- StatusCode2[StatusCode2["BadEditedOutOfRangeDominantValueChanged"] = 2166095872] = "BadEditedOutOfRangeDominantValueChanged";
409
- StatusCode2[StatusCode2["BadOutOfRangeDominantValueChangedDependentValueChanged"] = 2166161408] = "BadOutOfRangeDominantValueChangedDependentValueChanged";
410
- StatusCode2[StatusCode2["BadEditedOutOfRangeDominantValueChangedDependentValueChanged"] = 2166226944] = "BadEditedOutOfRangeDominantValueChangedDependentValueChanged";
411
- StatusCode2[StatusCode2["GoodCommunicationEvent"] = 10944512] = "GoodCommunicationEvent";
412
- StatusCode2[StatusCode2["GoodShutdownEvent"] = 11010048] = "GoodShutdownEvent";
413
- StatusCode2[StatusCode2["GoodCallAgain"] = 11075584] = "GoodCallAgain";
414
- StatusCode2[StatusCode2["GoodNonCriticalTimeout"] = 11141120] = "GoodNonCriticalTimeout";
415
- StatusCode2[StatusCode2["BadInvalidArgument"] = 2158690304] = "BadInvalidArgument";
416
- StatusCode2[StatusCode2["BadConnectionRejected"] = 2158755840] = "BadConnectionRejected";
417
- StatusCode2[StatusCode2["BadDisconnect"] = 2158821376] = "BadDisconnect";
418
- StatusCode2[StatusCode2["BadConnectionClosed"] = 2158886912] = "BadConnectionClosed";
419
- StatusCode2[StatusCode2["BadInvalidState"] = 2158952448] = "BadInvalidState";
420
- StatusCode2[StatusCode2["BadEndOfStream"] = 2159017984] = "BadEndOfStream";
421
- StatusCode2[StatusCode2["BadNoDataAvailable"] = 2159083520] = "BadNoDataAvailable";
422
- StatusCode2[StatusCode2["BadWaitingForResponse"] = 2159149056] = "BadWaitingForResponse";
423
- StatusCode2[StatusCode2["BadOperationAbandoned"] = 2159214592] = "BadOperationAbandoned";
424
- StatusCode2[StatusCode2["BadExpectedStreamToBlock"] = 2159280128] = "BadExpectedStreamToBlock";
425
- StatusCode2[StatusCode2["BadWouldBlock"] = 2159345664] = "BadWouldBlock";
426
- StatusCode2[StatusCode2["BadSyntaxError"] = 2159411200] = "BadSyntaxError";
427
- StatusCode2[StatusCode2["BadMaxConnectionsReached"] = 2159476736] = "BadMaxConnectionsReached";
428
- return StatusCode2;
429
- })(StatusCode || {});
430
-
431
215
  // src/services/attributeService.ts
432
216
  var AttributeService = class extends ServiceBase {
433
- async ReadValue(nodeIds) {
217
+ logger = getLogger("services.AttributeService");
218
+ /**
219
+ * Reads the Value attribute of one or more Nodes (OPC UA Part 4, Section 5.10.2).
220
+ * @param nodeIds - NodeIds of the Nodes to read.
221
+ * @param maxAge - Maximum age of the cached value in milliseconds the server may return. 0 = always current value.
222
+ * @param timestampsToReturn - Which timestamps to include in results. Default: Source.
223
+ * @returns Array of results containing value and raw status code number, one per requested NodeId.
224
+ */
225
+ async ReadValue(nodeIds, maxAge = 0, timestampsToReturn = TimestampsToReturnEnum.Source) {
434
226
  const readValueIds = nodeIds.map((ni) => {
435
227
  const readValueId = new ReadValueId();
436
228
  readValueId.nodeId = ni;
@@ -441,18 +233,18 @@ var AttributeService = class extends ServiceBase {
441
233
  });
442
234
  const request = new ReadRequest();
443
235
  request.requestHeader = this.createRequestHeader();
444
- request.maxAge = 6e4;
445
- request.timestampsToReturn = TimestampsToReturnEnum.Source;
236
+ request.maxAge = maxAge;
237
+ request.timestampsToReturn = timestampsToReturn;
446
238
  request.nodesToRead = readValueIds;
447
- console.log("Sending ReadRequest...");
239
+ this.logger.debug("Sending ReadRequest...");
448
240
  const response = await this.secureChannel.issueServiceRequest(request);
241
+ this.checkServiceResult(response.responseHeader?.serviceResult, "ReadRequest");
449
242
  const results = new Array();
450
243
  for (const dataValue of response.results ?? []) {
451
- const result = {
452
- status: StatusCodeToString(dataValue.statusCode),
244
+ results.push({
245
+ statusCode: dataValue.statusCode ?? StatusCode.Good,
453
246
  value: dataValue.value
454
- };
455
- results.push(result);
247
+ });
456
248
  }
457
249
  return results;
458
250
  }
@@ -463,9 +255,9 @@ var AttributeService = class extends ServiceBase {
463
255
 
464
256
  // src/readValueResult.ts
465
257
  var ReadValueResult = class {
466
- constructor(value, status) {
258
+ constructor(value, statusCode) {
467
259
  this.value = value;
468
- this.status = status;
260
+ this.statusCode = statusCode;
469
261
  }
470
262
  };
471
263
 
@@ -480,13 +272,17 @@ var SubscriptionHandlerEntry = class {
480
272
  };
481
273
 
482
274
  // src/subscriptionHandler.ts
275
+ var NODE_ID_DATA_CHANGE_NOTIFICATION = 811;
276
+ var NODE_ID_STATUS_CHANGE_NOTIFICATION = 818;
483
277
  var SubscriptionHandler = class {
484
278
  constructor(subscriptionService, monitoredItemService) {
485
279
  this.subscriptionService = subscriptionService;
486
280
  this.monitoredItemService = monitoredItemService;
487
281
  }
282
+ logger = getLogger("SubscriptionHandler");
488
283
  entries = new Array();
489
284
  nextHandle = 0;
285
+ isRunning = false;
490
286
  async subscribe(ids, callback) {
491
287
  if (this.entries.length > 0) {
492
288
  throw new Error("Subscribing more than once is not implemented");
@@ -494,80 +290,111 @@ var SubscriptionHandler = class {
494
290
  const subscriptionId = await this.subscriptionService.createSubscription();
495
291
  const items = [];
496
292
  for (const id of ids) {
497
- const entry = new SubscriptionHandlerEntry(
498
- subscriptionId,
499
- this.nextHandle++,
500
- id,
501
- callback
502
- );
293
+ const entry = new SubscriptionHandlerEntry(subscriptionId, this.nextHandle++, id, callback);
503
294
  this.entries.push(entry);
504
- const item = {
505
- id,
506
- handle: entry.handle
507
- };
508
- items.push(item);
295
+ items.push({ id, handle: entry.handle });
509
296
  }
510
297
  await this.monitoredItemService.createMonitoredItems(subscriptionId, items);
511
- this.publish([]);
512
- }
513
- async publish(acknowledgeSequenceNumbers) {
514
- const acknowledgements = [];
515
- for (let i = 0; i < acknowledgeSequenceNumbers.length; i++) {
516
- const acknowledgement = new SubscriptionAcknowledgement();
517
- acknowledgement.subscriptionId = this.entries[i].subscriptionId;
518
- acknowledgement.sequenceNumber = acknowledgeSequenceNumbers[i];
519
- acknowledgements.push(acknowledgement);
298
+ this.isRunning = true;
299
+ void this.publishLoop([]);
300
+ }
301
+ // https://reference.opcfoundation.org/Core/Part4/v105/docs/5.14.5
302
+ async publishLoop(pendingAcknowledgements) {
303
+ if (!this.isRunning) return;
304
+ let response;
305
+ try {
306
+ response = await this.subscriptionService.publish(pendingAcknowledgements);
307
+ } catch (err) {
308
+ this.logger.error(`Publish failed, stopping publish loop: ${err}`);
309
+ this.isRunning = false;
310
+ return;
311
+ }
312
+ const { subscriptionId, availableSequenceNumbers, moreNotifications, notificationMessage } = response;
313
+ const notificationDatas = notificationMessage?.notificationData ?? [];
314
+ const seqNumber = notificationMessage?.sequenceNumber;
315
+ const nextAcknowledgements = [];
316
+ const isKeepAlive = notificationDatas.length === 0;
317
+ if (!isKeepAlive && seqNumber !== void 0) {
318
+ const isAvailable = !availableSequenceNumbers || availableSequenceNumbers.includes(seqNumber);
319
+ if (isAvailable) {
320
+ const ack = new SubscriptionAcknowledgement();
321
+ ack.subscriptionId = subscriptionId;
322
+ ack.sequenceNumber = seqNumber;
323
+ nextAcknowledgements.push(ack);
324
+ }
520
325
  }
521
- const response = await this.subscriptionService.publish(acknowledgements);
522
- const messagesToAcknowledge = response.notificationMessage.sequenceNumber;
523
- const notificationDatas = response.notificationMessage.notificationData;
524
326
  for (const notificationData of notificationDatas) {
525
327
  const decodedData = notificationData.data;
526
328
  const typeNodeId = notificationData.typeId;
527
- if (typeNodeId.namespace === 0 && typeNodeId.identifier === 811) {
329
+ if (typeNodeId.namespace === 0 && typeNodeId.identifier === NODE_ID_DATA_CHANGE_NOTIFICATION) {
528
330
  const dataChangeNotification = decodedData;
529
331
  for (const item of dataChangeNotification.monitoredItems) {
530
- const clientHandle = item.clientHandle;
531
- const value = item.value;
532
- const entry = this.entries.find((e) => e.handle == clientHandle);
533
- entry?.callback([{
534
- id: entry.id,
535
- value: value.value?.value
536
- }]);
332
+ const entry = this.entries.find((e) => e.handle === item.clientHandle);
333
+ entry?.callback([{ id: entry.id, value: item.value.value?.value }]);
537
334
  }
335
+ } else if (typeNodeId.namespace === 0 && typeNodeId.identifier === NODE_ID_STATUS_CHANGE_NOTIFICATION) {
336
+ const statusChange = decodedData;
337
+ this.logger.warn(
338
+ `Subscription ${subscriptionId} status changed: 0x${statusChange.status?.toString(16).toUpperCase()}`
339
+ );
340
+ this.isRunning = false;
341
+ return;
538
342
  } else {
539
- console.log(`The change notification data type ${typeNodeId.namespace}:${typeNodeId.identifier} is not supported.`);
343
+ this.logger.warn(
344
+ `Notification data type ${typeNodeId.namespace}:${typeNodeId.identifier} is not supported.`
345
+ );
540
346
  }
541
347
  }
542
- setTimeout(() => {
543
- this.publish([messagesToAcknowledge]);
544
- }, 500);
348
+ if (moreNotifications) {
349
+ void this.publishLoop(nextAcknowledgements);
350
+ } else {
351
+ setTimeout(() => void this.publishLoop(nextAcknowledgements), 0);
352
+ }
545
353
  }
546
354
  };
547
355
  var SubscriptionService = class extends ServiceBase {
356
+ logger = getLogger("services.SubscriptionService");
357
+ /**
358
+ * Creates a new subscription on the server (OPC UA Part 4, Section 5.14.2).
359
+ * @param options - Optional subscription parameters; server revises all values.
360
+ * @returns The server-assigned subscription ID.
361
+ */
548
362
  // https://reference.opcfoundation.org/Core/Part4/v105/docs/5.14.2
549
- async createSubscription() {
363
+ async createSubscription(options) {
550
364
  const request = new CreateSubscriptionRequest();
551
365
  request.requestHeader = this.createRequestHeader();
552
- request.requestedPublishingInterval = 2e3;
553
- request.requestedLifetimeCount = 36e4;
554
- request.requestedMaxKeepAliveCount = 6e4;
555
- request.maxNotificationsPerPublish = 200;
366
+ request.requestedPublishingInterval = options?.requestedPublishingInterval ?? 2e3;
367
+ request.requestedLifetimeCount = options?.requestedLifetimeCount ?? 36e4;
368
+ request.requestedMaxKeepAliveCount = options?.requestedMaxKeepAliveCount ?? 6e4;
369
+ request.maxNotificationsPerPublish = options?.maxNotificationsPerPublish ?? 200;
556
370
  request.publishingEnabled = true;
557
- request.priority = 1;
558
- console.log("Sending createSubscription...");
371
+ request.priority = options?.priority ?? 1;
372
+ this.logger.debug("Sending CreateSubscriptionRequest...");
559
373
  const response = await this.secureChannel.issueServiceRequest(request);
560
- console.log("Subscription created with id:", response.subscriptionId);
374
+ const serviceResult = response.responseHeader?.serviceResult;
375
+ if (serviceResult !== void 0 && serviceResult !== StatusCode.Good) {
376
+ throw new Error(`CreateSubscriptionRequest failed: ${StatusCodeToString(serviceResult)}`);
377
+ }
378
+ this.logger.debug(`Subscription created with id: ${response.subscriptionId}`);
561
379
  return response.subscriptionId;
562
380
  }
381
+ /**
382
+ * Sends a publish request to receive notifications from active subscriptions (OPC UA Part 4, Section 5.14.5).
383
+ * @param acknowledgements - Sequence numbers to acknowledge from previous publish responses.
384
+ * @returns The publish response containing notification data.
385
+ */
563
386
  // https://reference.opcfoundation.org/Core/Part4/v105/docs/5.14.5
564
387
  async publish(acknowledgements) {
565
388
  const request = new PublishRequest();
566
389
  request.requestHeader = this.createRequestHeader();
567
390
  request.subscriptionAcknowledgements = acknowledgements;
568
- console.log("Sending publish...");
391
+ this.logger.debug("Sending PublishRequest...");
569
392
  const response = await this.secureChannel.issueServiceRequest(request);
570
- console.log("Received publish response.");
393
+ const serviceResult = response.responseHeader?.serviceResult;
394
+ if (serviceResult !== void 0 && serviceResult !== StatusCode.Good) {
395
+ throw new Error(`PublishRequest failed: ${StatusCodeToString(serviceResult)}`);
396
+ }
397
+ this.logger.debug("Received PublishResponse.");
571
398
  return response;
572
399
  }
573
400
  constructor(authToken, secureChannel) {
@@ -575,7 +402,15 @@ var SubscriptionService = class extends ServiceBase {
575
402
  }
576
403
  };
577
404
  var MonitoredItemService = class extends ServiceBase {
578
- async createMonitoredItems(subscriptionId, ids) {
405
+ logger = getLogger("services.MonitoredItemService");
406
+ /**
407
+ * Creates monitored items within a subscription (OPC UA Part 4, Section 5.13.2).
408
+ * @param subscriptionId - ID of the subscription to add monitored items to.
409
+ * @param ids - Array of NodeIds and client handles identifying the items to monitor.
410
+ * @param samplingInterval - Requested sampling interval in milliseconds. -1 = use subscription publishing interval.
411
+ * @param queueSize - Requested queue size for each monitored item.
412
+ */
413
+ async createMonitoredItems(subscriptionId, ids, samplingInterval = 1e3, queueSize = 100) {
579
414
  const items = ids.map((ni) => {
580
415
  const readValueId = new ReadValueId();
581
416
  readValueId.nodeId = ni.id;
@@ -584,9 +419,9 @@ var MonitoredItemService = class extends ServiceBase {
584
419
  readValueId.dataEncoding = new QualifiedName(0, "");
585
420
  const monitoringParameters = new MonitoringParameters();
586
421
  monitoringParameters.clientHandle = ni.handle;
587
- monitoringParameters.samplingInterval = 1e3;
422
+ monitoringParameters.samplingInterval = samplingInterval;
588
423
  monitoringParameters.filter = ExtensionObject.newEmpty();
589
- monitoringParameters.queueSize = 100;
424
+ monitoringParameters.queueSize = queueSize;
590
425
  monitoringParameters.discardOldest = true;
591
426
  const monitoredItemCreateRequest = new MonitoredItemCreateRequest();
592
427
  monitoredItemCreateRequest.itemToMonitor = readValueId;
@@ -599,26 +434,38 @@ var MonitoredItemService = class extends ServiceBase {
599
434
  request.subscriptionId = subscriptionId;
600
435
  request.timestampsToReturn = TimestampsToReturnEnum.Source;
601
436
  request.itemsToCreate = items;
602
- console.log("Sending createMonitoredItems...");
603
- await this.secureChannel.issueServiceRequest(request);
437
+ this.logger.debug("Sending CreateMonitoredItemsRequest...");
438
+ const response = await this.secureChannel.issueServiceRequest(request);
439
+ const serviceResult = response.responseHeader?.serviceResult;
440
+ if (serviceResult !== void 0 && serviceResult !== StatusCode.Good) {
441
+ throw new Error(`CreateMonitoredItemsRequest failed: ${StatusCodeToString(serviceResult)}`);
442
+ }
443
+ for (const result of response.results ?? []) {
444
+ if (result.statusCode !== void 0 && result.statusCode !== StatusCode.Good) {
445
+ this.logger.warn(`Failed to create monitored item: ${StatusCodeToString(result.statusCode)}`);
446
+ }
447
+ }
604
448
  }
605
449
  constructor(authToken, secureChannel) {
606
450
  super(authToken, secureChannel);
607
451
  }
608
452
  };
609
453
  var MethodService = class extends ServiceBase {
454
+ logger = getLogger("services.MethodService");
610
455
  /**
611
456
  * Calls one or more methods on the server (OPC UA Part 4, Section 5.11.2).
612
457
  * @param methodsToCall - Array of CallMethodRequest describing each method to invoke.
613
- * @returns Array of CallMethodResult, one per requested method.
458
+ * @returns Array of results containing output argument values and raw status code number, one per requested method.
614
459
  */
615
460
  async call(methodsToCall) {
616
461
  const request = new CallRequest();
617
462
  request.requestHeader = this.createRequestHeader();
618
463
  request.methodsToCall = methodsToCall;
464
+ this.logger.debug("Sending CallRequest...");
619
465
  const response = await this.secureChannel.issueServiceRequest(request);
466
+ this.checkServiceResult(response.responseHeader?.serviceResult, "CallRequest");
620
467
  return response.results.map((result) => ({
621
- status: StatusCodeToString(result.statusCode),
468
+ statusCode: result.statusCode ?? StatusCode.Good,
622
469
  value: result.outputArguments.map((arg) => arg.value)
623
470
  }));
624
471
  }
@@ -629,12 +476,18 @@ var MethodService = class extends ServiceBase {
629
476
 
630
477
  // src/callMethodResult.ts
631
478
  var CallMethodResult = class {
632
- constructor(values, status) {
479
+ constructor(values, statusCode) {
633
480
  this.values = values;
634
- this.status = status;
481
+ this.statusCode = statusCode;
635
482
  }
636
483
  };
637
484
  var BrowseService = class extends ServiceBase {
485
+ logger = getLogger("services.BrowseService");
486
+ /**
487
+ * Browses one or more Nodes and returns their References (OPC UA Part 4, Section 5.9.2).
488
+ * @param nodesToBrowse - Array of BrowseDescriptions specifying nodes and filter criteria.
489
+ * @returns Array of BrowseResult, one per requested node.
490
+ */
638
491
  async browse(nodesToBrowse) {
639
492
  const view = new ViewDescription();
640
493
  view.viewId = NodeId.newNumeric(0, 0);
@@ -645,15 +498,25 @@ var BrowseService = class extends ServiceBase {
645
498
  request.view = view;
646
499
  request.requestedMaxReferencesPerNode = 0;
647
500
  request.nodesToBrowse = nodesToBrowse;
501
+ this.logger.debug("Sending BrowseRequest...");
648
502
  const response = await this.secureChannel.issueServiceRequest(request);
503
+ this.checkServiceResult(response.responseHeader?.serviceResult, "BrowseRequest");
649
504
  return response.results ?? [];
650
505
  }
506
+ /**
507
+ * Continues a Browse operation using continuation points (OPC UA Part 4, Section 5.9.3).
508
+ * @param continuationPoints - Continuation points returned by a prior Browse or BrowseNext call.
509
+ * @param releaseContinuationPoints - If true, releases the continuation points without returning results.
510
+ * @returns Array of BrowseResult, one per continuation point.
511
+ */
651
512
  async browseNext(continuationPoints, releaseContinuationPoints) {
652
513
  const request = new BrowseNextRequest();
653
514
  request.requestHeader = this.createRequestHeader();
654
515
  request.releaseContinuationPoints = releaseContinuationPoints;
655
516
  request.continuationPoints = continuationPoints;
517
+ this.logger.debug("Sending BrowseNextRequest...");
656
518
  const response = await this.secureChannel.issueServiceRequest(request);
519
+ this.checkServiceResult(response.responseHeader?.serviceResult, "BrowseNextRequest");
657
520
  return response.results ?? [];
658
521
  }
659
522
  constructor(authToken, secureChannel) {
@@ -690,12 +553,50 @@ var Client = class {
690
553
  session;
691
554
  subscriptionHandler;
692
555
  logger;
556
+ // Stored after connect() so that refreshSession() can recreate services.
557
+ secureChannel;
558
+ sessionHandler;
693
559
  getSession() {
694
560
  if (!this.session) {
695
561
  throw new Error("No session available");
696
562
  }
697
563
  return this.session;
698
564
  }
565
+ /**
566
+ * (Re-)initialises all session-scoped services from the current `this.session`.
567
+ * Called both after the initial `connect()` and after a session refresh.
568
+ */
569
+ initServices() {
570
+ const authToken = this.session.getAuthToken();
571
+ const sc = this.secureChannel;
572
+ this.attributeService = new AttributeService(authToken, sc);
573
+ this.methodService = new MethodService(authToken, sc);
574
+ this.browseService = new BrowseService(authToken, sc);
575
+ this.subscriptionHandler = new SubscriptionHandler(
576
+ new SubscriptionService(authToken, sc),
577
+ new MonitoredItemService(authToken, sc)
578
+ );
579
+ }
580
+ /**
581
+ * Executes `fn` and, if it throws a `SessionInvalidError`, creates a fresh
582
+ * session and retries the operation exactly once.
583
+ *
584
+ * This covers the reactive case: a service call reveals that the server has
585
+ * already dropped the session (e.g. due to timeout). The new session is
586
+ * established transparently before re-running the original operation.
587
+ */
588
+ async withSessionRefresh(fn) {
589
+ try {
590
+ return await fn();
591
+ } catch (err) {
592
+ if (!(err instanceof SessionInvalidError)) throw err;
593
+ this.logger.info(`Session invalid (${err.statusCode.toString(16)}), refreshing session...`);
594
+ this.session = await this.sessionHandler.createNewSession(this.identity);
595
+ this.initServices();
596
+ this.logger.info("Session refreshed, retrying operation.");
597
+ return await fn();
598
+ }
599
+ }
699
600
  async connect() {
700
601
  const wsOptions = { endpoint: this.endpointUrl };
701
602
  const ws = new WebSocketFascade(wsOptions);
@@ -740,24 +641,21 @@ var Client = class {
740
641
  await sc.openSecureChannel();
741
642
  this.logger.debug("Secure channel established.");
742
643
  this.logger.debug("Creating session...");
743
- const sessionHandler = new SessionHandler(sc, this.configuration);
744
- this.session = await sessionHandler.createNewSession(this.identity);
644
+ this.sessionHandler = new SessionHandler(sc, this.configuration);
645
+ this.secureChannel = sc;
646
+ this.session = await this.sessionHandler.createNewSession(this.identity);
745
647
  this.logger.debug("Session created.");
746
648
  this.logger.debug("Initializing services...");
747
- this.attributeService = new AttributeService(this.session.getAuthToken(), sc);
748
- this.methodService = new MethodService(this.session.getAuthToken(), sc);
749
- this.browseService = new BrowseService(this.session.getAuthToken(), sc);
750
- this.subscriptionHandler = new SubscriptionHandler(
751
- new SubscriptionService(this.session.getAuthToken(), sc),
752
- new MonitoredItemService(this.session.getAuthToken(), sc)
753
- );
649
+ this.initServices();
754
650
  }
755
651
  async disconnect() {
756
652
  this.logger.info("Disconnecting from OPC UA server...");
757
653
  }
758
654
  async read(ids) {
759
- const result = await this.attributeService?.ReadValue(ids);
760
- return result?.map((r) => new ReadValueResult(r.value, r.status)) || [];
655
+ return this.withSessionRefresh(async () => {
656
+ const result = await this.attributeService?.ReadValue(ids);
657
+ return result?.map((r) => new ReadValueResult(r.value, r.statusCode)) ?? [];
658
+ });
761
659
  }
762
660
  /**
763
661
  * Method for calling a single method on the server.
@@ -767,17 +665,21 @@ var Client = class {
767
665
  * @returns The CallMethodResult for the invoked method.
768
666
  */
769
667
  async callMethod(objectId, methodId, inputArguments = []) {
770
- const request = new CallMethodRequest();
771
- request.objectId = objectId;
772
- request.methodId = methodId;
773
- request.inputArguments = inputArguments.map((arg) => Variant.newFrom(arg));
774
- const responses = await this.methodService.call([request]);
775
- const response = responses[0];
776
- return new CallMethodResult(response.value, response.status);
668
+ return this.withSessionRefresh(async () => {
669
+ const request = new CallMethodRequest();
670
+ request.objectId = objectId;
671
+ request.methodId = methodId;
672
+ request.inputArguments = inputArguments.map((arg) => Variant.newFrom(arg));
673
+ const responses = await this.methodService.call([request]);
674
+ const response = responses[0];
675
+ return new CallMethodResult(response.value, response.statusCode);
676
+ });
777
677
  }
778
678
  async browse(nodeId, recursive = false) {
779
- const visited = /* @__PURE__ */ new Set();
780
- return this.browseRecursive(nodeId, recursive, visited);
679
+ return this.withSessionRefresh(() => {
680
+ const visited = /* @__PURE__ */ new Set();
681
+ return this.browseRecursive(nodeId, recursive, visited);
682
+ });
781
683
  }
782
684
  async browseRecursive(nodeId, recursive, visited) {
783
685
  const nodeKey = `${nodeId.namespace}:${nodeId.identifier}`;
@@ -923,6 +825,6 @@ var UserIdentity = class _UserIdentity {
923
825
  }
924
826
  };
925
827
 
926
- export { BrowseNodeResult, Client, ConfigurationClient, UserIdentity };
828
+ export { BrowseNodeResult, Client, ConfigurationClient, SessionInvalidError, UserIdentity };
927
829
  //# sourceMappingURL=index.js.map
928
830
  //# sourceMappingURL=index.js.map