opcjs-client 0.1.3
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 +1177 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +98 -0
- package/dist/index.d.ts +98 -0
- package/dist/index.js +1172 -0
- package/dist/index.js.map +1 -0
- package/package.json +45 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1172 @@
|
|
|
1
|
+
import { ChannelFactory, SecureChannel, Configuration, Encoder, registerEncoders, Decoder, BinaryReader, registerTypeDecoders, registerBinaryDecoders, NodeId, UserTokenTypeEnum, AnonymousIdentityToken, UserNameIdentityToken, IssuedIdentityToken, SubscriptionAcknowledgement, CreateSubscriptionRequest, PublishRequest, ReadValueId, QualifiedName, MonitoringParameters, ExtensionObject, MonitoredItemCreateRequest, CreateMonitoredItemsRequest, TimestampsToReturnEnum, ReadRequest, ApplicationDescription, LocalizedText, ApplicationTypeEnum, CreateSessionRequest, CreateSessionResponse, SignatureData, ActivateSessionRequest, RequestHeader } from '@opcua/base';
|
|
2
|
+
|
|
3
|
+
// src/client.ts
|
|
4
|
+
var ServiceBase = class {
|
|
5
|
+
constructor(authToken, secureChannel) {
|
|
6
|
+
this.authToken = authToken;
|
|
7
|
+
this.secureChannel = secureChannel;
|
|
8
|
+
}
|
|
9
|
+
createRequestHeader() {
|
|
10
|
+
const requestHeader = new RequestHeader();
|
|
11
|
+
requestHeader.authenticationToken = this.authToken;
|
|
12
|
+
requestHeader.timestamp = /* @__PURE__ */ new Date();
|
|
13
|
+
requestHeader.requestHandle = 0;
|
|
14
|
+
requestHeader.returnDiagnostics = 0;
|
|
15
|
+
requestHeader.auditEntryId = "";
|
|
16
|
+
requestHeader.timeoutHint = 6e4;
|
|
17
|
+
requestHeader.additionalHeader = ExtensionObject.newEmpty();
|
|
18
|
+
return requestHeader;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// src/services/sessionService.ts
|
|
23
|
+
var SessionService = class _SessionService extends ServiceBase {
|
|
24
|
+
constructor(authToken, secureChannel, configuration) {
|
|
25
|
+
super(authToken, secureChannel);
|
|
26
|
+
this.configuration = configuration;
|
|
27
|
+
}
|
|
28
|
+
async createSession() {
|
|
29
|
+
console.log("Creating session...");
|
|
30
|
+
const clientDescription = new ApplicationDescription();
|
|
31
|
+
clientDescription.applicationUri = this.configuration.applicationUri;
|
|
32
|
+
clientDescription.productUri = this.configuration.productUri;
|
|
33
|
+
clientDescription.applicationName = new LocalizedText(void 0, this.configuration.productName);
|
|
34
|
+
clientDescription.applicationType = ApplicationTypeEnum.Client;
|
|
35
|
+
clientDescription.gatewayServerUri = "";
|
|
36
|
+
clientDescription.discoveryProfileUri = "";
|
|
37
|
+
clientDescription.discoveryUrls = new Array();
|
|
38
|
+
const request = new CreateSessionRequest();
|
|
39
|
+
request.requestHeader = this.createRequestHeader();
|
|
40
|
+
request.clientDescription = clientDescription;
|
|
41
|
+
request.serverUri = "";
|
|
42
|
+
request.endpointUrl = this.secureChannel.getEndpointUrl();
|
|
43
|
+
request.sessionName = "";
|
|
44
|
+
request.clientNonce = null;
|
|
45
|
+
request.clientCertificate = null;
|
|
46
|
+
request.requestedSessionTimeout = 6e4;
|
|
47
|
+
request.maxResponseMessageSize = 0;
|
|
48
|
+
console.log("Sending CreateSessionRequest...");
|
|
49
|
+
const response = await this.secureChannel.issueServiceRequest(request);
|
|
50
|
+
if (!response || !(response instanceof CreateSessionResponse)) {
|
|
51
|
+
throw new Error("Invalid response type for CreateSessionRequest");
|
|
52
|
+
}
|
|
53
|
+
const castedResponse = response;
|
|
54
|
+
if (!castedResponse || !castedResponse.sessionId || !castedResponse.authenticationToken) {
|
|
55
|
+
throw new Error("CreateSessionResponse missing SessionId or AuthenticationToken");
|
|
56
|
+
}
|
|
57
|
+
const endpoint = "opc." + this.secureChannel.getEndpointUrl();
|
|
58
|
+
const securityMode = this.secureChannel.getSecurityMode();
|
|
59
|
+
const securityPolicyUri = this.secureChannel.getSecurityPolicy();
|
|
60
|
+
const serverEndpoint = castedResponse?.serverEndpoints?.find((ep) => ep.endpointUrl === endpoint && ep.securityMode === securityMode && ep.securityPolicyUri === securityPolicyUri);
|
|
61
|
+
if (!serverEndpoint) {
|
|
62
|
+
throw new Error(`Server endpoint ${endpoint} not found in CreateSessionResponse`);
|
|
63
|
+
}
|
|
64
|
+
console.log("Session created with id:", castedResponse.sessionId.identifier);
|
|
65
|
+
return {
|
|
66
|
+
sessionId: castedResponse.sessionId.identifier,
|
|
67
|
+
authToken: castedResponse.authenticationToken,
|
|
68
|
+
endpoint: serverEndpoint
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async activateSession(identityToken) {
|
|
72
|
+
const signatureData = new SignatureData();
|
|
73
|
+
signatureData.algorithm = this.secureChannel.getSecurityPolicy();
|
|
74
|
+
signatureData.signature = new Uint8Array(0);
|
|
75
|
+
const request = new ActivateSessionRequest();
|
|
76
|
+
request.requestHeader = this.createRequestHeader();
|
|
77
|
+
request.clientSignature = signatureData;
|
|
78
|
+
request.clientSoftwareCertificates = new Array();
|
|
79
|
+
request.localeIds = ["en-US"];
|
|
80
|
+
request.userIdentityToken = ExtensionObject.newBinary(identityToken);
|
|
81
|
+
request.userTokenSignature = signatureData;
|
|
82
|
+
console.log("Sending ActivateSessionRequest...");
|
|
83
|
+
await this.secureChannel.issueServiceRequest(request);
|
|
84
|
+
console.log("Session activated.");
|
|
85
|
+
}
|
|
86
|
+
recreate(authToken) {
|
|
87
|
+
return new _SessionService(authToken, this.secureChannel, this.configuration);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// src/issuerConfiguration.ts
|
|
92
|
+
var IssuerConfiguration = class _IssuerConfiguration {
|
|
93
|
+
constructor(resourceId, authorityUrl, authorityProfileUri, tokenEndpoint, authorizationEndpoint, requestTypes, scopes) {
|
|
94
|
+
this.resourceId = resourceId;
|
|
95
|
+
this.authorityUrl = authorityUrl;
|
|
96
|
+
this.authorityProfileUri = authorityProfileUri;
|
|
97
|
+
this.tokenEndpoint = tokenEndpoint;
|
|
98
|
+
this.authorizationEndpoint = authorizationEndpoint;
|
|
99
|
+
this.requestTypes = requestTypes;
|
|
100
|
+
this.scopes = scopes;
|
|
101
|
+
}
|
|
102
|
+
static newFrom(issuerEndpointUrl) {
|
|
103
|
+
return new _IssuerConfiguration(
|
|
104
|
+
issuerEndpointUrl["ua.resourceId"],
|
|
105
|
+
issuerEndpointUrl["ua.authorityUrl"],
|
|
106
|
+
issuerEndpointUrl["ua.authorityProfileUri"],
|
|
107
|
+
issuerEndpointUrl["ua.tokenEndpoint"],
|
|
108
|
+
issuerEndpointUrl["ua.authorizationEndpoint"],
|
|
109
|
+
issuerEndpointUrl["ua.requestTypes"],
|
|
110
|
+
issuerEndpointUrl["ua.scopes"]
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// src/sessions/session.ts
|
|
116
|
+
var Session = class {
|
|
117
|
+
constructor(sessionId, authToken, endpoint, sessionServices) {
|
|
118
|
+
this.sessionId = sessionId;
|
|
119
|
+
this.authToken = authToken;
|
|
120
|
+
this.endpoint = endpoint;
|
|
121
|
+
this.sessionServices = sessionServices;
|
|
122
|
+
}
|
|
123
|
+
async activateSession(identity) {
|
|
124
|
+
const token = identity.getUserIdentityToken();
|
|
125
|
+
const tokenType = identity.getTokenType();
|
|
126
|
+
const tokenPolicy = this.endpoint.userIdentityTokens?.find((t) => t.tokenType === tokenType);
|
|
127
|
+
if (!tokenPolicy) {
|
|
128
|
+
throw new Error(`UserIdentityToken of type ${tokenType} not supported by server`);
|
|
129
|
+
}
|
|
130
|
+
token.policyId = tokenPolicy.policyId;
|
|
131
|
+
if (tokenType === UserTokenTypeEnum.IssuedToken) {
|
|
132
|
+
if (!tokenPolicy.issuerEndpointUrl) {
|
|
133
|
+
throw new Error("IssuerEndpointUrl not defined for IssuedToken");
|
|
134
|
+
}
|
|
135
|
+
const issuerEndpointUrl = JSON.parse(tokenPolicy.issuerEndpointUrl);
|
|
136
|
+
const issuerConfig = IssuerConfiguration.newFrom(issuerEndpointUrl);
|
|
137
|
+
const issuerLoginCallback = identity.getIssuerLoginCallback();
|
|
138
|
+
const tokenData = await issuerLoginCallback(issuerConfig);
|
|
139
|
+
const issuerToken = token;
|
|
140
|
+
issuerToken.tokenData = new TextEncoder().encode(tokenData.json);
|
|
141
|
+
}
|
|
142
|
+
await this.sessionServices.activateSession(token);
|
|
143
|
+
}
|
|
144
|
+
getAuthToken() {
|
|
145
|
+
return this.authToken;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// src/sessions/sessionHandler.ts
|
|
150
|
+
var SessionHandler = class {
|
|
151
|
+
sessionServices;
|
|
152
|
+
async createNewSession(identity) {
|
|
153
|
+
const ret = await this.sessionServices.createSession();
|
|
154
|
+
this.sessionServices = this.sessionServices.recreate(ret.authToken);
|
|
155
|
+
const session = new Session(ret.sessionId, ret.authToken, ret.endpoint, this.sessionServices);
|
|
156
|
+
await session.activateSession(identity);
|
|
157
|
+
return session;
|
|
158
|
+
}
|
|
159
|
+
constructor(secureChannel, configuration) {
|
|
160
|
+
this.sessionServices = new SessionService(NodeId.newTwoByte(0), secureChannel, configuration);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// src/services/attributeServiceAttributes.ts
|
|
165
|
+
var AttrIdValue = 13;
|
|
166
|
+
|
|
167
|
+
// src/services/attributeService.ts
|
|
168
|
+
var AttributeService = class extends ServiceBase {
|
|
169
|
+
async ReadValue(nodeIds) {
|
|
170
|
+
const readValueIds = nodeIds.map((ni) => {
|
|
171
|
+
const readValueId = new ReadValueId();
|
|
172
|
+
readValueId.nodeId = ni;
|
|
173
|
+
readValueId.attributeId = AttrIdValue;
|
|
174
|
+
readValueId.indexRange = "";
|
|
175
|
+
readValueId.dataEncoding = new QualifiedName(0, "");
|
|
176
|
+
return readValueId;
|
|
177
|
+
});
|
|
178
|
+
const request = new ReadRequest();
|
|
179
|
+
request.requestHeader = this.createRequestHeader();
|
|
180
|
+
request.maxAge = 6e4;
|
|
181
|
+
request.timestampsToReturn = TimestampsToReturnEnum.Source;
|
|
182
|
+
request.nodesToRead = readValueIds;
|
|
183
|
+
console.log("Sending ReadRequest...");
|
|
184
|
+
const response = await this.secureChannel.issueServiceRequest(request);
|
|
185
|
+
const results = new Array();
|
|
186
|
+
for (let dataValue of response.results ?? []) {
|
|
187
|
+
const result = {
|
|
188
|
+
status: dataValue.statusCode?.toString() ?? "Unknown",
|
|
189
|
+
value: dataValue.value
|
|
190
|
+
};
|
|
191
|
+
results.push(result);
|
|
192
|
+
}
|
|
193
|
+
return results;
|
|
194
|
+
}
|
|
195
|
+
constructor(authToken, secureChannel) {
|
|
196
|
+
super(authToken, secureChannel);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// src/readValueResult.ts
|
|
201
|
+
var ReadValueResult = class {
|
|
202
|
+
constructor(value, status) {
|
|
203
|
+
this.value = value;
|
|
204
|
+
this.status = status;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// src/subscriptionHandlerEntry.ts
|
|
209
|
+
var SubscriptionHandlerEntry = class {
|
|
210
|
+
constructor(subscriptionId, handle, id, callback) {
|
|
211
|
+
this.subscriptionId = subscriptionId;
|
|
212
|
+
this.handle = handle;
|
|
213
|
+
this.id = id;
|
|
214
|
+
this.callback = callback;
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// src/subscriptionHandler.ts
|
|
219
|
+
var SubscriptionHandler = class {
|
|
220
|
+
constructor(subscriptionService, monitoredItemService) {
|
|
221
|
+
this.subscriptionService = subscriptionService;
|
|
222
|
+
this.monitoredItemService = monitoredItemService;
|
|
223
|
+
}
|
|
224
|
+
entries = new Array();
|
|
225
|
+
nextHandle = 0;
|
|
226
|
+
async subscribe(ids, callback) {
|
|
227
|
+
if (this.entries.length > 0) {
|
|
228
|
+
throw new Error("Subscribing more than once is not implemented");
|
|
229
|
+
}
|
|
230
|
+
const subscriptionId = await this.subscriptionService.createSubscription();
|
|
231
|
+
const items = [];
|
|
232
|
+
for (let id of ids) {
|
|
233
|
+
const entry = new SubscriptionHandlerEntry(
|
|
234
|
+
subscriptionId,
|
|
235
|
+
this.nextHandle++,
|
|
236
|
+
id,
|
|
237
|
+
callback
|
|
238
|
+
);
|
|
239
|
+
this.entries.push(entry);
|
|
240
|
+
const item = {
|
|
241
|
+
id: id.toNodeId(),
|
|
242
|
+
handle: entry.handle
|
|
243
|
+
};
|
|
244
|
+
items.push(item);
|
|
245
|
+
}
|
|
246
|
+
await this.monitoredItemService.createMonitoredItems(subscriptionId, items);
|
|
247
|
+
this.publish([]);
|
|
248
|
+
}
|
|
249
|
+
async publish(acknowledgeSequenceNumbers) {
|
|
250
|
+
const acknowledgements = [];
|
|
251
|
+
for (let i = 0; i < acknowledgeSequenceNumbers.length; i++) {
|
|
252
|
+
const acknowledgement = new SubscriptionAcknowledgement();
|
|
253
|
+
acknowledgement.subscriptionId = this.entries[i].subscriptionId;
|
|
254
|
+
acknowledgement.sequenceNumber = acknowledgeSequenceNumbers[i];
|
|
255
|
+
acknowledgements.push(acknowledgement);
|
|
256
|
+
}
|
|
257
|
+
const response = await this.subscriptionService.publish(acknowledgements);
|
|
258
|
+
const messagesToAcknowledge = response.notificationMessage.sequenceNumber;
|
|
259
|
+
const notificationDatas = response.notificationMessage.notificationData;
|
|
260
|
+
for (let notificationData of notificationDatas) {
|
|
261
|
+
const decodedData = notificationData.data;
|
|
262
|
+
const typeNodeId = notificationData.typeId;
|
|
263
|
+
if (typeNodeId.namespace === 0 && typeNodeId.identifier === 811) {
|
|
264
|
+
const dataChangeNotification = decodedData;
|
|
265
|
+
for (let item of dataChangeNotification.monitoredItems) {
|
|
266
|
+
const clientHandle = item.clientHandle;
|
|
267
|
+
const value = item.value;
|
|
268
|
+
const entry = this.entries.find((e) => e.handle == clientHandle);
|
|
269
|
+
entry?.callback([{
|
|
270
|
+
id: entry.id,
|
|
271
|
+
value: value.value?.value
|
|
272
|
+
}]);
|
|
273
|
+
}
|
|
274
|
+
} else {
|
|
275
|
+
console.log(`The change notification data type ${typeNodeId.namespace}:${typeNodeId.identifier} is not supported.`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
setTimeout(() => {
|
|
279
|
+
this.publish([messagesToAcknowledge]);
|
|
280
|
+
}, 500);
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
var SubscriptionService = class extends ServiceBase {
|
|
284
|
+
// https://reference.opcfoundation.org/Core/Part4/v105/docs/5.14.2
|
|
285
|
+
async createSubscription() {
|
|
286
|
+
const request = new CreateSubscriptionRequest();
|
|
287
|
+
request.requestHeader = this.createRequestHeader();
|
|
288
|
+
request.requestedPublishingInterval = 2e3;
|
|
289
|
+
request.requestedLifetimeCount = 36e4;
|
|
290
|
+
request.requestedMaxKeepAliveCount = 6e4;
|
|
291
|
+
request.maxNotificationsPerPublish = 200;
|
|
292
|
+
request.publishingEnabled = true;
|
|
293
|
+
request.priority = 1;
|
|
294
|
+
console.log("Sending createSubscription...");
|
|
295
|
+
const response = await this.secureChannel.issueServiceRequest(request);
|
|
296
|
+
console.log("Subscription created with id:", response.subscriptionId);
|
|
297
|
+
return response.subscriptionId;
|
|
298
|
+
}
|
|
299
|
+
// https://reference.opcfoundation.org/Core/Part4/v105/docs/5.14.5
|
|
300
|
+
async publish(acknowledgements) {
|
|
301
|
+
const request = new PublishRequest();
|
|
302
|
+
request.requestHeader = this.createRequestHeader();
|
|
303
|
+
request.subscriptionAcknowledgements = acknowledgements;
|
|
304
|
+
console.log("Sending publish...");
|
|
305
|
+
const response = await this.secureChannel.issueServiceRequest(request);
|
|
306
|
+
console.log("Received publish response.");
|
|
307
|
+
return response;
|
|
308
|
+
}
|
|
309
|
+
constructor(authToken, secureChannel) {
|
|
310
|
+
super(authToken, secureChannel);
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
var MonitoredItemService = class extends ServiceBase {
|
|
314
|
+
async createMonitoredItems(subscriptionId, ids) {
|
|
315
|
+
const items = ids.map((ni) => {
|
|
316
|
+
const readValueId = new ReadValueId();
|
|
317
|
+
readValueId.nodeId = ni.id;
|
|
318
|
+
readValueId.attributeId = AttrIdValue;
|
|
319
|
+
readValueId.indexRange = "";
|
|
320
|
+
readValueId.dataEncoding = new QualifiedName(0, "");
|
|
321
|
+
const monitoringParameters = new MonitoringParameters();
|
|
322
|
+
monitoringParameters.clientHandle = ni.handle;
|
|
323
|
+
monitoringParameters.samplingInterval = 1e3;
|
|
324
|
+
monitoringParameters.filter = ExtensionObject.newEmpty();
|
|
325
|
+
monitoringParameters.queueSize = 100;
|
|
326
|
+
monitoringParameters.discardOldest = true;
|
|
327
|
+
const monitoredItemCreateRequest = new MonitoredItemCreateRequest();
|
|
328
|
+
monitoredItemCreateRequest.itemToMonitor = readValueId;
|
|
329
|
+
monitoredItemCreateRequest.requestedParameters = monitoringParameters;
|
|
330
|
+
return monitoredItemCreateRequest;
|
|
331
|
+
});
|
|
332
|
+
const request = new CreateMonitoredItemsRequest();
|
|
333
|
+
request.requestHeader = this.createRequestHeader();
|
|
334
|
+
request.subscriptionId = subscriptionId;
|
|
335
|
+
request.timestampsToReturn = TimestampsToReturnEnum.Source;
|
|
336
|
+
request.itemsToCreate = items;
|
|
337
|
+
console.log("Sending createMonitoredItems...");
|
|
338
|
+
await this.secureChannel.issueServiceRequest(request);
|
|
339
|
+
}
|
|
340
|
+
constructor(authToken, secureChannel) {
|
|
341
|
+
super(authToken, secureChannel);
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// src/client.ts
|
|
346
|
+
var Client = class {
|
|
347
|
+
constructor(endpointUrl, configuration, identity) {
|
|
348
|
+
this.configuration = configuration;
|
|
349
|
+
this.identity = identity;
|
|
350
|
+
this.endpointUrl = endpointUrl;
|
|
351
|
+
}
|
|
352
|
+
endpointUrl;
|
|
353
|
+
channel;
|
|
354
|
+
session;
|
|
355
|
+
subscriptionHandler;
|
|
356
|
+
getSession() {
|
|
357
|
+
if (!this.session) {
|
|
358
|
+
throw new Error("No session available");
|
|
359
|
+
}
|
|
360
|
+
return this.session;
|
|
361
|
+
}
|
|
362
|
+
async connect() {
|
|
363
|
+
const channel = ChannelFactory.createChannel(this.endpointUrl);
|
|
364
|
+
let connected = false;
|
|
365
|
+
while (!connected) {
|
|
366
|
+
console.log(`Connecting to OPC UA server at ${this.endpointUrl}...`);
|
|
367
|
+
connected = await channel.connect();
|
|
368
|
+
if (!connected) {
|
|
369
|
+
console.log("Connection failed, retrying in 2 seconds...");
|
|
370
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
console.log("Connected to OPC UA server.");
|
|
374
|
+
this.channel = new SecureChannel(channel, this.configuration);
|
|
375
|
+
await this.channel.openSecureChannelRequest();
|
|
376
|
+
const sessionHandler = new SessionHandler(this.channel, this.configuration);
|
|
377
|
+
this.session = await sessionHandler.createNewSession(this.identity);
|
|
378
|
+
this.subscriptionHandler = new SubscriptionHandler(
|
|
379
|
+
new SubscriptionService(this.session.getAuthToken(), this.channel),
|
|
380
|
+
new MonitoredItemService(this.session.getAuthToken(), this.channel)
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
async disconnect() {
|
|
384
|
+
console.log("Disconnecting from OPC UA server...");
|
|
385
|
+
if (this.channel) {
|
|
386
|
+
await this.channel.disconnect();
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
async read(ids) {
|
|
390
|
+
const service = new AttributeService(this.getSession().getAuthToken(), this.channel);
|
|
391
|
+
const result = await service.ReadValue(ids.map((i) => i.toNodeId()));
|
|
392
|
+
return result.map((r) => new ReadValueResult(r.value, r.status));
|
|
393
|
+
}
|
|
394
|
+
async subscribe(ids, callback) {
|
|
395
|
+
this.subscriptionHandler?.subscribe(ids, callback);
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
// ../base/src/codecs/codecError.ts
|
|
400
|
+
var CodecError = class _CodecError extends Error {
|
|
401
|
+
/**
|
|
402
|
+
* The encoding ID involved in the error (if applicable).
|
|
403
|
+
*/
|
|
404
|
+
encodingId;
|
|
405
|
+
/**
|
|
406
|
+
* The encoding format where the error occurred (Binary, Xml, Json).
|
|
407
|
+
*/
|
|
408
|
+
format;
|
|
409
|
+
/**
|
|
410
|
+
* Suggested action to resolve the error.
|
|
411
|
+
*/
|
|
412
|
+
suggestedAction;
|
|
413
|
+
/**
|
|
414
|
+
* The type name involved in the error (if applicable).
|
|
415
|
+
*/
|
|
416
|
+
typeName;
|
|
417
|
+
/**
|
|
418
|
+
* Original error that caused this codec error (if any).
|
|
419
|
+
*/
|
|
420
|
+
cause;
|
|
421
|
+
constructor(message, options) {
|
|
422
|
+
super(message);
|
|
423
|
+
this.name = "CodecError";
|
|
424
|
+
this.encodingId = options?.encodingId;
|
|
425
|
+
this.format = options?.format;
|
|
426
|
+
this.suggestedAction = options?.suggestedAction;
|
|
427
|
+
this.typeName = options?.typeName;
|
|
428
|
+
this.cause = options?.cause;
|
|
429
|
+
if (Error.captureStackTrace) {
|
|
430
|
+
Error.captureStackTrace(this, _CodecError);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Returns a detailed error message including all context.
|
|
435
|
+
*/
|
|
436
|
+
toString() {
|
|
437
|
+
let result = `${this.name}: ${this.message}`;
|
|
438
|
+
if (this.encodingId) {
|
|
439
|
+
result += `
|
|
440
|
+
Encoding ID: ${this.encodingId}`;
|
|
441
|
+
}
|
|
442
|
+
if (this.format) {
|
|
443
|
+
result += `
|
|
444
|
+
Format: ${this.format}`;
|
|
445
|
+
}
|
|
446
|
+
if (this.typeName) {
|
|
447
|
+
result += `
|
|
448
|
+
Type: ${this.typeName}`;
|
|
449
|
+
}
|
|
450
|
+
if (this.suggestedAction) {
|
|
451
|
+
result += `
|
|
452
|
+
Suggested Action: ${this.suggestedAction}`;
|
|
453
|
+
}
|
|
454
|
+
return result;
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
// ../base/src/codecs/binary/binaryWriter.ts
|
|
459
|
+
var EPOCH_DIFF_MS = 11644473600000n;
|
|
460
|
+
var TICKS_PER_MS = 10000n;
|
|
461
|
+
function selectNodeIdEncodingFormat(nodeId) {
|
|
462
|
+
if (nodeId.identifierType === 0 /* Numeric */) {
|
|
463
|
+
const id = nodeId.identifier;
|
|
464
|
+
const ns = nodeId.namespace;
|
|
465
|
+
if (ns === 0 && id >= 0 && id <= 255) {
|
|
466
|
+
return 0 /* TwoByte */;
|
|
467
|
+
}
|
|
468
|
+
if (ns >= 0 && ns <= 255 && id >= 0 && id <= 65535) {
|
|
469
|
+
return 1 /* FourByte */;
|
|
470
|
+
}
|
|
471
|
+
return 2 /* Numeric */;
|
|
472
|
+
}
|
|
473
|
+
if (nodeId.identifierType === 1 /* String */) {
|
|
474
|
+
return 3 /* String */;
|
|
475
|
+
}
|
|
476
|
+
if (nodeId.identifierType === 2 /* Guid */) {
|
|
477
|
+
return 4 /* Guid */;
|
|
478
|
+
}
|
|
479
|
+
if (nodeId.identifierType === 3 /* ByteString */) {
|
|
480
|
+
return 5 /* ByteString */;
|
|
481
|
+
}
|
|
482
|
+
throw new CodecError(`Invalid NodeId identifier type: ${nodeId.identifierType}`);
|
|
483
|
+
}
|
|
484
|
+
var ExpandedNodeIdMask = {
|
|
485
|
+
ServerIndexFlag: 64,
|
|
486
|
+
NamespaceUriFlag: 128
|
|
487
|
+
};
|
|
488
|
+
var LocalizedTextMask = {
|
|
489
|
+
LocaleFlag: 1,
|
|
490
|
+
TextFlag: 2
|
|
491
|
+
};
|
|
492
|
+
var DataValueMaskBits = {
|
|
493
|
+
Value: 1,
|
|
494
|
+
StatusCode: 2,
|
|
495
|
+
SourceTimestamp: 4,
|
|
496
|
+
ServerTimestamp: 8,
|
|
497
|
+
SourcePicoseconds: 16,
|
|
498
|
+
ServerPicoseconds: 32
|
|
499
|
+
};
|
|
500
|
+
var DiagnosticInfoMaskBits = {
|
|
501
|
+
SymbolicId: 1,
|
|
502
|
+
NamespaceUri: 2,
|
|
503
|
+
LocalizedText: 4,
|
|
504
|
+
Locale: 8,
|
|
505
|
+
AdditionalInfo: 16,
|
|
506
|
+
InnerStatusCode: 32,
|
|
507
|
+
InnerDiagnosticInfo: 64
|
|
508
|
+
};
|
|
509
|
+
var VariantMask = {
|
|
510
|
+
TypeMask: 63,
|
|
511
|
+
ArrayDimensions: 64,
|
|
512
|
+
Array: 128
|
|
513
|
+
};
|
|
514
|
+
var BinaryWriter = class {
|
|
515
|
+
buffer;
|
|
516
|
+
position;
|
|
517
|
+
getData() {
|
|
518
|
+
return this.buffer.subarray(0, this.position);
|
|
519
|
+
}
|
|
520
|
+
/** Returns the number of bytes written so far. */
|
|
521
|
+
getLength() {
|
|
522
|
+
return this.position;
|
|
523
|
+
}
|
|
524
|
+
/** Appends raw bytes to the buffer. */
|
|
525
|
+
writeBytes(data) {
|
|
526
|
+
this.ensureCapacity(data.length);
|
|
527
|
+
this.buffer.set(data, this.position);
|
|
528
|
+
this.position += data.length;
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Overwrites bytes in the buffer starting at the given position.
|
|
532
|
+
* Also truncates the written length to `offset + data.length` if that
|
|
533
|
+
* is less than the current position (i.e. replaces and trims the tail).
|
|
534
|
+
*/
|
|
535
|
+
writeBytesAt(data, offset) {
|
|
536
|
+
const end = offset + data.length;
|
|
537
|
+
this.ensureCapacity(Math.max(0, end - this.position));
|
|
538
|
+
this.buffer.set(data, offset);
|
|
539
|
+
if (end > this.position) {
|
|
540
|
+
this.position = end;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Inserts bytes at the given offset, shifting all subsequent content right.
|
|
545
|
+
*/
|
|
546
|
+
insertBytesAt(data, offset) {
|
|
547
|
+
this.ensureCapacity(data.length);
|
|
548
|
+
this.buffer.copyWithin(offset + data.length, offset, this.position);
|
|
549
|
+
this.buffer.set(data, offset);
|
|
550
|
+
this.position += data.length;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Overwrites a UInt32 (little-endian) at the given byte offset without
|
|
554
|
+
* advancing the write position.
|
|
555
|
+
*/
|
|
556
|
+
writeUInt32At(value, offset) {
|
|
557
|
+
if (!Number.isInteger(value) || value < 0 || value > 4294967295) {
|
|
558
|
+
throw new CodecError(`UInt32 value ${value} out of range [0, 4294967295]`);
|
|
559
|
+
}
|
|
560
|
+
this.buffer.writeUInt32LE(value, offset);
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Ensure buffer has enough capacity, growing if necessary.
|
|
564
|
+
*/
|
|
565
|
+
ensureCapacity(additionalBytes) {
|
|
566
|
+
const required = this.position + additionalBytes;
|
|
567
|
+
if (required > this.buffer.length) {
|
|
568
|
+
const newSize = Math.max(required, this.buffer.length * 2);
|
|
569
|
+
const newBuffer = Buffer.allocUnsafe(newSize);
|
|
570
|
+
this.buffer.copy(newBuffer, 0, 0, this.position);
|
|
571
|
+
this.buffer = newBuffer;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
writeBoolean(value) {
|
|
575
|
+
this.ensureCapacity(1);
|
|
576
|
+
this.buffer.writeUInt8(value ? 1 : 0, this.position);
|
|
577
|
+
this.position += 1;
|
|
578
|
+
}
|
|
579
|
+
writeByte(value) {
|
|
580
|
+
if (value < 0 || value > 255) {
|
|
581
|
+
throw new CodecError(`Byte value ${value} out of range [0, 255]`);
|
|
582
|
+
}
|
|
583
|
+
this.ensureCapacity(1);
|
|
584
|
+
this.buffer.writeUInt8(value, this.position);
|
|
585
|
+
this.position += 1;
|
|
586
|
+
}
|
|
587
|
+
writeSByte(value) {
|
|
588
|
+
if (value < -128 || value > 127) {
|
|
589
|
+
throw new CodecError(`SByte value ${value} out of range [-128, 127]`);
|
|
590
|
+
}
|
|
591
|
+
this.ensureCapacity(1);
|
|
592
|
+
this.buffer.writeInt8(value, this.position);
|
|
593
|
+
this.position += 1;
|
|
594
|
+
}
|
|
595
|
+
writeInt16(value) {
|
|
596
|
+
if (value < -32768 || value > 32767) {
|
|
597
|
+
throw new CodecError(`Int16 value ${value} out of range [-32768, 32767]`);
|
|
598
|
+
}
|
|
599
|
+
this.ensureCapacity(2);
|
|
600
|
+
this.buffer.writeInt16LE(value, this.position);
|
|
601
|
+
this.position += 2;
|
|
602
|
+
}
|
|
603
|
+
writeUInt16(value) {
|
|
604
|
+
if (value < 0 || value > 65535) {
|
|
605
|
+
throw new CodecError(`UInt16 value ${value} out of range [0, 65535]`);
|
|
606
|
+
}
|
|
607
|
+
this.ensureCapacity(2);
|
|
608
|
+
this.buffer.writeUInt16LE(value, this.position);
|
|
609
|
+
this.position += 2;
|
|
610
|
+
}
|
|
611
|
+
writeInt32(value) {
|
|
612
|
+
if (!Number.isInteger(value) || value < -2147483648 || value > 2147483647) {
|
|
613
|
+
throw new CodecError(`Int32 value ${value} out of range [-2147483648, 2147483647]`);
|
|
614
|
+
}
|
|
615
|
+
this.ensureCapacity(4);
|
|
616
|
+
this.buffer.writeInt32LE(value, this.position);
|
|
617
|
+
this.position += 4;
|
|
618
|
+
}
|
|
619
|
+
writeUInt32(value) {
|
|
620
|
+
if (!Number.isInteger(value) || value < 0 || value > 4294967295) {
|
|
621
|
+
throw new CodecError(`UInt32 value ${value} out of range [0, 4294967295]`);
|
|
622
|
+
}
|
|
623
|
+
this.ensureCapacity(4);
|
|
624
|
+
this.buffer.writeUInt32LE(value, this.position);
|
|
625
|
+
this.position += 4;
|
|
626
|
+
}
|
|
627
|
+
writeInt64(value) {
|
|
628
|
+
this.ensureCapacity(8);
|
|
629
|
+
this.buffer.writeBigInt64LE(value, this.position);
|
|
630
|
+
this.position += 8;
|
|
631
|
+
}
|
|
632
|
+
writeUInt64(value) {
|
|
633
|
+
this.ensureCapacity(8);
|
|
634
|
+
this.buffer.writeBigUInt64LE(value, this.position);
|
|
635
|
+
this.position += 8;
|
|
636
|
+
}
|
|
637
|
+
writeFloat(value) {
|
|
638
|
+
this.ensureCapacity(4);
|
|
639
|
+
this.buffer.writeFloatLE(value, this.position);
|
|
640
|
+
this.position += 4;
|
|
641
|
+
}
|
|
642
|
+
writeDouble(value) {
|
|
643
|
+
this.ensureCapacity(8);
|
|
644
|
+
this.buffer.writeDoubleLE(value, this.position);
|
|
645
|
+
this.position += 8;
|
|
646
|
+
}
|
|
647
|
+
writeString(value) {
|
|
648
|
+
if (value == null) {
|
|
649
|
+
this.writeInt32(-1);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
const utf8Bytes = Buffer.from(value, "utf8");
|
|
653
|
+
const length = utf8Bytes.length;
|
|
654
|
+
if (length > 16777216) {
|
|
655
|
+
throw new CodecError(
|
|
656
|
+
`String length ${length} exceeds maximum allowed length of 16,777,216 bytes`,
|
|
657
|
+
{ format: "Binary", suggestedAction: "Reduce string length" }
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
this.writeInt32(length);
|
|
661
|
+
this.ensureCapacity(length);
|
|
662
|
+
utf8Bytes.copy(this.buffer, this.position);
|
|
663
|
+
this.position += length;
|
|
664
|
+
}
|
|
665
|
+
writeDateTime(value) {
|
|
666
|
+
const jsTimestamp = BigInt(value.getTime());
|
|
667
|
+
const opcTimestamp = (jsTimestamp + EPOCH_DIFF_MS) * TICKS_PER_MS;
|
|
668
|
+
this.writeInt64(opcTimestamp);
|
|
669
|
+
}
|
|
670
|
+
writeGuid(value) {
|
|
671
|
+
const hex = value.replace(/-/g, "");
|
|
672
|
+
if (hex.length !== 32) {
|
|
673
|
+
throw new CodecError(`Invalid GUID format: ${value}`);
|
|
674
|
+
}
|
|
675
|
+
this.ensureCapacity(16);
|
|
676
|
+
const data1 = parseInt(hex.substr(0, 8), 16);
|
|
677
|
+
this.buffer.writeUInt32LE(data1, this.position);
|
|
678
|
+
const data2 = parseInt(hex.substr(8, 4), 16);
|
|
679
|
+
this.buffer.writeUInt16LE(data2, this.position + 4);
|
|
680
|
+
const data3 = parseInt(hex.substr(12, 4), 16);
|
|
681
|
+
this.buffer.writeUInt16LE(data3, this.position + 6);
|
|
682
|
+
for (let i = 0; i < 8; i++) {
|
|
683
|
+
const byte = parseInt(hex.substr(16 + i * 2, 2), 16);
|
|
684
|
+
this.buffer.writeUInt8(byte, this.position + 8 + i);
|
|
685
|
+
}
|
|
686
|
+
this.position += 16;
|
|
687
|
+
}
|
|
688
|
+
writeByteString(value) {
|
|
689
|
+
if (value == null) {
|
|
690
|
+
this.writeInt32(-1);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
const length = value.length;
|
|
694
|
+
if (length > 16777216) {
|
|
695
|
+
throw new CodecError(
|
|
696
|
+
`ByteString length ${length} exceeds maximum allowed length of 16,777,216 bytes`,
|
|
697
|
+
{ format: "Binary", suggestedAction: "Reduce ByteString length" }
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
this.writeInt32(length);
|
|
701
|
+
this.ensureCapacity(length);
|
|
702
|
+
if (Buffer.isBuffer(value)) {
|
|
703
|
+
value.copy(this.buffer, this.position);
|
|
704
|
+
} else {
|
|
705
|
+
this.buffer.set(value, this.position);
|
|
706
|
+
}
|
|
707
|
+
this.position += length;
|
|
708
|
+
}
|
|
709
|
+
writeXmlElement(value) {
|
|
710
|
+
this.writeString(value);
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Write an array with Int32 length prefix.
|
|
714
|
+
* Per FR-011: -1 = null, 0 = empty, positive = element count
|
|
715
|
+
* Per FR-019: Maximum array length is 2,147,483,647 elements
|
|
716
|
+
*
|
|
717
|
+
* @param array The array to encode (undefined for null array)
|
|
718
|
+
* @param encodeElement Function to encode each element
|
|
719
|
+
* @throws {CodecError} if array length exceeds Int32 maximum
|
|
720
|
+
* @see OPC 10000-6 Section 5.2.5 - Arrays
|
|
721
|
+
*/
|
|
722
|
+
writeArray(array, encodeElement) {
|
|
723
|
+
if (array === void 0) {
|
|
724
|
+
this.writeInt32(-1);
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
const length = array.length;
|
|
728
|
+
if (length > 2147483647) {
|
|
729
|
+
throw new CodecError(
|
|
730
|
+
`Array length ${length} exceeds maximum allowed length of 2,147,483,647 elements`,
|
|
731
|
+
{ format: "Binary", suggestedAction: "Reduce array size" }
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
this.writeInt32(length);
|
|
735
|
+
for (const element of array) {
|
|
736
|
+
encodeElement(this, element);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
// === Complex type write methods ===
|
|
740
|
+
/**
|
|
741
|
+
* Encode a NodeId in binary format using the most compact representation.
|
|
742
|
+
* @see OPC 10000-6 Tables 16-19
|
|
743
|
+
*/
|
|
744
|
+
writeNodeId(value) {
|
|
745
|
+
const format = selectNodeIdEncodingFormat(value);
|
|
746
|
+
switch (format) {
|
|
747
|
+
case 0 /* TwoByte */:
|
|
748
|
+
this.writeByte(0 /* TwoByte */);
|
|
749
|
+
this.writeByte(value.identifier);
|
|
750
|
+
break;
|
|
751
|
+
case 1 /* FourByte */:
|
|
752
|
+
this.writeByte(1 /* FourByte */);
|
|
753
|
+
this.writeByte(value.namespace);
|
|
754
|
+
this.writeUInt16(value.identifier);
|
|
755
|
+
break;
|
|
756
|
+
case 2 /* Numeric */:
|
|
757
|
+
this.writeByte(2 /* Numeric */);
|
|
758
|
+
this.writeUInt16(value.namespace);
|
|
759
|
+
this.writeUInt32(value.identifier);
|
|
760
|
+
break;
|
|
761
|
+
case 3 /* String */:
|
|
762
|
+
this.writeByte(3 /* String */);
|
|
763
|
+
this.writeUInt16(value.namespace);
|
|
764
|
+
this.writeString(value.identifier);
|
|
765
|
+
break;
|
|
766
|
+
case 4 /* Guid */:
|
|
767
|
+
this.writeByte(4 /* Guid */);
|
|
768
|
+
this.writeUInt16(value.namespace);
|
|
769
|
+
this.writeGuid(value.identifier);
|
|
770
|
+
break;
|
|
771
|
+
case 5 /* ByteString */: {
|
|
772
|
+
this.writeByte(5 /* ByteString */);
|
|
773
|
+
this.writeUInt16(value.namespace);
|
|
774
|
+
const ident = value.identifier;
|
|
775
|
+
const buf = ident instanceof Uint8Array && !(ident instanceof Buffer) ? Buffer.from(ident) : ident;
|
|
776
|
+
this.writeByteString(buf);
|
|
777
|
+
break;
|
|
778
|
+
}
|
|
779
|
+
default:
|
|
780
|
+
throw new CodecError(`Unsupported NodeId encoding format: ${format}`);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Encode an ExpandedNodeId in binary format.
|
|
785
|
+
* @see OPC 10000-6 Table 20
|
|
786
|
+
*/
|
|
787
|
+
writeExpandedNodeId(value) {
|
|
788
|
+
const startPos = this.position;
|
|
789
|
+
this.writeNodeId(value);
|
|
790
|
+
let encodingByte = this.buffer[startPos];
|
|
791
|
+
if (value.namespaceUri !== void 0) {
|
|
792
|
+
encodingByte |= ExpandedNodeIdMask.NamespaceUriFlag;
|
|
793
|
+
}
|
|
794
|
+
if (value.serverIndex !== void 0) {
|
|
795
|
+
encodingByte |= ExpandedNodeIdMask.ServerIndexFlag;
|
|
796
|
+
}
|
|
797
|
+
this.buffer[startPos] = encodingByte;
|
|
798
|
+
if (value.namespaceUri !== void 0) {
|
|
799
|
+
this.writeString(value.namespaceUri);
|
|
800
|
+
}
|
|
801
|
+
if (value.serverIndex !== void 0) {
|
|
802
|
+
this.writeUInt32(value.serverIndex);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Encode a StatusCode as a UInt32.
|
|
807
|
+
* @see OPC 10000-6 Section 5.2.2.16
|
|
808
|
+
*/
|
|
809
|
+
writeStatusCode(value) {
|
|
810
|
+
this.writeUInt32(value);
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Encode a QualifiedName as NamespaceIndex (UInt16) + Name (String).
|
|
814
|
+
* @see OPC 10000-6 Table 8
|
|
815
|
+
*/
|
|
816
|
+
writeQualifiedName(value) {
|
|
817
|
+
this.writeUInt16(value.namespaceIndex);
|
|
818
|
+
this.writeString(value.name);
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Encode a LocalizedText with optional locale and text.
|
|
822
|
+
* @see OPC 10000-6 Table 9
|
|
823
|
+
*/
|
|
824
|
+
writeLocalizedText(value) {
|
|
825
|
+
let encodingMask = 0;
|
|
826
|
+
if (value.locale !== void 0 && value.locale !== "") {
|
|
827
|
+
encodingMask |= LocalizedTextMask.LocaleFlag;
|
|
828
|
+
}
|
|
829
|
+
if (value.text !== "") {
|
|
830
|
+
encodingMask |= LocalizedTextMask.TextFlag;
|
|
831
|
+
}
|
|
832
|
+
this.writeByte(encodingMask);
|
|
833
|
+
if (encodingMask & LocalizedTextMask.LocaleFlag) {
|
|
834
|
+
this.writeString(value.locale);
|
|
835
|
+
}
|
|
836
|
+
if (encodingMask & LocalizedTextMask.TextFlag) {
|
|
837
|
+
this.writeString(value.text);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Encode an ExtensionObject with its TypeId and body.
|
|
842
|
+
* @see OPC 10000-6 Section 5.2.2.15
|
|
843
|
+
*/
|
|
844
|
+
writeExtensionObject(value, encoder) {
|
|
845
|
+
const typeId = value.typeId;
|
|
846
|
+
if ("namespaceUri" in typeId || "serverIndex" in typeId) {
|
|
847
|
+
this.writeExpandedNodeId(typeId);
|
|
848
|
+
} else {
|
|
849
|
+
this.writeNodeId(typeId);
|
|
850
|
+
}
|
|
851
|
+
this.writeByte(value.encoding);
|
|
852
|
+
switch (value.encoding) {
|
|
853
|
+
case 0 /* None */:
|
|
854
|
+
break;
|
|
855
|
+
case 1 /* Binary */:
|
|
856
|
+
if (!value.data) {
|
|
857
|
+
throw new CodecError("ExtensionObject with Binary encoding must have data");
|
|
858
|
+
}
|
|
859
|
+
const binaryData = encoder.encodeWithoutId(value.data, "binary");
|
|
860
|
+
this.writeByteString(binaryData);
|
|
861
|
+
break;
|
|
862
|
+
case 2 /* Xml */:
|
|
863
|
+
if (!value.data) {
|
|
864
|
+
throw new CodecError("ExtensionObject with Xml encoding must have data");
|
|
865
|
+
}
|
|
866
|
+
const xmlString = encoder.encodeWithoutId(value.data, "xml");
|
|
867
|
+
this.writeXmlElement(xmlString);
|
|
868
|
+
break;
|
|
869
|
+
default:
|
|
870
|
+
throw new CodecError(`Invalid ExtensionObject encoding: ${value.encoding}`);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Encode a DataValue with optional fields controlled by an encoding mask.
|
|
875
|
+
* @see OPC 10000-6 Table 26
|
|
876
|
+
*/
|
|
877
|
+
writeDataValue(value, encoder) {
|
|
878
|
+
let encodingMask = 0;
|
|
879
|
+
if (value.value !== null && value.value !== void 0) {
|
|
880
|
+
encodingMask |= DataValueMaskBits.Value;
|
|
881
|
+
}
|
|
882
|
+
if (value.statusCode !== null) {
|
|
883
|
+
encodingMask |= DataValueMaskBits.StatusCode;
|
|
884
|
+
}
|
|
885
|
+
if (value.sourceTimestamp !== null) {
|
|
886
|
+
encodingMask |= DataValueMaskBits.SourceTimestamp;
|
|
887
|
+
}
|
|
888
|
+
if (value.serverTimestamp !== null) {
|
|
889
|
+
encodingMask |= DataValueMaskBits.ServerTimestamp;
|
|
890
|
+
}
|
|
891
|
+
if (value.sourcePicoseconds !== null) {
|
|
892
|
+
encodingMask |= DataValueMaskBits.SourcePicoseconds;
|
|
893
|
+
}
|
|
894
|
+
if (value.serverPicoseconds !== null) {
|
|
895
|
+
encodingMask |= DataValueMaskBits.ServerPicoseconds;
|
|
896
|
+
}
|
|
897
|
+
this.writeByte(encodingMask);
|
|
898
|
+
if (encodingMask & DataValueMaskBits.Value) {
|
|
899
|
+
this.writeVariant(value.value, encoder);
|
|
900
|
+
}
|
|
901
|
+
if (encodingMask & DataValueMaskBits.StatusCode) {
|
|
902
|
+
this.writeUInt32(value.statusCode ?? 0 /* Good */);
|
|
903
|
+
}
|
|
904
|
+
if (encodingMask & DataValueMaskBits.SourceTimestamp) {
|
|
905
|
+
this.writeDateTime(value.sourceTimestamp);
|
|
906
|
+
}
|
|
907
|
+
if (encodingMask & DataValueMaskBits.ServerTimestamp) {
|
|
908
|
+
this.writeDateTime(value.serverTimestamp);
|
|
909
|
+
}
|
|
910
|
+
if (encodingMask & DataValueMaskBits.SourcePicoseconds) {
|
|
911
|
+
this.writeUInt16(value.sourcePicoseconds);
|
|
912
|
+
}
|
|
913
|
+
if (encodingMask & DataValueMaskBits.ServerPicoseconds) {
|
|
914
|
+
this.writeUInt16(value.serverPicoseconds);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Encode a Variant with type ID, value(s), and optional array dimensions.
|
|
919
|
+
* @see OPC 10000-6 Section 5.2.2.16
|
|
920
|
+
*/
|
|
921
|
+
writeVariant(value, encoder) {
|
|
922
|
+
if (value.variantType < 0 || value.variantType > 25) {
|
|
923
|
+
throw new CodecError(`Invalid Variant type ID: ${value.variantType}. Must be 0-25.`);
|
|
924
|
+
}
|
|
925
|
+
let mask = value.variantType & VariantMask.TypeMask;
|
|
926
|
+
const isArrayValue = Array.isArray(value.value);
|
|
927
|
+
if (isArrayValue) {
|
|
928
|
+
mask |= VariantMask.Array;
|
|
929
|
+
}
|
|
930
|
+
if (value.arrayDimensions !== void 0 && value.arrayDimensions.length > 0) {
|
|
931
|
+
mask |= VariantMask.ArrayDimensions;
|
|
932
|
+
}
|
|
933
|
+
this.writeByte(mask);
|
|
934
|
+
if (isArrayValue) {
|
|
935
|
+
const array = value.value;
|
|
936
|
+
this.writeInt32(array.length);
|
|
937
|
+
for (const elem of array) {
|
|
938
|
+
this.writeVariantValue(value.variantType, elem, encoder);
|
|
939
|
+
}
|
|
940
|
+
} else if (value.variantType !== 0 /* Null */) {
|
|
941
|
+
this.writeVariantValue(value.variantType, value.value, encoder);
|
|
942
|
+
}
|
|
943
|
+
if (value.arrayDimensions !== void 0 && value.arrayDimensions.length > 0) {
|
|
944
|
+
this.writeInt32(value.arrayDimensions.length);
|
|
945
|
+
for (const dim of value.arrayDimensions) {
|
|
946
|
+
this.writeInt32(dim);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Encode a DiagnosticInfo with optional fields controlled by an encoding mask.
|
|
952
|
+
* Supports recursive InnerDiagnosticInfo.
|
|
953
|
+
* @see OPC 10000-6 Table 24
|
|
954
|
+
*/
|
|
955
|
+
writeDiagnosticInfo(value) {
|
|
956
|
+
let encodingMask = 0;
|
|
957
|
+
if (value.symbolicId !== null) {
|
|
958
|
+
encodingMask |= DiagnosticInfoMaskBits.SymbolicId;
|
|
959
|
+
}
|
|
960
|
+
if (value.namespaceUri !== null) {
|
|
961
|
+
encodingMask |= DiagnosticInfoMaskBits.NamespaceUri;
|
|
962
|
+
}
|
|
963
|
+
if (value.localizedText !== null) {
|
|
964
|
+
encodingMask |= DiagnosticInfoMaskBits.LocalizedText;
|
|
965
|
+
}
|
|
966
|
+
if (value.locale !== null) {
|
|
967
|
+
encodingMask |= DiagnosticInfoMaskBits.Locale;
|
|
968
|
+
}
|
|
969
|
+
if (value.additionalInfo !== null) {
|
|
970
|
+
encodingMask |= DiagnosticInfoMaskBits.AdditionalInfo;
|
|
971
|
+
}
|
|
972
|
+
if (value.innerStatusCode !== null) {
|
|
973
|
+
encodingMask |= DiagnosticInfoMaskBits.InnerStatusCode;
|
|
974
|
+
}
|
|
975
|
+
if (value.innerDiagnosticInfo !== null) {
|
|
976
|
+
encodingMask |= DiagnosticInfoMaskBits.InnerDiagnosticInfo;
|
|
977
|
+
}
|
|
978
|
+
this.writeByte(encodingMask);
|
|
979
|
+
if (encodingMask & DiagnosticInfoMaskBits.SymbolicId) {
|
|
980
|
+
this.writeInt32(value.symbolicId);
|
|
981
|
+
}
|
|
982
|
+
if (encodingMask & DiagnosticInfoMaskBits.NamespaceUri) {
|
|
983
|
+
this.writeInt32(value.namespaceUri);
|
|
984
|
+
}
|
|
985
|
+
if (encodingMask & DiagnosticInfoMaskBits.LocalizedText) {
|
|
986
|
+
this.writeInt32(value.localizedText);
|
|
987
|
+
}
|
|
988
|
+
if (encodingMask & DiagnosticInfoMaskBits.Locale) {
|
|
989
|
+
this.writeInt32(value.locale);
|
|
990
|
+
}
|
|
991
|
+
if (encodingMask & DiagnosticInfoMaskBits.AdditionalInfo) {
|
|
992
|
+
this.writeString(value.additionalInfo);
|
|
993
|
+
}
|
|
994
|
+
if (encodingMask & DiagnosticInfoMaskBits.InnerStatusCode) {
|
|
995
|
+
this.writeUInt32(value.innerStatusCode ?? 0 /* Good */);
|
|
996
|
+
}
|
|
997
|
+
if (encodingMask & DiagnosticInfoMaskBits.InnerDiagnosticInfo) {
|
|
998
|
+
this.writeDiagnosticInfo(value.innerDiagnosticInfo);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
// === Private helpers ===
|
|
1002
|
+
writeVariantValue(type, value, encoder) {
|
|
1003
|
+
switch (type) {
|
|
1004
|
+
case 0 /* Null */:
|
|
1005
|
+
break;
|
|
1006
|
+
case 1 /* Boolean */:
|
|
1007
|
+
this.writeBoolean(value);
|
|
1008
|
+
break;
|
|
1009
|
+
case 2 /* SByte */:
|
|
1010
|
+
this.writeSByte(value);
|
|
1011
|
+
break;
|
|
1012
|
+
case 3 /* Byte */:
|
|
1013
|
+
this.writeByte(value);
|
|
1014
|
+
break;
|
|
1015
|
+
case 4 /* Int16 */:
|
|
1016
|
+
this.writeInt16(value);
|
|
1017
|
+
break;
|
|
1018
|
+
case 5 /* UInt16 */:
|
|
1019
|
+
this.writeUInt16(value);
|
|
1020
|
+
break;
|
|
1021
|
+
case 6 /* Int32 */:
|
|
1022
|
+
this.writeInt32(value);
|
|
1023
|
+
break;
|
|
1024
|
+
case 7 /* UInt32 */:
|
|
1025
|
+
this.writeUInt32(value);
|
|
1026
|
+
break;
|
|
1027
|
+
case 8 /* Int64 */:
|
|
1028
|
+
this.writeInt64(value);
|
|
1029
|
+
break;
|
|
1030
|
+
case 9 /* UInt64 */:
|
|
1031
|
+
this.writeUInt64(value);
|
|
1032
|
+
break;
|
|
1033
|
+
case 10 /* Float */:
|
|
1034
|
+
this.writeFloat(value);
|
|
1035
|
+
break;
|
|
1036
|
+
case 11 /* Double */:
|
|
1037
|
+
this.writeDouble(value);
|
|
1038
|
+
break;
|
|
1039
|
+
case 12 /* String */:
|
|
1040
|
+
this.writeString(value);
|
|
1041
|
+
break;
|
|
1042
|
+
case 13 /* DateTime */:
|
|
1043
|
+
this.writeDateTime(value);
|
|
1044
|
+
break;
|
|
1045
|
+
case 14 /* Guid */:
|
|
1046
|
+
this.writeGuid(value);
|
|
1047
|
+
break;
|
|
1048
|
+
case 15 /* ByteString */:
|
|
1049
|
+
this.writeByteString(value);
|
|
1050
|
+
break;
|
|
1051
|
+
case 16 /* XmlElement */:
|
|
1052
|
+
this.writeXmlElement(value);
|
|
1053
|
+
break;
|
|
1054
|
+
case 17 /* NodeId */:
|
|
1055
|
+
this.writeNodeId(value);
|
|
1056
|
+
break;
|
|
1057
|
+
case 18 /* ExpandedNodeId */:
|
|
1058
|
+
this.writeExpandedNodeId(value);
|
|
1059
|
+
break;
|
|
1060
|
+
case 19 /* StatusCode */:
|
|
1061
|
+
this.writeStatusCode(value);
|
|
1062
|
+
break;
|
|
1063
|
+
case 20 /* QualifiedName */:
|
|
1064
|
+
this.writeQualifiedName(value);
|
|
1065
|
+
break;
|
|
1066
|
+
case 21 /* LocalizedText */:
|
|
1067
|
+
this.writeLocalizedText(value);
|
|
1068
|
+
break;
|
|
1069
|
+
case 22 /* ExtensionObject */:
|
|
1070
|
+
this.writeExtensionObject(value, encoder);
|
|
1071
|
+
break;
|
|
1072
|
+
case 23 /* DataValue */:
|
|
1073
|
+
this.writeDataValue(value, encoder);
|
|
1074
|
+
break;
|
|
1075
|
+
case 24 /* Variant */:
|
|
1076
|
+
this.writeVariant(value, encoder);
|
|
1077
|
+
break;
|
|
1078
|
+
case 25 /* DiagnosticInfo */:
|
|
1079
|
+
this.writeDiagnosticInfo(value);
|
|
1080
|
+
break;
|
|
1081
|
+
default:
|
|
1082
|
+
throw new CodecError(`Unsupported Variant type: ${type}`);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
constructor(initialSize = 1024) {
|
|
1086
|
+
this.buffer = Buffer.allocUnsafe(initialSize);
|
|
1087
|
+
this.position = 0;
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
|
|
1091
|
+
// src/configurationClient.ts
|
|
1092
|
+
var ConfigurationClient = class _ConfigurationClient extends Configuration {
|
|
1093
|
+
static getSimple(name, company) {
|
|
1094
|
+
const applicationUri = `urn:${company}:${name}`;
|
|
1095
|
+
const productUri = `urn:${company}:${name}:product`;
|
|
1096
|
+
const encoder = new Encoder();
|
|
1097
|
+
encoder.registerWriterFactory("binary", () => {
|
|
1098
|
+
return new BinaryWriter();
|
|
1099
|
+
});
|
|
1100
|
+
registerEncoders(encoder);
|
|
1101
|
+
const decoder = new Decoder();
|
|
1102
|
+
decoder.registerReaderFactory("binary", (data) => {
|
|
1103
|
+
return new BinaryReader(data);
|
|
1104
|
+
});
|
|
1105
|
+
registerTypeDecoders(decoder);
|
|
1106
|
+
registerBinaryDecoders(decoder);
|
|
1107
|
+
return new _ConfigurationClient(name, applicationUri, name, productUri, encoder, decoder);
|
|
1108
|
+
}
|
|
1109
|
+
constructor(applicationName, applicationUri, productName, productUri, encoder, decoder) {
|
|
1110
|
+
super(applicationName, applicationUri, productName, productUri, encoder, decoder);
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
var Id = class _Id {
|
|
1114
|
+
constructor(nodeId) {
|
|
1115
|
+
this.nodeId = nodeId;
|
|
1116
|
+
}
|
|
1117
|
+
static newId(namespace, identifier) {
|
|
1118
|
+
return new _Id(new NodeId(namespace, identifier));
|
|
1119
|
+
}
|
|
1120
|
+
toNodeId() {
|
|
1121
|
+
return this.nodeId;
|
|
1122
|
+
}
|
|
1123
|
+
toString() {
|
|
1124
|
+
return `${this.nodeId.namespace}:${this.nodeId.identifier}`;
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
var UserIdentity = class _UserIdentity {
|
|
1128
|
+
userIdentityToken;
|
|
1129
|
+
tokenType = UserTokenTypeEnum.Anonymous;
|
|
1130
|
+
issuerLoginCallback = void 0;
|
|
1131
|
+
static newAnonymous() {
|
|
1132
|
+
const userIdentity = new _UserIdentity();
|
|
1133
|
+
userIdentity.userIdentityToken = new AnonymousIdentityToken();
|
|
1134
|
+
userIdentity.userIdentityToken.policyId = "anonymous";
|
|
1135
|
+
userIdentity.tokenType = UserTokenTypeEnum.Anonymous;
|
|
1136
|
+
return userIdentity;
|
|
1137
|
+
}
|
|
1138
|
+
static newWithUserName(userName, password) {
|
|
1139
|
+
const userIdentity = new _UserIdentity();
|
|
1140
|
+
const nameToken = new UserNameIdentityToken();
|
|
1141
|
+
nameToken.userName = userName;
|
|
1142
|
+
nameToken.password = new TextEncoder().encode(password);
|
|
1143
|
+
userIdentity.userIdentityToken = nameToken;
|
|
1144
|
+
userIdentity.tokenType = UserTokenTypeEnum.UserName;
|
|
1145
|
+
return userIdentity;
|
|
1146
|
+
}
|
|
1147
|
+
static newWithIssuerToken(loginCallback) {
|
|
1148
|
+
const userIdentity = new _UserIdentity();
|
|
1149
|
+
const issuedToken = new IssuedIdentityToken();
|
|
1150
|
+
issuedToken.tokenData = new TextEncoder().encode("");
|
|
1151
|
+
userIdentity.userIdentityToken = issuedToken;
|
|
1152
|
+
userIdentity.tokenType = UserTokenTypeEnum.IssuedToken;
|
|
1153
|
+
userIdentity.issuerLoginCallback = loginCallback;
|
|
1154
|
+
return userIdentity;
|
|
1155
|
+
}
|
|
1156
|
+
getUserIdentityToken() {
|
|
1157
|
+
return this.userIdentityToken;
|
|
1158
|
+
}
|
|
1159
|
+
getTokenType() {
|
|
1160
|
+
return this.tokenType;
|
|
1161
|
+
}
|
|
1162
|
+
getIssuerLoginCallback() {
|
|
1163
|
+
if (!this.issuerLoginCallback) {
|
|
1164
|
+
throw new Error("No issuer login callback defined");
|
|
1165
|
+
}
|
|
1166
|
+
return this.issuerLoginCallback;
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
|
|
1170
|
+
export { Client, ConfigurationClient, Id, UserIdentity };
|
|
1171
|
+
//# sourceMappingURL=index.js.map
|
|
1172
|
+
//# sourceMappingURL=index.js.map
|