opcjs-client 0.1.10 → 0.1.14

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/README.md ADDED
@@ -0,0 +1,303 @@
1
+ # opcjs-client
2
+
3
+ An OPC UA client library for TypeScript targeting the browser and Node.js, built on top of `opcjs-base`.
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ npm install opcjs-client opcjs-base
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```ts
14
+ import { Client, ConfigurationClient, UserIdentity } from 'opcjs-client'
15
+ import { NodeId } from 'opcjs-base'
16
+
17
+ const config = ConfigurationClient.getSimple('MyApp', 'MyCompany')
18
+ const client = new Client('opc.tcp://localhost:4840', UserIdentity.newAnonymous(), config)
19
+
20
+ await client.connect()
21
+
22
+ // Read a node value
23
+ const results = await client.read([NodeId.newNumeric(0, 2258)]) // CurrentTime
24
+ console.log(results[0].value)
25
+
26
+ await client.disconnect()
27
+ ```
28
+
29
+ ## API
30
+
31
+ ### `new Client(endpointUrl, identity, configuration)`
32
+
33
+ Creates a new client instance. Does not connect until `connect()` is called.
34
+
35
+ | Parameter | Type | Description |
36
+ |-----------|------|-------------|
37
+ | `endpointUrl` | `string` | OPC UA server endpoint (e.g. `opc.tcp://host:4840`) |
38
+ | `identity` | `UserIdentity` | Credentials used for `ActivateSession` |
39
+ | `configuration` | `ConfigurationClient` | Application description, encoder/decoder, and security settings |
40
+
41
+ ### `client.connect(): Promise<void>`
42
+
43
+ Opens the WebSocket transport, establishes a TCP/SecureChannel, creates an OPC UA session, and starts the keep-alive timer.
44
+
45
+ Reconnects automatically on channel drops (Session Auto Reconnect, OPC UA Part 4 §5.7.1):
46
+ 1. Attempts `ActivateSession` on the new channel to reuse the existing session.
47
+ 2. Falls back to a full `CreateSession` + `ActivateSession` if reactivation fails.
48
+
49
+ ### `client.disconnect(): Promise<void>`
50
+
51
+ Sends `CloseSession` (with `deleteSubscriptions=true`), closes the SecureChannel, and shuts down the WebSocket transport.
52
+
53
+ ### `client.read(ids, options?): Promise<ReadValueResult[]>`
54
+
55
+ Reads the `Value` attribute of one or more nodes.
56
+
57
+ ```ts
58
+ const [result] = await client.read([NodeId.newNumeric(0, 2258)])
59
+ // result.value — the read value
60
+ // result.statusCode — OPC UA StatusCode
61
+ // result.diagnosticInfo — populated when returnDiagnostics > 0
62
+ ```
63
+
64
+ ### `client.getSelectionList(nodeId): Promise<SelectionList | null>`
65
+
66
+ Reads `SelectionListType` metadata (OPC UA Part 5 §7.18) from a variable and exposes it to application code.
67
+
68
+ The method reads the variable's `HasProperty` references for:
69
+ - `Selections` (mandatory)
70
+ - `SelectionDescriptions` (optional)
71
+ - `RestrictToList` (optional)
72
+
73
+ It returns `null` when the variable does not expose a `Selections` property.
74
+
75
+ ```ts
76
+ const list = await client.getSelectionList(nodeId)
77
+ if (list) {
78
+ console.log(list.selections)
79
+ console.log(list.selectionDescriptions.map(d => d.text))
80
+ console.log('restrictToList:', list.restrictToList)
81
+ }
82
+ ```
83
+
84
+ ### `client.browse(nodeId, recursive?, options?): Promise<BrowseNodeResult[]>`
85
+
86
+ Browses the `HierarchicalReferences` of a node. Set `recursive` to `true` to traverse the full sub-tree.
87
+
88
+ Continuation points are handled automatically: all pages are fetched and merged before the promise resolves.
89
+
90
+ ### `client.callMethod(objectId, methodId, inputArguments?, options?): Promise<CallMethodResult>`
91
+
92
+ Calls an OPC UA method.
93
+
94
+ ```ts
95
+ import { CallMethodArgument } from 'opcjs-client'
96
+
97
+ const result = await client.callMethod(
98
+ NodeId.newNumeric(0, 1000), // Object that owns the method
99
+ NodeId.newNumeric(0, 1001), // Method node
100
+ [42, 'hello'] as CallMethodArgument[],
101
+ )
102
+ // result.values — output argument values
103
+ // result.statusCode — OPC UA StatusCode
104
+ // result.diagnosticInfo — populated when returnDiagnostics > 0
105
+ ```
106
+
107
+ ### `client.subscribe(ids, callback, options?): Promise<void>`
108
+
109
+ Creates an OPC UA subscription and monitored items, then starts the Publish loop.
110
+
111
+ ```ts
112
+ await client.subscribe(
113
+ [NodeId.newNumeric(0, 2258)],
114
+ (notifications) => {
115
+ for (const { id, value } of notifications) {
116
+ console.log(id, value)
117
+ }
118
+ },
119
+ { requestedPublishingInterval: 1000 },
120
+ )
121
+ ```
122
+
123
+ `SubscriptionOptions` (all optional, server may revise):
124
+
125
+ | Field | Default | Description |
126
+ |-------|---------|-------------|
127
+ | `requestedPublishingInterval` | `2000` ms | Publishing interval |
128
+ | `requestedLifetimeCount` | `360000` | Subscription lifetime in publishing intervals |
129
+ | `requestedMaxKeepAliveCount` | `60000` | Keep-alive count |
130
+ | `maxNotificationsPerPublish` | `200` | `0` = no limit |
131
+ | `priority` | `1` | Priority relative to other subscriptions |
132
+ | `samplingInterval` | `-1` (use publishing interval) | Monitored item sampling interval |
133
+ | `queueSize` | server default | Per-item notification queue |
134
+
135
+ ## Authentication
136
+
137
+ ```ts
138
+ // Anonymous (default)
139
+ const identity = UserIdentity.newAnonymous()
140
+
141
+ // Username / password
142
+ const identity = UserIdentity.newWithUserName('user', 'pass')
143
+
144
+ // Issued token (e.g. OAuth / JWT)
145
+ const identity = UserIdentity.newWithIssuerToken(async (config) => {
146
+ return { tokenData: new TextEncoder().encode(await fetchJwt(config)) }
147
+ })
148
+ ```
149
+
150
+ ## Security Configuration
151
+
152
+ Attach a `SecurityConfiguration` to the `ConfigurationClient` to restrict which security options are accepted:
153
+
154
+ ```ts
155
+ import { UserTokenTypeEnum } from 'opcjs-base'
156
+
157
+ const config = ConfigurationClient.getSimple('MyApp', 'MyCompany')
158
+ config.securityConfiguration = {
159
+ // Require authentication — reject anonymous connections
160
+ allowedUserTokenTypes: [UserTokenTypeEnum.UserName],
161
+ // Require an encrypted channel (throws until non-None policies are supported)
162
+ allowSecurityPolicyNone: false,
163
+ }
164
+ ```
165
+
166
+ | Field | Default | Description |
167
+ |-------|---------|-------------|
168
+ | `allowedUserTokenTypes` | all types | Token types the client will accept |
169
+ | `allowSecurityPolicyNone` | `true` | Allow unencrypted SecurityPolicy None channels |
170
+ | `messageSecurityMode` | any | Required `MessageSecurityMode` |
171
+ | `trustedCAs` | — | DER-encoded trusted CA certificates (reserved for future use) |
172
+ | `unknownCertificatePolicy` | — | `'reject'` or `'trust'` for unverifiable server certificates (reserved for future use) |
173
+
174
+ > **Security note:** `allowSecurityPolicyNone: true` (the default) allows cleartext communication. Set it to `false` once non-None security policies are available in this client implementation.
175
+
176
+ ## Request Options
177
+
178
+ All service methods (`read`, `callMethod`, `browse`) accept an optional `RequestOptions` object as their last parameter.
179
+
180
+ | Field | Type | Default | Description |
181
+ |-------|------|---------|-------------|
182
+ | `returnDiagnostics` | `number` | `0` | Bitmask of diagnostic fields the server should populate (OPC UA Part 4, §7.15). Use `ReturnDiagnosticsMask` constants to compose the value. |
183
+
184
+ ### `ReturnDiagnosticsMask` constants
185
+
186
+ ```ts
187
+ import { ReturnDiagnosticsMask } from 'opcjs-client'
188
+ ```
189
+
190
+ | Constant | Value | Description |
191
+ |----------|-------|-------------|
192
+ | `ServiceLevel` | `0x001f` | All service-level fields |
193
+ | `OperationLevel` | `0x03e0` | All operation-level fields |
194
+ | `All` | `0x03ff` | All diagnostic fields |
195
+ | `ServiceSymbolicId` | `0x0001` | Service symbolic identifier |
196
+ | `ServiceLocalizedText` | `0x0002` | Service localised text |
197
+ | `ServiceAdditionalInfo` | `0x0004` | Service additional info |
198
+ | `ServiceInnerStatusCode` | `0x0008` | Service inner status code |
199
+ | `ServiceInnerDiagnostics` | `0x0010` | Service inner diagnostic info |
200
+ | `OperationSymbolicId` | `0x0020` | Operation symbolic identifier |
201
+ | `OperationLocalizedText` | `0x0040` | Operation localised text |
202
+ | `OperationAdditionalInfo` | `0x0080` | Operation additional info |
203
+ | `OperationInnerStatusCode` | `0x0100` | Operation inner status code |
204
+ | `OperationInnerDiagnostics` | `0x0200` | Operation inner diagnostic info |
205
+
206
+ ### Example
207
+
208
+ ```ts
209
+ import { ReturnDiagnosticsMask } from 'opcjs-client'
210
+
211
+ const [result] = await client.read(
212
+ [NodeId.newNumeric(0, 2258)],
213
+ { returnDiagnostics: ReturnDiagnosticsMask.All },
214
+ )
215
+ if (result.diagnosticInfo) {
216
+ console.log('additional info:', result.diagnosticInfo.additionalInfo)
217
+ }
218
+
219
+ const callResult = await client.callMethod(
220
+ objectId,
221
+ methodId,
222
+ [],
223
+ { returnDiagnostics: ReturnDiagnosticsMask.OperationLevel },
224
+ )
225
+ ```
226
+
227
+ When `returnDiagnostics` is `0` (the default) the `diagnosticInfo` field on each result is `undefined`.
228
+
229
+ ## Core Capacities
230
+
231
+ This section satisfies the **Documentation – Core Capacities** conformance unit (OPC UA Core 2022 Client Facet, §A).
232
+
233
+ | Capacity | Value | Notes |
234
+ |----------|-------|-------|
235
+ | SecureChannels per `Client` instance | **1** | A single channel is opened on `connect()` and renewed automatically at 75 % of token lifetime |
236
+ | Sessions per `Client` instance | **1** | One OPC UA session per `Client`; create multiple `Client` instances for multiple sessions |
237
+ | Subscriptions per session | **1** | More than one subscription per `Client` is not yet implemented |
238
+ | Monitored items per subscription | Unlimited (server-limited) | All requested items are batched into a single `CreateMonitoredItems` call |
239
+ | Continuation points | Server-determined | `browse()` follows all continuation points automatically via `BrowseNext` |
240
+ | Max message size | Server-determined | Negotiated during Hello/Ack (UA-TCP handshake) |
241
+ | Request handles | Monotonically increasing `uint32` | Managed by `serviceBase.ts`; wraps at 2³²−1 |
242
+
243
+ ## Time Synchronisation
244
+
245
+ This client relies on the **OS system clock** for all timestamps.
246
+
247
+ On Linux this is typically kept accurate by `systemd-timesyncd` (SNTP) or a full NTP daemon (`ntpd`, `chrony`). On Windows, the W32tm service provides equivalent synchronisation. On macOS, `timed` handles clock sync.
248
+
249
+ This satisfies the **Time Sync – OS based support** optional conformance unit, which in turn satisfies the **Time Sync – Support** required conformance unit from the OPC UA Core 2022 Client Facet.
250
+
251
+ No application-level time-sync mechanism (IEEE 1588 PTP, IEEE 802.1AS, UA-based time sync) is implemented.
252
+
253
+ ## Conformance Status
254
+
255
+ | Conformance Unit | Status |
256
+ |-----------------|--------|
257
+ | Address Space Client NodeId IdTypes | ✅ Done |
258
+ | Base Info Client Currency | ✅ Done (see below) |
259
+ | Base Info Client Selection List | ✅ Done (`client.getSelectionList`) |
260
+ | Documentation – Core Capacities | ✅ Done (see above) |
261
+ | Base Services Client Diagnostics | ✅ Done (`RequestOptions.returnDiagnostics`, `ReturnDiagnosticsMask`) |
262
+ | Security Administration | ✅ Done (`SecurityConfiguration`) |
263
+ | Session Client Auto Reconnect | ✅ Done |
264
+ | Session Client Base (`CloseSession`) | ✅ Done |
265
+ | Session Client General Service Behaviour | ✅ Done |
266
+ | Session Client KeepAlive | ✅ Done (dedicated timer + Publish loop) |
267
+ | Protocol UA TCP | ✅ Done |
268
+ | UA Binary Encoding | ✅ Done |
269
+ | UA Secure Conversation | ✅ Done |
270
+ | SecurityPolicy None | ✅ Done |
271
+ | Time Sync – Support (via OS) | ✅ Satisfied |
272
+ | Security User Anonymous Client | ✅ Done |
273
+
274
+ ## Currency Values — `CurrencyUnitType`
275
+
276
+ DataVariables whose DataType is `CurrencyUnitType` (`ns=0; i=23498`, OPC UA Part 5 §12) are
277
+ automatically decoded to a typed object instead of a raw `ExtensionObject`.
278
+
279
+ ```ts
280
+ import { CurrencyUnitType } from 'opcjs-base'
281
+ import { NodeId } from 'opcjs-base'
282
+
283
+ const [result] = await client.read([NodeId.newNumeric(0, /* currency node */ 1234)])
284
+ if (result.value instanceof CurrencyUnitType) {
285
+ console.log(result.value.alphabeticCode) // e.g. "EUR"
286
+ console.log(result.value.numericCode) // e.g. 978
287
+ console.log(result.value.exponent) // e.g. -2 (cent-denominated)
288
+ console.log(result.value.currency.text) // e.g. "Euro"
289
+ }
290
+ ```
291
+
292
+ The fields follow ISO 4217:
293
+
294
+ | Field | OPC UA type | Description |
295
+ |-------|-------------|-------------|
296
+ | `numericCode` | `Int16` | ISO 4217 numeric code (e.g. `978` = EUR) |
297
+ | `exponent` | `SByte` | Decimal-place shift (e.g. `−2` for cent currencies, `0` for JPY) |
298
+ | `alphabeticCode` | `String` | Three-letter ISO 4217 code (e.g. `"EUR"`) |
299
+ | `currency` | `LocalizedText` | Human-readable currency name |
300
+
301
+ ## License
302
+
303
+ MIT