opcjs-client 0.1.29-alpha → 0.1.32-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 +282 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +164 -2
- package/dist/index.d.ts +164 -2
- package/dist/index.js +283 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StatusCodeToString, initLoggerProvider, getLogger, WebSocketFascade, WebSocketReadableStream, WebSocketWritableStream, SecureChannelContext, TcpMessageInjector, TcpConnectionHandler, TcpMessageDecoupler,
|
|
1
|
+
import { NodeId, StatusCodeToString, initLoggerProvider, getLogger, WebSocketFascade, WebSocketReadableStream, WebSocketWritableStream, SecureChannelContext, TcpMessageInjector, TcpConnectionHandler, TcpMessageDecoupler, SecureChannelMessageEncoder, SecureChannelTypeDecoder, SecureChannelMessageDecoder, SecureChannelTypeEncoder, SecureChannelChunkWriter, SecureChannelChunkReader, SecureChannelFacade, CallMethodRequest, Variant, BrowseDescription, BrowseDirectionEnum, BrowseResultMaskEnum, Configuration, LoggerFactory, Encoder, BinaryWriter, registerEncoders, Decoder, BinaryReader, registerTypeDecoders, registerBinaryDecoders, UserTokenTypeEnum, AnonymousIdentityToken, UserNameIdentityToken, IssuedIdentityToken, TimestampsToReturnEnum, ReadValueId, QualifiedName, ReadRequest, StatusCode, CallRequest, ViewDescription, BrowseRequest, BrowseNextRequest, SubscriptionAcknowledgement, ExpandedNodeId, CreateSubscriptionRequest, PublishRequest, MonitoringParameters, ExtensionObject, MonitoredItemCreateRequest, MonitoringModeEnum, CreateMonitoredItemsRequest, StatusCodeToStringNumber, RequestHeader, ApplicationDescription, LocalizedText, ApplicationTypeEnum, CreateSessionRequest, CreateSessionResponse, SignatureData, ActivateSessionRequest, CloseSessionRequest } from 'opcjs-base';
|
|
2
2
|
|
|
3
3
|
// src/client.ts
|
|
4
4
|
var SessionInvalidError = class extends Error {
|
|
@@ -90,17 +90,23 @@ var SessionService = class _SessionService extends ServiceBase {
|
|
|
90
90
|
if (serviceResult !== void 0 && serviceResult !== StatusCode.Good) {
|
|
91
91
|
throw new Error(`CreateSessionRequest failed: ${StatusCodeToString(serviceResult)}`);
|
|
92
92
|
}
|
|
93
|
-
const
|
|
94
|
-
const endpointUrl = new URL(endpoint);
|
|
93
|
+
const clientConnectionUrl = new URL("opc." + this.secureChannel.getEndpointUrl());
|
|
95
94
|
const securityMode = this.secureChannel.getSecurityMode();
|
|
96
95
|
const securityPolicyUri = this.secureChannel.getSecurityPolicy();
|
|
96
|
+
const normalizeEndpointUrl = (serverEndpointUrl) => {
|
|
97
|
+
const url = new URL(serverEndpointUrl);
|
|
98
|
+
url.hostname = clientConnectionUrl.hostname;
|
|
99
|
+
url.port = clientConnectionUrl.port;
|
|
100
|
+
return url;
|
|
101
|
+
};
|
|
97
102
|
const serverEndpoint = castedResponse?.serverEndpoints?.find((currentEndpoint) => {
|
|
98
|
-
const
|
|
99
|
-
return
|
|
103
|
+
const normalized = normalizeEndpointUrl(currentEndpoint.endpointUrl);
|
|
104
|
+
return normalized.protocol === clientConnectionUrl.protocol && normalized.pathname === clientConnectionUrl.pathname && currentEndpoint.securityMode === securityMode && currentEndpoint.securityPolicyUri === securityPolicyUri;
|
|
100
105
|
});
|
|
101
106
|
if (!serverEndpoint) {
|
|
102
|
-
throw new Error(`Server endpoint ${
|
|
107
|
+
throw new Error(`Server endpoint ${clientConnectionUrl.toString()} not found in CreateSessionResponse`);
|
|
103
108
|
}
|
|
109
|
+
serverEndpoint.endpointUrl = normalizeEndpointUrl(serverEndpoint.endpointUrl).toString();
|
|
104
110
|
this.logger.debug("Session created with id:", castedResponse.sessionId.identifier);
|
|
105
111
|
return {
|
|
106
112
|
sessionId: castedResponse.sessionId.identifier,
|
|
@@ -131,6 +137,24 @@ var SessionService = class _SessionService extends ServiceBase {
|
|
|
131
137
|
}
|
|
132
138
|
this.logger.debug("Session activated.");
|
|
133
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Closes the current session on the server (OPC UA Part 4, Section 5.7.4).
|
|
142
|
+
* @param deleteSubscriptions - When true the server deletes all Subscriptions
|
|
143
|
+
* associated with this Session, freeing their resources immediately.
|
|
144
|
+
* Pass false to keep Subscriptions alive for transfer to another Session.
|
|
145
|
+
*/
|
|
146
|
+
async closeSession(deleteSubscriptions) {
|
|
147
|
+
this.logger.debug("Sending CloseSessionRequest...");
|
|
148
|
+
const request = new CloseSessionRequest();
|
|
149
|
+
request.requestHeader = this.createRequestHeader();
|
|
150
|
+
request.deleteSubscriptions = deleteSubscriptions;
|
|
151
|
+
const response = await this.secureChannel.issueServiceRequest(request);
|
|
152
|
+
const result = response?.responseHeader?.serviceResult;
|
|
153
|
+
if (result !== void 0 && result !== StatusCode.Good) {
|
|
154
|
+
throw new Error(`CloseSession failed: ${StatusCodeToString(result)}`);
|
|
155
|
+
}
|
|
156
|
+
this.logger.debug("Session closed.");
|
|
157
|
+
}
|
|
134
158
|
recreate(authToken) {
|
|
135
159
|
return new _SessionService(authToken, this.secureChannel, this.configuration);
|
|
136
160
|
}
|
|
@@ -192,23 +216,97 @@ var Session = class {
|
|
|
192
216
|
getAuthToken() {
|
|
193
217
|
return this.authToken;
|
|
194
218
|
}
|
|
219
|
+
getSessionId() {
|
|
220
|
+
return this.sessionId;
|
|
221
|
+
}
|
|
222
|
+
getEndpoint() {
|
|
223
|
+
return this.endpoint;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Closes the session on the server (OPC UA Part 4, Section 5.7.4).
|
|
227
|
+
* @param deleteSubscriptions - When true the server deletes all Subscriptions
|
|
228
|
+
* tied to this Session. Defaults to true.
|
|
229
|
+
*/
|
|
230
|
+
async close(deleteSubscriptions = true) {
|
|
231
|
+
await this.sessionServices.closeSession(deleteSubscriptions);
|
|
232
|
+
}
|
|
195
233
|
};
|
|
196
234
|
|
|
197
235
|
// src/sessions/sessionHandler.ts
|
|
198
236
|
var SessionHandler = class {
|
|
237
|
+
constructor(secureChannel, configuration) {
|
|
238
|
+
this.configuration = configuration;
|
|
239
|
+
this.sessionServices = new SessionService(NodeId.newTwoByte(0), secureChannel, configuration);
|
|
240
|
+
}
|
|
199
241
|
sessionServices;
|
|
242
|
+
logger = getLogger("sessions.SessionHandler");
|
|
200
243
|
async createNewSession(identity) {
|
|
201
244
|
const ret = await this.sessionServices.createSession();
|
|
202
245
|
this.sessionServices = this.sessionServices.recreate(ret.authToken);
|
|
203
246
|
const session = new Session(ret.sessionId, ret.authToken, ret.endpoint, this.sessionServices);
|
|
247
|
+
this.validateUserTokenPolicy(identity, ret.endpoint);
|
|
204
248
|
await session.activateSession(identity);
|
|
205
249
|
return session;
|
|
206
250
|
}
|
|
207
|
-
|
|
208
|
-
|
|
251
|
+
/**
|
|
252
|
+
* Attempts to reactivate an existing OPC UA session on the current (new) SecureChannel
|
|
253
|
+
* without calling CreateSession first (OPC UA Part 4, Section 5.7.3).
|
|
254
|
+
*
|
|
255
|
+
* This is the preferred recovery path when the SecureChannel drops but the server-side
|
|
256
|
+
* session has not yet timed out: only a new channel is needed, not a new session.
|
|
257
|
+
*
|
|
258
|
+
* @returns The reactivated Session if ActivateSession succeeded, or `null` if the server
|
|
259
|
+
* rejected the request (e.g. the session had already expired).
|
|
260
|
+
*/
|
|
261
|
+
async tryActivateExistingSession(existingAuthToken, existingSessionId, existingEndpoint, identity) {
|
|
262
|
+
const serviceForExistingSession = this.sessionServices.recreate(existingAuthToken);
|
|
263
|
+
try {
|
|
264
|
+
const session = new Session(existingSessionId, existingAuthToken, existingEndpoint, serviceForExistingSession);
|
|
265
|
+
await session.activateSession(identity);
|
|
266
|
+
return session;
|
|
267
|
+
} catch (err) {
|
|
268
|
+
this.logger.debug("ActivateSession for existing session failed:", err);
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Closes the active session on the server (OPC UA Part 4, Section 5.7.4).
|
|
274
|
+
* @param deleteSubscriptions - Forwarded to CloseSessionRequest. Defaults to true.
|
|
275
|
+
*/
|
|
276
|
+
async closeSession(deleteSubscriptions = true) {
|
|
277
|
+
await this.sessionServices.closeSession(deleteSubscriptions);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Validates the requested user-identity token type against:
|
|
281
|
+
* 1. The `allowedUserTokenTypes` from the client security configuration — the
|
|
282
|
+
* client has explicitly restricted which token types it will use.
|
|
283
|
+
* 2. The token policies advertised by the server endpoint — verifies that the
|
|
284
|
+
* server actually supports at least one of the allowed types.
|
|
285
|
+
*
|
|
286
|
+
* Throws with a descriptive message if either check fails.
|
|
287
|
+
*/
|
|
288
|
+
validateUserTokenPolicy(identity, endpoint) {
|
|
289
|
+
const allowedTypes = this.configuration.securityConfiguration?.allowedUserTokenTypes;
|
|
290
|
+
if (!allowedTypes) return;
|
|
291
|
+
const requestedType = identity.getTokenType();
|
|
292
|
+
if (!allowedTypes.includes(requestedType)) {
|
|
293
|
+
throw new Error(
|
|
294
|
+
`User token type '${UserTokenTypeEnum[requestedType]}' is not permitted by the client security configuration. Allowed types: ${allowedTypes.map((t) => UserTokenTypeEnum[t]).join(", ")}.`
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
const serverTypes = endpoint.userIdentityTokens?.map((p) => p.tokenType) ?? [];
|
|
298
|
+
const intersection = allowedTypes.filter((t) => serverTypes.includes(t));
|
|
299
|
+
if (intersection.length === 0) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`Server endpoint does not offer any user token type from the allowed list: ${allowedTypes.map((t) => UserTokenTypeEnum[t]).join(", ")}. Server offers: ${serverTypes.map((t) => UserTokenTypeEnum[t]).join(", ")}.`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
209
304
|
}
|
|
210
305
|
};
|
|
211
306
|
|
|
307
|
+
// src/securityConfiguration.ts
|
|
308
|
+
var SECURITY_POLICY_NONE_URI = "http://opcfoundation.org/UA/SecurityPolicy#None";
|
|
309
|
+
|
|
212
310
|
// src/services/attributeServiceAttributes.ts
|
|
213
311
|
var AttrIdValue = 13;
|
|
214
312
|
|
|
@@ -283,6 +381,10 @@ var SubscriptionHandler = class {
|
|
|
283
381
|
entries = new Array();
|
|
284
382
|
nextHandle = 0;
|
|
285
383
|
isRunning = false;
|
|
384
|
+
/** Returns true when at least one subscription is active and the publish loop is running. */
|
|
385
|
+
hasActiveSubscription() {
|
|
386
|
+
return this.isRunning && this.entries.length > 0;
|
|
387
|
+
}
|
|
286
388
|
async subscribe(ids, callback) {
|
|
287
389
|
if (this.entries.length > 0) {
|
|
288
390
|
throw new Error("Subscribing more than once is not implemented");
|
|
@@ -539,6 +641,8 @@ var BrowseNodeResult = class {
|
|
|
539
641
|
};
|
|
540
642
|
|
|
541
643
|
// src/client.ts
|
|
644
|
+
var SERVER_STATUS_NODE_ID = NodeId.newNumeric(0, 2256);
|
|
645
|
+
var KEEP_ALIVE_INTERVAL_MS = 25e3;
|
|
542
646
|
var Client = class {
|
|
543
647
|
constructor(endpointUrl, configuration, identity) {
|
|
544
648
|
this.configuration = configuration;
|
|
@@ -554,9 +658,12 @@ var Client = class {
|
|
|
554
658
|
session;
|
|
555
659
|
subscriptionHandler;
|
|
556
660
|
logger;
|
|
557
|
-
// Stored after connect() so that refreshSession() can
|
|
661
|
+
// Stored after connect() so that refreshSession() and disconnect() can use them.
|
|
558
662
|
secureChannel;
|
|
663
|
+
secureChannelFacade;
|
|
664
|
+
ws;
|
|
559
665
|
sessionHandler;
|
|
666
|
+
keepAliveTimer;
|
|
560
667
|
getSession() {
|
|
561
668
|
if (!this.session) {
|
|
562
669
|
throw new Error("No session available");
|
|
@@ -585,20 +692,77 @@ var Client = class {
|
|
|
585
692
|
* This covers the reactive case: a service call reveals that the server has
|
|
586
693
|
* already dropped the session (e.g. due to timeout). The new session is
|
|
587
694
|
* established transparently before re-running the original operation.
|
|
695
|
+
*
|
|
696
|
+
* For any other error (e.g. transport-level failures when the SecureChannel
|
|
697
|
+
* drops), this method attempts to reconnect the channel and reactivate the
|
|
698
|
+
* existing session first — falling back to a brand-new session only when
|
|
699
|
+
* reactivation fails — before retrying the operation once.
|
|
588
700
|
*/
|
|
589
701
|
async withSessionRefresh(fn) {
|
|
590
702
|
try {
|
|
591
703
|
return await fn();
|
|
592
704
|
} catch (err) {
|
|
593
|
-
if (
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
705
|
+
if (err instanceof SessionInvalidError) {
|
|
706
|
+
this.logger.info(`Session invalid (${err.statusCode.toString(16)}), refreshing session...`);
|
|
707
|
+
this.session = await this.sessionHandler.createNewSession(this.identity);
|
|
708
|
+
this.initServices();
|
|
709
|
+
this.logger.info("Session refreshed, retrying operation.");
|
|
710
|
+
return await fn();
|
|
711
|
+
}
|
|
712
|
+
this.logger.info("Service call failed, attempting channel reconnect and session reactivation...");
|
|
713
|
+
try {
|
|
714
|
+
await this.reconnectAndReactivate();
|
|
715
|
+
this.initServices();
|
|
716
|
+
this.logger.info("Reconnected successfully, retrying operation.");
|
|
717
|
+
} catch (reconnectErr) {
|
|
718
|
+
this.logger.warn("Channel reconnect failed:", reconnectErr);
|
|
719
|
+
throw err;
|
|
720
|
+
}
|
|
598
721
|
return await fn();
|
|
599
722
|
}
|
|
600
723
|
}
|
|
724
|
+
/**
|
|
725
|
+
* Starts a periodic keep-alive timer that reads Server_ServerStatus when no subscription is
|
|
726
|
+
* active. OPC UA Part 4, Section 5.7.1 requires clients to keep the session alive; when no
|
|
727
|
+
* subscription Publish loop is running this is the only mechanism that does so.
|
|
728
|
+
*/
|
|
729
|
+
startKeepAlive() {
|
|
730
|
+
this.keepAliveTimer = setInterval(() => {
|
|
731
|
+
if (this.subscriptionHandler?.hasActiveSubscription()) {
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
if (this.attributeService) {
|
|
735
|
+
void this.attributeService.ReadValue([SERVER_STATUS_NODE_ID]).catch((err) => {
|
|
736
|
+
this.logger.warn("Keep-alive read failed:", err);
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
}, KEEP_ALIVE_INTERVAL_MS);
|
|
740
|
+
}
|
|
741
|
+
stopKeepAlive() {
|
|
742
|
+
clearInterval(this.keepAliveTimer);
|
|
743
|
+
this.keepAliveTimer = void 0;
|
|
744
|
+
}
|
|
601
745
|
async connect() {
|
|
746
|
+
const { ws, sc } = await this.openTransportAndChannel();
|
|
747
|
+
this.secureChannel = sc;
|
|
748
|
+
this.secureChannelFacade = sc;
|
|
749
|
+
this.ws = ws;
|
|
750
|
+
this.logger.debug("Creating session...");
|
|
751
|
+
this.sessionHandler = new SessionHandler(sc, this.configuration);
|
|
752
|
+
this.session = await this.sessionHandler.createNewSession(this.identity);
|
|
753
|
+
this.logger.debug("Session created.");
|
|
754
|
+
this.logger.debug("Initializing services...");
|
|
755
|
+
this.initServices();
|
|
756
|
+
this.startKeepAlive();
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Builds the full WebSocket → TCP → SecureChannel pipeline and returns the
|
|
760
|
+
* two objects needed to drive it: the raw WebSocket facade (for teardown)
|
|
761
|
+
* and the SecureChannelFacade (for service requests/session management).
|
|
762
|
+
*
|
|
763
|
+
* Extracted from `connect()` so it can be reused by `reconnectAndReactivate()`.
|
|
764
|
+
*/
|
|
765
|
+
async openTransportAndChannel() {
|
|
602
766
|
const wsOptions = { endpoint: this.endpointUrl };
|
|
603
767
|
const ws = new WebSocketFascade(wsOptions);
|
|
604
768
|
const webSocketReadableStream = new WebSocketReadableStream(ws, 1e3);
|
|
@@ -607,7 +771,7 @@ var Client = class {
|
|
|
607
771
|
const tcpMessageInjector = new TcpMessageInjector();
|
|
608
772
|
const tcpConnectionHandler = new TcpConnectionHandler(tcpMessageInjector, scContext);
|
|
609
773
|
const tcpMessageDecoupler = new TcpMessageDecoupler(tcpConnectionHandler.onTcpMessage.bind(tcpConnectionHandler));
|
|
610
|
-
const scMessageEncoder = new
|
|
774
|
+
const scMessageEncoder = new SecureChannelMessageEncoder(scContext);
|
|
611
775
|
const scTypeDecoder = new SecureChannelTypeDecoder(
|
|
612
776
|
this.configuration.decoder
|
|
613
777
|
);
|
|
@@ -641,16 +805,106 @@ var Client = class {
|
|
|
641
805
|
this.logger.debug("Opening secure channel...");
|
|
642
806
|
await sc.openSecureChannel();
|
|
643
807
|
this.logger.debug("Secure channel established.");
|
|
644
|
-
this.
|
|
645
|
-
|
|
808
|
+
this.enforceChannelSecurityConfig(sc);
|
|
809
|
+
return { ws, sc };
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Validates the negotiated channel's security policy and mode against the
|
|
813
|
+
* client's `SecurityConfiguration` (OPC UA Part 2, Security Administration).
|
|
814
|
+
*
|
|
815
|
+
* Throws if:
|
|
816
|
+
* - `allowSecurityPolicyNone` is `false` and the channel uses SecurityPolicy None.
|
|
817
|
+
* - `messageSecurityMode` is set and does not match the channel's actual mode.
|
|
818
|
+
*/
|
|
819
|
+
enforceChannelSecurityConfig(sc) {
|
|
820
|
+
const config = this.configuration.securityConfiguration;
|
|
821
|
+
if (!config) return;
|
|
822
|
+
const negotiatedPolicy = sc.getSecurityPolicy();
|
|
823
|
+
const negotiatedMode = sc.getSecurityMode();
|
|
824
|
+
if (config.allowSecurityPolicyNone === false && negotiatedPolicy === SECURITY_POLICY_NONE_URI) {
|
|
825
|
+
throw new Error(
|
|
826
|
+
"Connection refused: SecurityPolicy None is disabled by the client security configuration. Only SecurityPolicy None is currently supported by this client implementation."
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
if (config.messageSecurityMode !== void 0 && config.messageSecurityMode !== negotiatedMode) {
|
|
830
|
+
throw new Error(
|
|
831
|
+
`Connection refused: negotiated MessageSecurityMode ${negotiatedMode} does not match the required mode ${config.messageSecurityMode} from the security configuration.`
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Tears down the current (dead) channel and establishes a fresh one, then
|
|
837
|
+
* attempts to recover the existing OPC UA session via ActivateSession before
|
|
838
|
+
* falling back to a full CreateSession + ActivateSession.
|
|
839
|
+
*
|
|
840
|
+
* OPC UA Part 4, Section 5.7.1 / Session Client Auto Reconnect conformance unit:
|
|
841
|
+
* When the SecureChannel drops but the server-side session has not yet timed
|
|
842
|
+
* out, the client SHOULD reuse the existing session by calling ActivateSession
|
|
843
|
+
* on the new channel. Only if that fails should the client create a new session.
|
|
844
|
+
*/
|
|
845
|
+
async reconnectAndReactivate() {
|
|
846
|
+
this.logger.info("Tearing down dead channel before reconnect...");
|
|
847
|
+
try {
|
|
848
|
+
this.secureChannelFacade?.close();
|
|
849
|
+
} catch {
|
|
850
|
+
}
|
|
851
|
+
try {
|
|
852
|
+
this.ws?.close();
|
|
853
|
+
} catch {
|
|
854
|
+
}
|
|
855
|
+
this.secureChannelFacade = void 0;
|
|
856
|
+
this.secureChannel = void 0;
|
|
857
|
+
this.ws = void 0;
|
|
858
|
+
const { ws, sc } = await this.openTransportAndChannel();
|
|
646
859
|
this.secureChannel = sc;
|
|
860
|
+
this.secureChannelFacade = sc;
|
|
861
|
+
this.ws = ws;
|
|
862
|
+
this.sessionHandler = new SessionHandler(sc, this.configuration);
|
|
863
|
+
if (this.session) {
|
|
864
|
+
const reactivated = await this.sessionHandler.tryActivateExistingSession(
|
|
865
|
+
this.session.getAuthToken(),
|
|
866
|
+
this.session.getSessionId(),
|
|
867
|
+
this.session.getEndpoint(),
|
|
868
|
+
this.identity
|
|
869
|
+
);
|
|
870
|
+
if (reactivated !== null) {
|
|
871
|
+
this.session = reactivated;
|
|
872
|
+
this.logger.info("Existing session successfully reactivated on new channel.");
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
this.logger.info("ActivateSession for existing session failed; creating a fresh session...");
|
|
876
|
+
}
|
|
647
877
|
this.session = await this.sessionHandler.createNewSession(this.identity);
|
|
648
|
-
this.logger.
|
|
649
|
-
this.logger.debug("Initializing services...");
|
|
650
|
-
this.initServices();
|
|
878
|
+
this.logger.info("Fresh session established on new channel.");
|
|
651
879
|
}
|
|
880
|
+
/**
|
|
881
|
+
* Gracefully disconnects from the OPC UA server.
|
|
882
|
+
*
|
|
883
|
+
* Sequence per OPC UA Part 4, Section 5.7.4:
|
|
884
|
+
* 1. CloseSession (deleteSubscriptions=true) so the server frees all resources.
|
|
885
|
+
* 2. Close the SecureChannel, which cancels the pending token-renewal timer.
|
|
886
|
+
* 3. Close the WebSocket transport.
|
|
887
|
+
*
|
|
888
|
+
* CloseSession errors are swallowed so transport teardown always completes even
|
|
889
|
+
* when the session has already expired on the server side.
|
|
890
|
+
*/
|
|
652
891
|
async disconnect() {
|
|
653
892
|
this.logger.info("Disconnecting from OPC UA server...");
|
|
893
|
+
this.stopKeepAlive();
|
|
894
|
+
if (this.session && this.sessionHandler) {
|
|
895
|
+
try {
|
|
896
|
+
await this.sessionHandler.closeSession(true);
|
|
897
|
+
} catch (err) {
|
|
898
|
+
this.logger.warn("CloseSession failed (continuing teardown):", err);
|
|
899
|
+
}
|
|
900
|
+
this.session = void 0;
|
|
901
|
+
}
|
|
902
|
+
this.secureChannelFacade?.close();
|
|
903
|
+
this.secureChannelFacade = void 0;
|
|
904
|
+
this.secureChannel = void 0;
|
|
905
|
+
this.ws?.close();
|
|
906
|
+
this.ws = void 0;
|
|
907
|
+
this.logger.info("Disconnected.");
|
|
654
908
|
}
|
|
655
909
|
async read(ids) {
|
|
656
910
|
return this.withSessionRefresh(async () => {
|
|
@@ -737,6 +991,14 @@ var Client = class {
|
|
|
737
991
|
}
|
|
738
992
|
};
|
|
739
993
|
var ConfigurationClient = class _ConfigurationClient extends Configuration {
|
|
994
|
+
/**
|
|
995
|
+
* Optional security restrictions applied during `Client.connect()`.
|
|
996
|
+
* When not set, permissive defaults are used (SecurityPolicy None allowed,
|
|
997
|
+
* all user-token types accepted).
|
|
998
|
+
*
|
|
999
|
+
* @see SecurityConfiguration
|
|
1000
|
+
*/
|
|
1001
|
+
securityConfiguration;
|
|
740
1002
|
static getSimple(name, company, loggerFactory) {
|
|
741
1003
|
if (!loggerFactory) {
|
|
742
1004
|
loggerFactory = new LoggerFactory({
|
|
@@ -826,6 +1088,6 @@ var UserIdentity = class _UserIdentity {
|
|
|
826
1088
|
}
|
|
827
1089
|
};
|
|
828
1090
|
|
|
829
|
-
export { BrowseNodeResult, Client, ConfigurationClient, SessionInvalidError, UserIdentity };
|
|
1091
|
+
export { BrowseNodeResult, Client, ConfigurationClient, SECURITY_POLICY_NONE_URI, SessionInvalidError, UserIdentity };
|
|
830
1092
|
//# sourceMappingURL=index.js.map
|
|
831
1093
|
//# sourceMappingURL=index.js.map
|