opcjs-client 0.1.13 → 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 +303 -0
- package/dist/index.cjs +1387 -123
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +787 -12
- package/dist/index.d.ts +787 -12
- package/dist/index.js +1381 -125
- package/dist/index.js.map +1 -1
- package/package.json +10 -6
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
|