opcjs-client 0.1.26-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.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { ISecureChannel, RequestHeader, NodeId, EndpointDescription, UserIdentityToken, Configuration, UserTokenTypeEnum, ILoggerFactory, Encoder, Decoder, ExpandedNodeId, QualifiedName, LocalizedText, NodeClassEnum, UaPrimitive } from 'opcjs-base';
1
+ import { ISecureChannel, RequestHeader, NodeId, EndpointDescription, UserIdentityToken, Configuration, UserTokenTypeEnum, MessageSecurityModeEnum, ILoggerFactory, Encoder, Decoder, ExpandedNodeId, QualifiedName, LocalizedText, NodeClassEnum, UaPrimitive } from 'opcjs-base';
2
2
 
3
3
  declare abstract class ServiceBase {
4
4
  private authToken;
@@ -35,6 +35,13 @@ declare class SessionService extends ServiceBase {
35
35
  * @param identityToken - User identity token (anonymous, username/password, certificate, or issued token).
36
36
  */
37
37
  activateSession(identityToken: UserIdentityToken): Promise<void>;
38
+ /**
39
+ * Closes the current session on the server (OPC UA Part 4, Section 5.7.4).
40
+ * @param deleteSubscriptions - When true the server deletes all Subscriptions
41
+ * associated with this Session, freeing their resources immediately.
42
+ * Pass false to keep Subscriptions alive for transfer to another Session.
43
+ */
44
+ closeSession(deleteSubscriptions: boolean): Promise<void>;
38
45
  recreate(authToken: NodeId): SessionService;
39
46
  constructor(authToken: NodeId, secureChannel: ISecureChannel, configuration: Configuration);
40
47
  }
@@ -84,6 +91,14 @@ declare class Session {
84
91
  private sessionServices;
85
92
  activateSession(identity: UserIdentity): Promise<void>;
86
93
  getAuthToken(): NodeId;
94
+ getSessionId(): number;
95
+ getEndpoint(): EndpointDescription;
96
+ /**
97
+ * Closes the session on the server (OPC UA Part 4, Section 5.7.4).
98
+ * @param deleteSubscriptions - When true the server deletes all Subscriptions
99
+ * tied to this Session. Defaults to true.
100
+ */
101
+ close(deleteSubscriptions?: boolean): Promise<void>;
87
102
  constructor(sessionId: number, authToken: NodeId, endpoint: EndpointDescription, sessionServices: SessionService);
88
103
  }
89
104
 
@@ -93,7 +108,100 @@ declare class ReadValueResult {
93
108
  constructor(value: unknown, statusCode: number);
94
109
  }
95
110
 
111
+ /** URI for the SecurityPolicy None profile. */
112
+ declare const SECURITY_POLICY_NONE_URI = "http://opcfoundation.org/UA/SecurityPolicy#None";
113
+ /**
114
+ * How the client should handle a server certificate it cannot verify.
115
+ *
116
+ * - `'reject'` — abort the connection (secure default when `trustedCAs` is configured).
117
+ * - `'trust'` — accept any certificate without verification (development convenience;
118
+ * **insecure in production**).
119
+ *
120
+ * @note Enforcement is deferred until certificate-based security policies are
121
+ * implemented. The value is stored and will be honoured when that support lands.
122
+ */
123
+ type UnknownCertificatePolicy = 'reject' | 'trust';
124
+ /**
125
+ * OPC UA client security configuration (OPC UA Part 2, Security Administration CU).
126
+ *
127
+ * Restricts which security options the client will accept when connecting to or
128
+ * negotiating with a server. All fields are optional; omitting a field applies
129
+ * the permissive default so that existing callers need no changes.
130
+ *
131
+ * @example
132
+ * ```ts
133
+ * const config = ConfigurationClient.getSimple('MyApp', 'MyCompany')
134
+ * config.securityConfiguration = {
135
+ * allowedUserTokenTypes: [UserTokenTypeEnum.UserName],
136
+ * allowSecurityPolicyNone: false, // require an encrypted channel
137
+ * }
138
+ * ```
139
+ */
140
+ type SecurityConfiguration = {
141
+ /**
142
+ * User-identity-token types the client is willing to accept.
143
+ *
144
+ * When set, `connect()` will throw if:
145
+ * - The supplied `UserIdentity`'s token type is not in this list, OR
146
+ * - The server endpoint does not offer any type from this list.
147
+ *
148
+ * Default: all token types are accepted (no restriction).
149
+ */
150
+ allowedUserTokenTypes?: UserTokenTypeEnum[];
151
+ /**
152
+ * Allow connecting when the negotiated SecurityPolicy is `None` (unencrypted,
153
+ * unsigned channel).
154
+ *
155
+ * Set to `false` to require a secure channel — `connect()` will throw rather
156
+ * than establish a cleartext connection.
157
+ *
158
+ * Currently the only supported security policy is `None`. Setting this to `false`
159
+ * will therefore cause `connect()` to always throw until non-None policies are
160
+ * added to this client implementation.
161
+ *
162
+ * Default: `true` (SecurityPolicy None is permitted).
163
+ */
164
+ allowSecurityPolicyNone?: boolean;
165
+ /**
166
+ * Required `MessageSecurityMode` for the secure channel.
167
+ *
168
+ * When set, `connect()` verifies that the channel's negotiated mode matches
169
+ * this value and throws otherwise.
170
+ *
171
+ * Currently only `MessageSecurityModeEnum.None` is supported.
172
+ *
173
+ * Default: no requirement (any mode is accepted).
174
+ */
175
+ messageSecurityMode?: MessageSecurityModeEnum;
176
+ /**
177
+ * DER-encoded X.509 trusted CA certificates used to verify the server's
178
+ * application instance certificate.
179
+ *
180
+ * @note Reserved for future use. Certificate verification is not yet implemented.
181
+ * Providing this field has no effect until non-None security policies land.
182
+ */
183
+ trustedCAs?: Uint8Array[];
184
+ /**
185
+ * How to handle a server certificate that cannot be verified against `trustedCAs`.
186
+ *
187
+ * - `'reject'` — refuse the connection (default when `trustedCAs` is provided).
188
+ * - `'trust'` — accept any certificate (development only; insecure in production).
189
+ *
190
+ * @note Reserved for future use. Has no effect until certificate verification is
191
+ * implemented alongside non-None security policies.
192
+ */
193
+ unknownCertificatePolicy?: UnknownCertificatePolicy;
194
+ };
195
+
96
196
  declare class ConfigurationClient extends Configuration {
197
+ /**
198
+ * Optional security restrictions applied during `Client.connect()`.
199
+ * When not set, permissive defaults are used (SecurityPolicy None allowed,
200
+ * all user-token types accepted).
201
+ *
202
+ * @see SecurityConfiguration
203
+ */
204
+ securityConfiguration?: SecurityConfiguration;
97
205
  static getSimple(name: string, company: string, loggerFactory?: ILoggerFactory): ConfigurationClient;
98
206
  constructor(applicationName: string, applicationUri: string, productName: string, productUri: string, encoder: Encoder, decoder: Decoder, loggerFactory: ILoggerFactory);
99
207
  }
@@ -126,7 +234,10 @@ declare class Client {
126
234
  private subscriptionHandler?;
127
235
  private logger;
128
236
  private secureChannel?;
237
+ private secureChannelFacade?;
238
+ private ws?;
129
239
  private sessionHandler?;
240
+ private keepAliveTimer?;
130
241
  getSession(): Session;
131
242
  /**
132
243
  * (Re-)initialises all session-scoped services from the current `this.session`.
@@ -140,9 +251,60 @@ declare class Client {
140
251
  * This covers the reactive case: a service call reveals that the server has
141
252
  * already dropped the session (e.g. due to timeout). The new session is
142
253
  * established transparently before re-running the original operation.
254
+ *
255
+ * For any other error (e.g. transport-level failures when the SecureChannel
256
+ * drops), this method attempts to reconnect the channel and reactivate the
257
+ * existing session first — falling back to a brand-new session only when
258
+ * reactivation fails — before retrying the operation once.
143
259
  */
144
260
  private withSessionRefresh;
261
+ /**
262
+ * Starts a periodic keep-alive timer that reads Server_ServerStatus when no subscription is
263
+ * active. OPC UA Part 4, Section 5.7.1 requires clients to keep the session alive; when no
264
+ * subscription Publish loop is running this is the only mechanism that does so.
265
+ */
266
+ private startKeepAlive;
267
+ private stopKeepAlive;
145
268
  connect(): Promise<void>;
269
+ /**
270
+ * Builds the full WebSocket → TCP → SecureChannel pipeline and returns the
271
+ * two objects needed to drive it: the raw WebSocket facade (for teardown)
272
+ * and the SecureChannelFacade (for service requests/session management).
273
+ *
274
+ * Extracted from `connect()` so it can be reused by `reconnectAndReactivate()`.
275
+ */
276
+ private openTransportAndChannel;
277
+ /**
278
+ * Validates the negotiated channel's security policy and mode against the
279
+ * client's `SecurityConfiguration` (OPC UA Part 2, Security Administration).
280
+ *
281
+ * Throws if:
282
+ * - `allowSecurityPolicyNone` is `false` and the channel uses SecurityPolicy None.
283
+ * - `messageSecurityMode` is set and does not match the channel's actual mode.
284
+ */
285
+ private enforceChannelSecurityConfig;
286
+ /**
287
+ * Tears down the current (dead) channel and establishes a fresh one, then
288
+ * attempts to recover the existing OPC UA session via ActivateSession before
289
+ * falling back to a full CreateSession + ActivateSession.
290
+ *
291
+ * OPC UA Part 4, Section 5.7.1 / Session Client Auto Reconnect conformance unit:
292
+ * When the SecureChannel drops but the server-side session has not yet timed
293
+ * out, the client SHOULD reuse the existing session by calling ActivateSession
294
+ * on the new channel. Only if that fails should the client create a new session.
295
+ */
296
+ private reconnectAndReactivate;
297
+ /**
298
+ * Gracefully disconnects from the OPC UA server.
299
+ *
300
+ * Sequence per OPC UA Part 4, Section 5.7.4:
301
+ * 1. CloseSession (deleteSubscriptions=true) so the server frees all resources.
302
+ * 2. Close the SecureChannel, which cancels the pending token-renewal timer.
303
+ * 3. Close the WebSocket transport.
304
+ *
305
+ * CloseSession errors are swallowed so transport teardown always completes even
306
+ * when the session has already expired on the server side.
307
+ */
146
308
  disconnect(): Promise<void>;
147
309
  read(ids: NodeId[]): Promise<ReadValueResult[]>;
148
310
  /**
@@ -178,4 +340,4 @@ declare class SessionInvalidError extends Error {
178
340
  constructor(statusCode: number);
179
341
  }
180
342
 
181
- export { BrowseNodeResult, Client, ConfigurationClient, SessionInvalidError, UserIdentity };
343
+ export { BrowseNodeResult, Client, ConfigurationClient, SECURITY_POLICY_NONE_URI, type SecurityConfiguration, SessionInvalidError, type UnknownCertificatePolicy, UserIdentity };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ISecureChannel, RequestHeader, NodeId, EndpointDescription, UserIdentityToken, Configuration, UserTokenTypeEnum, ILoggerFactory, Encoder, Decoder, ExpandedNodeId, QualifiedName, LocalizedText, NodeClassEnum, UaPrimitive } from 'opcjs-base';
1
+ import { ISecureChannel, RequestHeader, NodeId, EndpointDescription, UserIdentityToken, Configuration, UserTokenTypeEnum, MessageSecurityModeEnum, ILoggerFactory, Encoder, Decoder, ExpandedNodeId, QualifiedName, LocalizedText, NodeClassEnum, UaPrimitive } from 'opcjs-base';
2
2
 
3
3
  declare abstract class ServiceBase {
4
4
  private authToken;
@@ -35,6 +35,13 @@ declare class SessionService extends ServiceBase {
35
35
  * @param identityToken - User identity token (anonymous, username/password, certificate, or issued token).
36
36
  */
37
37
  activateSession(identityToken: UserIdentityToken): Promise<void>;
38
+ /**
39
+ * Closes the current session on the server (OPC UA Part 4, Section 5.7.4).
40
+ * @param deleteSubscriptions - When true the server deletes all Subscriptions
41
+ * associated with this Session, freeing their resources immediately.
42
+ * Pass false to keep Subscriptions alive for transfer to another Session.
43
+ */
44
+ closeSession(deleteSubscriptions: boolean): Promise<void>;
38
45
  recreate(authToken: NodeId): SessionService;
39
46
  constructor(authToken: NodeId, secureChannel: ISecureChannel, configuration: Configuration);
40
47
  }
@@ -84,6 +91,14 @@ declare class Session {
84
91
  private sessionServices;
85
92
  activateSession(identity: UserIdentity): Promise<void>;
86
93
  getAuthToken(): NodeId;
94
+ getSessionId(): number;
95
+ getEndpoint(): EndpointDescription;
96
+ /**
97
+ * Closes the session on the server (OPC UA Part 4, Section 5.7.4).
98
+ * @param deleteSubscriptions - When true the server deletes all Subscriptions
99
+ * tied to this Session. Defaults to true.
100
+ */
101
+ close(deleteSubscriptions?: boolean): Promise<void>;
87
102
  constructor(sessionId: number, authToken: NodeId, endpoint: EndpointDescription, sessionServices: SessionService);
88
103
  }
89
104
 
@@ -93,7 +108,100 @@ declare class ReadValueResult {
93
108
  constructor(value: unknown, statusCode: number);
94
109
  }
95
110
 
111
+ /** URI for the SecurityPolicy None profile. */
112
+ declare const SECURITY_POLICY_NONE_URI = "http://opcfoundation.org/UA/SecurityPolicy#None";
113
+ /**
114
+ * How the client should handle a server certificate it cannot verify.
115
+ *
116
+ * - `'reject'` — abort the connection (secure default when `trustedCAs` is configured).
117
+ * - `'trust'` — accept any certificate without verification (development convenience;
118
+ * **insecure in production**).
119
+ *
120
+ * @note Enforcement is deferred until certificate-based security policies are
121
+ * implemented. The value is stored and will be honoured when that support lands.
122
+ */
123
+ type UnknownCertificatePolicy = 'reject' | 'trust';
124
+ /**
125
+ * OPC UA client security configuration (OPC UA Part 2, Security Administration CU).
126
+ *
127
+ * Restricts which security options the client will accept when connecting to or
128
+ * negotiating with a server. All fields are optional; omitting a field applies
129
+ * the permissive default so that existing callers need no changes.
130
+ *
131
+ * @example
132
+ * ```ts
133
+ * const config = ConfigurationClient.getSimple('MyApp', 'MyCompany')
134
+ * config.securityConfiguration = {
135
+ * allowedUserTokenTypes: [UserTokenTypeEnum.UserName],
136
+ * allowSecurityPolicyNone: false, // require an encrypted channel
137
+ * }
138
+ * ```
139
+ */
140
+ type SecurityConfiguration = {
141
+ /**
142
+ * User-identity-token types the client is willing to accept.
143
+ *
144
+ * When set, `connect()` will throw if:
145
+ * - The supplied `UserIdentity`'s token type is not in this list, OR
146
+ * - The server endpoint does not offer any type from this list.
147
+ *
148
+ * Default: all token types are accepted (no restriction).
149
+ */
150
+ allowedUserTokenTypes?: UserTokenTypeEnum[];
151
+ /**
152
+ * Allow connecting when the negotiated SecurityPolicy is `None` (unencrypted,
153
+ * unsigned channel).
154
+ *
155
+ * Set to `false` to require a secure channel — `connect()` will throw rather
156
+ * than establish a cleartext connection.
157
+ *
158
+ * Currently the only supported security policy is `None`. Setting this to `false`
159
+ * will therefore cause `connect()` to always throw until non-None policies are
160
+ * added to this client implementation.
161
+ *
162
+ * Default: `true` (SecurityPolicy None is permitted).
163
+ */
164
+ allowSecurityPolicyNone?: boolean;
165
+ /**
166
+ * Required `MessageSecurityMode` for the secure channel.
167
+ *
168
+ * When set, `connect()` verifies that the channel's negotiated mode matches
169
+ * this value and throws otherwise.
170
+ *
171
+ * Currently only `MessageSecurityModeEnum.None` is supported.
172
+ *
173
+ * Default: no requirement (any mode is accepted).
174
+ */
175
+ messageSecurityMode?: MessageSecurityModeEnum;
176
+ /**
177
+ * DER-encoded X.509 trusted CA certificates used to verify the server's
178
+ * application instance certificate.
179
+ *
180
+ * @note Reserved for future use. Certificate verification is not yet implemented.
181
+ * Providing this field has no effect until non-None security policies land.
182
+ */
183
+ trustedCAs?: Uint8Array[];
184
+ /**
185
+ * How to handle a server certificate that cannot be verified against `trustedCAs`.
186
+ *
187
+ * - `'reject'` — refuse the connection (default when `trustedCAs` is provided).
188
+ * - `'trust'` — accept any certificate (development only; insecure in production).
189
+ *
190
+ * @note Reserved for future use. Has no effect until certificate verification is
191
+ * implemented alongside non-None security policies.
192
+ */
193
+ unknownCertificatePolicy?: UnknownCertificatePolicy;
194
+ };
195
+
96
196
  declare class ConfigurationClient extends Configuration {
197
+ /**
198
+ * Optional security restrictions applied during `Client.connect()`.
199
+ * When not set, permissive defaults are used (SecurityPolicy None allowed,
200
+ * all user-token types accepted).
201
+ *
202
+ * @see SecurityConfiguration
203
+ */
204
+ securityConfiguration?: SecurityConfiguration;
97
205
  static getSimple(name: string, company: string, loggerFactory?: ILoggerFactory): ConfigurationClient;
98
206
  constructor(applicationName: string, applicationUri: string, productName: string, productUri: string, encoder: Encoder, decoder: Decoder, loggerFactory: ILoggerFactory);
99
207
  }
@@ -126,7 +234,10 @@ declare class Client {
126
234
  private subscriptionHandler?;
127
235
  private logger;
128
236
  private secureChannel?;
237
+ private secureChannelFacade?;
238
+ private ws?;
129
239
  private sessionHandler?;
240
+ private keepAliveTimer?;
130
241
  getSession(): Session;
131
242
  /**
132
243
  * (Re-)initialises all session-scoped services from the current `this.session`.
@@ -140,9 +251,60 @@ declare class Client {
140
251
  * This covers the reactive case: a service call reveals that the server has
141
252
  * already dropped the session (e.g. due to timeout). The new session is
142
253
  * established transparently before re-running the original operation.
254
+ *
255
+ * For any other error (e.g. transport-level failures when the SecureChannel
256
+ * drops), this method attempts to reconnect the channel and reactivate the
257
+ * existing session first — falling back to a brand-new session only when
258
+ * reactivation fails — before retrying the operation once.
143
259
  */
144
260
  private withSessionRefresh;
261
+ /**
262
+ * Starts a periodic keep-alive timer that reads Server_ServerStatus when no subscription is
263
+ * active. OPC UA Part 4, Section 5.7.1 requires clients to keep the session alive; when no
264
+ * subscription Publish loop is running this is the only mechanism that does so.
265
+ */
266
+ private startKeepAlive;
267
+ private stopKeepAlive;
145
268
  connect(): Promise<void>;
269
+ /**
270
+ * Builds the full WebSocket → TCP → SecureChannel pipeline and returns the
271
+ * two objects needed to drive it: the raw WebSocket facade (for teardown)
272
+ * and the SecureChannelFacade (for service requests/session management).
273
+ *
274
+ * Extracted from `connect()` so it can be reused by `reconnectAndReactivate()`.
275
+ */
276
+ private openTransportAndChannel;
277
+ /**
278
+ * Validates the negotiated channel's security policy and mode against the
279
+ * client's `SecurityConfiguration` (OPC UA Part 2, Security Administration).
280
+ *
281
+ * Throws if:
282
+ * - `allowSecurityPolicyNone` is `false` and the channel uses SecurityPolicy None.
283
+ * - `messageSecurityMode` is set and does not match the channel's actual mode.
284
+ */
285
+ private enforceChannelSecurityConfig;
286
+ /**
287
+ * Tears down the current (dead) channel and establishes a fresh one, then
288
+ * attempts to recover the existing OPC UA session via ActivateSession before
289
+ * falling back to a full CreateSession + ActivateSession.
290
+ *
291
+ * OPC UA Part 4, Section 5.7.1 / Session Client Auto Reconnect conformance unit:
292
+ * When the SecureChannel drops but the server-side session has not yet timed
293
+ * out, the client SHOULD reuse the existing session by calling ActivateSession
294
+ * on the new channel. Only if that fails should the client create a new session.
295
+ */
296
+ private reconnectAndReactivate;
297
+ /**
298
+ * Gracefully disconnects from the OPC UA server.
299
+ *
300
+ * Sequence per OPC UA Part 4, Section 5.7.4:
301
+ * 1. CloseSession (deleteSubscriptions=true) so the server frees all resources.
302
+ * 2. Close the SecureChannel, which cancels the pending token-renewal timer.
303
+ * 3. Close the WebSocket transport.
304
+ *
305
+ * CloseSession errors are swallowed so transport teardown always completes even
306
+ * when the session has already expired on the server side.
307
+ */
146
308
  disconnect(): Promise<void>;
147
309
  read(ids: NodeId[]): Promise<ReadValueResult[]>;
148
310
  /**
@@ -178,4 +340,4 @@ declare class SessionInvalidError extends Error {
178
340
  constructor(statusCode: number);
179
341
  }
180
342
 
181
- export { BrowseNodeResult, Client, ConfigurationClient, SessionInvalidError, UserIdentity };
343
+ export { BrowseNodeResult, Client, ConfigurationClient, SECURITY_POLICY_NONE_URI, type SecurityConfiguration, SessionInvalidError, type UnknownCertificatePolicy, UserIdentity };