node-opcua-client 2.178.0 → 2.179.0
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/alarms_and_conditions/client_alarm_tools.js +7 -5
- package/dist/alarms_and_conditions/client_alarm_tools.js.map +1 -1
- package/dist/alarms_and_conditions/client_tools.js +38 -28
- package/dist/alarms_and_conditions/client_tools.js.map +1 -1
- package/dist/client_base.js.map +1 -1
- package/dist/client_monitored_item.d.ts +1 -1
- package/dist/client_monitored_item.js +4 -2
- package/dist/client_monitored_item.js.map +1 -1
- package/dist/client_monitored_item_group.d.ts +4 -3
- package/dist/client_monitored_item_group.js +8 -5
- package/dist/client_monitored_item_group.js.map +1 -1
- package/dist/client_monitored_item_toolbox.js +4 -2
- package/dist/client_monitored_item_toolbox.js.map +1 -1
- package/dist/client_session.d.ts +9 -2
- package/dist/client_session_keepalive_manager.d.ts +1 -1
- package/dist/client_session_keepalive_manager.js +50 -29
- package/dist/client_session_keepalive_manager.js.map +1 -1
- package/dist/client_subscription.d.ts +2 -2
- package/dist/client_subscription.js +3 -1
- package/dist/client_subscription.js.map +1 -1
- package/dist/common.d.ts +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/opcua_client.js +1 -0
- package/dist/opcua_client.js.map +1 -1
- package/dist/private/client_base_impl.js +125 -65
- package/dist/private/client_base_impl.js.map +1 -1
- package/dist/private/client_monitored_item_group_impl.d.ts +1 -0
- package/dist/private/client_monitored_item_group_impl.js +6 -8
- package/dist/private/client_monitored_item_group_impl.js.map +1 -1
- package/dist/private/client_monitored_item_impl.js +0 -4
- package/dist/private/client_monitored_item_impl.js.map +1 -1
- package/dist/private/client_publish_engine.js +62 -28
- package/dist/private/client_publish_engine.js.map +1 -1
- package/dist/private/client_session_impl.d.ts +49 -17
- package/dist/private/client_session_impl.js +240 -143
- package/dist/private/client_session_impl.js.map +1 -1
- package/dist/private/client_subscription_impl.js +15 -12
- package/dist/private/client_subscription_impl.js.map +1 -1
- package/dist/private/i_private_client.d.ts +1 -1
- package/dist/private/opcua_client_impl.js +12 -10
- package/dist/private/opcua_client_impl.js.map +1 -1
- package/dist/private/reconnection/client_publish_engine_reconnection.js +17 -7
- package/dist/private/reconnection/client_publish_engine_reconnection.js.map +1 -1
- package/dist/private/reconnection/client_subscription_reconnection.js +11 -7
- package/dist/private/reconnection/client_subscription_reconnection.js.map +1 -1
- package/dist/private/reconnection/reconnection.js +31 -14
- package/dist/private/reconnection/reconnection.js.map +1 -1
- package/dist/reverse/client_reverse_connect.js.map +1 -1
- package/dist/tools/findservers.js.map +1 -1
- package/dist/tools/read_history_server_capabilities.d.ts +2 -1
- package/dist/tools/read_history_server_capabilities.js +6 -3
- package/dist/tools/read_history_server_capabilities.js.map +1 -1
- package/package.json +50 -50
- package/source/alarms_and_conditions/client_alarm_tools.ts +10 -8
- package/source/alarms_and_conditions/client_tools.ts +71 -30
- package/source/client_base.ts +0 -1
- package/source/client_monitored_item.ts +10 -9
- package/source/client_monitored_item_base.ts +2 -8
- package/source/client_monitored_item_group.ts +13 -10
- package/source/client_monitored_item_toolbox.ts +4 -2
- package/source/client_session.ts +10 -3
- package/source/client_session_keepalive_manager.ts +72 -55
- package/source/client_subscription.ts +6 -3
- package/source/common.ts +4 -0
- package/source/index.ts +1 -1
- package/source/opcua_client.ts +1 -0
- package/source/private/client_base_impl.ts +179 -102
- package/source/private/client_monitored_item_group_impl.ts +17 -18
- package/source/private/client_monitored_item_impl.ts +0 -4
- package/source/private/client_publish_engine.ts +78 -40
- package/source/private/client_session_impl.ts +393 -236
- package/source/private/client_subscription_impl.ts +20 -25
- package/source/private/i_private_client.ts +1 -1
- package/source/private/opcua_client_impl.ts +13 -10
- package/source/private/reconnection/client_publish_engine_reconnection.ts +22 -21
- package/source/private/reconnection/client_subscription_reconnection.ts +12 -8
- package/source/private/reconnection/reconnection.ts +35 -18
- package/source/reverse/client_reverse_connect.ts +11 -3
- package/source/tools/findservers.ts +0 -1
- package/source/tools/read_history_server_capabilities.ts +9 -7
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module node-opcua-client-private
|
|
3
3
|
*/
|
|
4
|
-
// tslint:disable:unified-signatures
|
|
5
4
|
|
|
6
5
|
import { EventEmitter } from "node:events";
|
|
7
6
|
import chalk from "chalk";
|
|
@@ -51,6 +50,7 @@ import type { ClientSessionImpl } from "./client_session_impl";
|
|
|
51
50
|
interface ModifySubscriptionOptionsEx extends ModifySubscriptionOptions {
|
|
52
51
|
subscriptionId?: SubscriptionId;
|
|
53
52
|
}
|
|
53
|
+
|
|
54
54
|
import { detectLongOperation } from "./performance";
|
|
55
55
|
|
|
56
56
|
const debugLog = make_debugLog("CLIENT_SUBSCRIPTION");
|
|
@@ -249,12 +249,10 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
|
|
|
249
249
|
public terminate(): Promise<void>;
|
|
250
250
|
public terminate(callback: ErrorCallback): void;
|
|
251
251
|
public terminate(callback?: ErrorCallback): Promise<void> | void {
|
|
252
|
-
|
|
252
|
+
// c8 ignore next
|
|
253
|
+
doDebug && debugLog("Terminating client subscription ", this.subscriptionId);
|
|
253
254
|
|
|
254
|
-
if (
|
|
255
|
-
this.subscriptionId === TERMINATED_SUBSCRIPTION_ID ||
|
|
256
|
-
this.subscriptionId === TERMINATING_SUBSCRIPTION_ID
|
|
257
|
-
) {
|
|
255
|
+
if (this.subscriptionId === TERMINATED_SUBSCRIPTION_ID || this.subscriptionId === TERMINATING_SUBSCRIPTION_ID) {
|
|
258
256
|
// already terminated... just ignore
|
|
259
257
|
callback?.();
|
|
260
258
|
return;
|
|
@@ -279,7 +277,8 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
|
|
|
279
277
|
},
|
|
280
278
|
(err: Error | null, response?: DeleteSubscriptionsResponse) => {
|
|
281
279
|
if (response && response?.results?.[0] !== StatusCodes.Good) {
|
|
282
|
-
|
|
280
|
+
// c8 ignore next
|
|
281
|
+
doDebug && debugLog("warning: deleteSubscription returned ", response.results);
|
|
283
282
|
}
|
|
284
283
|
if (err) {
|
|
285
284
|
/**
|
|
@@ -293,7 +292,8 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
|
|
|
293
292
|
}
|
|
294
293
|
);
|
|
295
294
|
} else {
|
|
296
|
-
|
|
295
|
+
// c8 ignore next
|
|
296
|
+
doDebug && debugLog("subscriptionId is not value ", this.subscriptionId);
|
|
297
297
|
assert(this.subscriptionId === PENDING_SUBSCRIPTION_ID);
|
|
298
298
|
this._terminate_step2(callback as ErrorCallback);
|
|
299
299
|
}
|
|
@@ -332,7 +332,6 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
|
|
|
332
332
|
monitoringMode?: MonitoringMode | Callback<ClientMonitoredItemBase>,
|
|
333
333
|
callback?: Callback<ClientMonitoredItemBase>
|
|
334
334
|
): Promise<ClientMonitoredItemBase> | undefined {
|
|
335
|
-
|
|
336
335
|
// When called with 3 args via the promise API, thenify
|
|
337
336
|
// injects its callback as the 4th argument (the
|
|
338
337
|
// monitoringMode slot). Detect this and shift.
|
|
@@ -378,7 +377,6 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
|
|
|
378
377
|
timestampsToReturn: TimestampsToReturn,
|
|
379
378
|
callback?: Callback<ClientMonitoredItemGroup>
|
|
380
379
|
): Promise<ClientMonitoredItemGroup> | undefined {
|
|
381
|
-
|
|
382
380
|
const monitoredItemGroup = new ClientMonitoredItemGroupImpl(this, itemsToMonitor, requestedParameters, timestampsToReturn);
|
|
383
381
|
|
|
384
382
|
this._wait_for_subscription_to_be_ready((err?: Error) => {
|
|
@@ -397,10 +395,7 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
|
|
|
397
395
|
return undefined;
|
|
398
396
|
}
|
|
399
397
|
|
|
400
|
-
public _delete_monitored_items(
|
|
401
|
-
monitoredItems: ClientMonitoredItemBase[],
|
|
402
|
-
callback: ErrorCallback
|
|
403
|
-
): void {
|
|
398
|
+
public _delete_monitored_items(monitoredItems: ClientMonitoredItemBase[], callback: ErrorCallback): void {
|
|
404
399
|
assert(typeof callback === "function");
|
|
405
400
|
assert(Array.isArray(monitoredItems));
|
|
406
401
|
|
|
@@ -423,10 +418,7 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
|
|
|
423
418
|
|
|
424
419
|
public async setPublishingMode(publishingEnabled: boolean): Promise<StatusCode>;
|
|
425
420
|
public setPublishingMode(publishingEnabled: boolean, callback: Callback<StatusCode>): void;
|
|
426
|
-
public setPublishingMode(
|
|
427
|
-
publishingEnabled: boolean, callback?: Callback<StatusCode>
|
|
428
|
-
): Promise<StatusCode> | undefined {
|
|
429
|
-
|
|
421
|
+
public setPublishingMode(publishingEnabled: boolean, callback?: Callback<StatusCode>): Promise<StatusCode> | undefined {
|
|
430
422
|
const session = this.session as ClientSessionImpl;
|
|
431
423
|
if (!session) {
|
|
432
424
|
callback?.(new Error("no session"));
|
|
@@ -703,14 +695,16 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
|
|
|
703
695
|
private __on_publish_response_StatusChangeNotification(notification: StatusChangeNotification) {
|
|
704
696
|
assert(notification.schema.name === "StatusChangeNotification");
|
|
705
697
|
|
|
706
|
-
|
|
698
|
+
// c8 ignore next
|
|
699
|
+
doDebug && debugLog("Client has received a Status Change Notification ", notification.status.toString());
|
|
707
700
|
|
|
708
701
|
if (notification.status === StatusCodes.GoodSubscriptionTransferred) {
|
|
709
702
|
// OPCUA UA Spec 1.0.3 : part 3 - page 82 - 5.13.7 TransferSubscriptions:
|
|
710
703
|
// If the Server transfers the Subscription to the new Session, the Server shall issue
|
|
711
704
|
// a StatusChangeNotification notificationMessage with the status code
|
|
712
705
|
// Good_SubscriptionTransferred to the old Session.
|
|
713
|
-
|
|
706
|
+
// c8 ignore next
|
|
707
|
+
doDebug && debugLog("ClientSubscription#__on_publish_response_StatusChangeNotification : GoodSubscriptionTransferred");
|
|
714
708
|
|
|
715
709
|
// may be it has been transferred after a reconnection.... in this case should do nothing about it
|
|
716
710
|
}
|
|
@@ -765,7 +759,8 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
|
|
|
765
759
|
|
|
766
760
|
if (notificationData.length === 0) {
|
|
767
761
|
// this is a keep alive message
|
|
768
|
-
|
|
762
|
+
// c8 ignore next
|
|
763
|
+
doDebug && debugLog(chalk.yellow("Client : received a keep alive notification from client"));
|
|
769
764
|
/**
|
|
770
765
|
* notify the observers that a keep alive Publish Response has been received from the server.
|
|
771
766
|
* @event keepalive
|
|
@@ -818,7 +813,8 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
|
|
|
818
813
|
if (s(this).$_slowNotifCount > 0 && s(this).$_slowNotifCount % 1000 !== 0) return;
|
|
819
814
|
s(this).$_slowNotifCount++;
|
|
820
815
|
warningLog(
|
|
821
|
-
`[NODE-OPCUA-W32]}: monitored.item event handler takes too much time : operation duration ${duration} ms [repeated ${
|
|
816
|
+
`[NODE-OPCUA-W32]}: monitored.item event handler takes too much time : operation duration ${duration} ms [repeated ${
|
|
817
|
+
s(this).$_slowNotifCount
|
|
822
818
|
} times]\n please ensure that your monitoredItem event handler is not blocking the event loop.`
|
|
823
819
|
);
|
|
824
820
|
}
|
|
@@ -935,8 +931,6 @@ export function ClientMonitoredItem_create(
|
|
|
935
931
|
return monitoredItem;
|
|
936
932
|
}
|
|
937
933
|
|
|
938
|
-
// tslint:disable:no-var-requires
|
|
939
|
-
// tslint:disable:max-line-length
|
|
940
934
|
import { withCallback } from "thenify-ex";
|
|
941
935
|
|
|
942
936
|
const _opts = { multiArgs: false };
|
|
@@ -960,7 +954,8 @@ export function __create_subscription(subscription: ClientSubscriptionImpl, call
|
|
|
960
954
|
}
|
|
961
955
|
const session = subscription.session;
|
|
962
956
|
|
|
963
|
-
|
|
957
|
+
// c8 ignore next
|
|
958
|
+
doDebug && debugLog(chalk.yellow.bold("ClientSubscription created "));
|
|
964
959
|
|
|
965
960
|
const request = new CreateSubscriptionRequest({
|
|
966
961
|
maxNotificationsPerPublish: subscription.maxNotificationsPerPublish,
|
|
@@ -15,7 +15,7 @@ export interface IClientBase {
|
|
|
15
15
|
callback: (err: Error | null, session?: ClientSessionImpl) => void
|
|
16
16
|
): void;
|
|
17
17
|
_removeSession(session: ClientSessionImpl): void;
|
|
18
|
-
closeSession(session: ClientSession, deleteSubscription:
|
|
18
|
+
closeSession(session: ClientSession, deleteSubscription: boolean, arg2: (err?: Error | undefined) => void): void;
|
|
19
19
|
|
|
20
20
|
performMessageTransaction(request: Request, callback: ResponseCallback<Response>): void;
|
|
21
21
|
endpoint?: EndpointDescription;
|
|
@@ -7,7 +7,6 @@ import { callbackify } from "node:util";
|
|
|
7
7
|
import chalk from "chalk";
|
|
8
8
|
|
|
9
9
|
import { assert } from "node-opcua-assert";
|
|
10
|
-
import { createFastUninitializedBuffer } from "node-opcua-buffer-utils";
|
|
11
10
|
import { DataTypeExtractStrategy } from "node-opcua-client-dynamic-extension-object";
|
|
12
11
|
import {
|
|
13
12
|
type Certificate,
|
|
@@ -352,7 +351,7 @@ function createUserNameIdentityToken(
|
|
|
352
351
|
});
|
|
353
352
|
|
|
354
353
|
// now encrypt password as requested
|
|
355
|
-
const lenBuf =
|
|
354
|
+
const lenBuf = Buffer.allocUnsafe(4);
|
|
356
355
|
lenBuf.writeUInt32LE(identityToken.password.length + serverNonce.length, 0);
|
|
357
356
|
const block = Buffer.concat([lenBuf, identityToken.password, serverNonce]);
|
|
358
357
|
identityToken.password = cryptoFactory.asymmetricEncrypt(block, publicKey);
|
|
@@ -608,7 +607,6 @@ export class OPCUAClientImpl extends ClientBaseImpl<OPCUAClientBaseEvents> {
|
|
|
608
607
|
*
|
|
609
608
|
* const create
|
|
610
609
|
*/
|
|
611
|
-
// biome-ignore lint/suspicious/useAdjacentOverloadSignatures: static vs instance method
|
|
612
610
|
public static async createSession(
|
|
613
611
|
endpointUrl: string,
|
|
614
612
|
userIdentity?: UserIdentityInfo,
|
|
@@ -746,7 +744,8 @@ export class OPCUAClientImpl extends ClientBaseImpl<OPCUAClientBaseEvents> {
|
|
|
746
744
|
|
|
747
745
|
const old_client = internalSession._client;
|
|
748
746
|
|
|
749
|
-
|
|
747
|
+
// c8 ignore next
|
|
748
|
+
doDebug && debugLog("OPCUAClientImpl#reactivateSession");
|
|
750
749
|
|
|
751
750
|
this._activateSession(
|
|
752
751
|
internalSession,
|
|
@@ -840,7 +839,8 @@ export class OPCUAClientImpl extends ClientBaseImpl<OPCUAClientBaseEvents> {
|
|
|
840
839
|
this.performMessageTransaction(request, (err: Error | null, response?: Response) => {
|
|
841
840
|
/* c8 ignore next */
|
|
842
841
|
if (err) {
|
|
843
|
-
|
|
842
|
+
// c8 ignore next
|
|
843
|
+
doDebug && debugLog("__createSession_step3 has failed", err.message);
|
|
844
844
|
return callback(err);
|
|
845
845
|
// // we could have an invalid state here or a connection error
|
|
846
846
|
// errorLog("error: ", err.message, " retrying in ... 5 secondes");
|
|
@@ -883,7 +883,8 @@ export class OPCUAClientImpl extends ClientBaseImpl<OPCUAClientBaseEvents> {
|
|
|
883
883
|
session.serverCertificate = response.serverCertificate;
|
|
884
884
|
session.serverSignature = response.serverSignature;
|
|
885
885
|
|
|
886
|
-
|
|
886
|
+
// c8 ignore next
|
|
887
|
+
doDebug && debugLog("revised session timeout = ", session.timeout, response.revisedSessionTimeout);
|
|
887
888
|
|
|
888
889
|
response.serverEndpoints = response.serverEndpoints || [];
|
|
889
890
|
|
|
@@ -1027,6 +1028,11 @@ export class OPCUAClientImpl extends ClientBaseImpl<OPCUAClientBaseEvents> {
|
|
|
1027
1028
|
session.lastRequestSentTime = new Date();
|
|
1028
1029
|
|
|
1029
1030
|
this.performMessageTransaction(request, (err1: Error | null, response?: Response) => {
|
|
1031
|
+
// this path bypasses ClientSessionImpl._performMessageTransaction, so it has to
|
|
1032
|
+
// record the contact itself - and under the same rule, not a stricter one: a
|
|
1033
|
+
// server that answers and rejects the activation is still a server we reached.
|
|
1034
|
+
session.noteServerAnswer(err1, response);
|
|
1035
|
+
|
|
1030
1036
|
if (!err1 && response && response.responseHeader.serviceResult.isGoodish()) {
|
|
1031
1037
|
/* c8 ignore next */
|
|
1032
1038
|
if (!(response instanceof ActivateSessionResponse)) {
|
|
@@ -1041,7 +1047,6 @@ export class OPCUAClientImpl extends ClientBaseImpl<OPCUAClientBaseEvents> {
|
|
|
1041
1047
|
// OPC 10000-18 §5.2.8) so a Client can react without server access.
|
|
1042
1048
|
session.lastActivateSessionStatusCode = response.responseHeader.serviceResult;
|
|
1043
1049
|
session.serverNonce = response.serverNonce;
|
|
1044
|
-
session.lastResponseReceivedTime = new Date();
|
|
1045
1050
|
if (this.keepSessionAlive) {
|
|
1046
1051
|
session.startKeepAliveManager(this.keepAliveInterval);
|
|
1047
1052
|
}
|
|
@@ -1073,7 +1078,7 @@ export class OPCUAClientImpl extends ClientBaseImpl<OPCUAClientBaseEvents> {
|
|
|
1073
1078
|
return;
|
|
1074
1079
|
}
|
|
1075
1080
|
const currentDer = Array.isArray(current) ? combine_der(current) : current;
|
|
1076
|
-
if (session.serverCertificate
|
|
1081
|
+
if (session.serverCertificate?.equals(currentDer)) {
|
|
1077
1082
|
return;
|
|
1078
1083
|
}
|
|
1079
1084
|
doDebug &&
|
|
@@ -1295,8 +1300,6 @@ export class OPCUAClientImpl extends ClientBaseImpl<OPCUAClientBaseEvents> {
|
|
|
1295
1300
|
}
|
|
1296
1301
|
}
|
|
1297
1302
|
|
|
1298
|
-
// tslint:disable:no-var-requires
|
|
1299
|
-
// tslint:disable:max-line-length
|
|
1300
1303
|
import { withCallback } from "thenify-ex";
|
|
1301
1304
|
|
|
1302
1305
|
/**
|
|
@@ -39,10 +39,7 @@ function extractStatusCode(err: Error | null | undefined, response?: RepublishRe
|
|
|
39
39
|
return StatusCodes.Bad;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
function _sendRepublish(
|
|
43
|
-
session: ClientSessionImpl,
|
|
44
|
-
subscription: ClientSubscriptionImpl
|
|
45
|
-
): Promise<{ isDone: boolean }> {
|
|
42
|
+
function _sendRepublish(session: ClientSessionImpl, subscription: ClientSubscriptionImpl): Promise<{ isDone: boolean }> {
|
|
46
43
|
return new Promise<{ isDone: boolean }>((resolve, reject) => {
|
|
47
44
|
assert(Number.isFinite(subscription.lastSequenceNumber) && subscription.lastSequenceNumber + 1 >= 0);
|
|
48
45
|
|
|
@@ -63,7 +60,8 @@ function _sendRepublish(
|
|
|
63
60
|
}
|
|
64
61
|
|
|
65
62
|
if (!session || session._closeEventHasBeenEmitted) {
|
|
66
|
-
|
|
63
|
+
// c8 ignore next
|
|
64
|
+
doDebug && debugLog("ClientPublishEngine#_republish aborted ");
|
|
67
65
|
return resolve({ isDone: true });
|
|
68
66
|
}
|
|
69
67
|
|
|
@@ -89,16 +87,14 @@ function _sendRepublish(
|
|
|
89
87
|
if (!err) {
|
|
90
88
|
err = new Error(response.responseHeader.serviceResult.toString());
|
|
91
89
|
}
|
|
92
|
-
|
|
90
|
+
// c8 ignore next
|
|
91
|
+
doDebug && debugLog(" _send_republish ends with ", err.message);
|
|
93
92
|
reject(err);
|
|
94
93
|
});
|
|
95
94
|
});
|
|
96
95
|
}
|
|
97
96
|
|
|
98
|
-
async function _republish(
|
|
99
|
-
engine: ClientSidePublishEngine,
|
|
100
|
-
subscription: ClientSubscriptionImpl
|
|
101
|
-
): Promise<void> {
|
|
97
|
+
async function _republish(engine: ClientSidePublishEngine, subscription: ClientSubscriptionImpl): Promise<void> {
|
|
102
98
|
const session = engine.session as ClientSessionImpl;
|
|
103
99
|
|
|
104
100
|
// loop until done (all messages re-published or error)
|
|
@@ -110,14 +106,14 @@ async function _republish(
|
|
|
110
106
|
isDone = result.isDone;
|
|
111
107
|
}
|
|
112
108
|
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
// c8 ignore next
|
|
110
|
+
if (doDebug) {
|
|
111
|
+
debugLog("nbPendingPublishRequest = ", engine.nbPendingPublishRequests);
|
|
112
|
+
debugLog(" _republish ends with ", "null");
|
|
113
|
+
}
|
|
115
114
|
}
|
|
116
115
|
|
|
117
|
-
async function __askSubscriptionRepublish(
|
|
118
|
-
engine: ClientSidePublishEngine,
|
|
119
|
-
subscription: ClientSubscriptionImpl
|
|
120
|
-
): Promise<void> {
|
|
116
|
+
async function __askSubscriptionRepublish(engine: ClientSidePublishEngine, subscription: ClientSubscriptionImpl): Promise<void> {
|
|
121
117
|
try {
|
|
122
118
|
await _republish(engine, subscription);
|
|
123
119
|
} catch (err) {
|
|
@@ -131,7 +127,8 @@ async function __askSubscriptionRepublish(
|
|
|
131
127
|
|
|
132
128
|
const error = err as Error;
|
|
133
129
|
|
|
134
|
-
|
|
130
|
+
// c8 ignore next
|
|
131
|
+
doDebug && debugLog("__askSubscriptionRepublish--------------------- err =", error.message);
|
|
135
132
|
|
|
136
133
|
if (error.message.match(/BadSessionInvalid/)) {
|
|
137
134
|
// _republish failed because session is not valid anymore on server side.
|
|
@@ -147,9 +144,11 @@ async function __askSubscriptionRepublish(
|
|
|
147
144
|
// In this case, Client must recreate a subscription and recreate monitored item without altering
|
|
148
145
|
// the event handlers
|
|
149
146
|
//
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
147
|
+
// c8 ignore next
|
|
148
|
+
doDebug &&
|
|
149
|
+
debugLog(
|
|
150
|
+
chalk.bgWhite.red("__askSubscriptionRepublish failed " + " subscriptionId is not valid anymore on server side.")
|
|
151
|
+
);
|
|
153
152
|
await recreateSubscriptionAndMonitoredItem(subscription);
|
|
154
153
|
return;
|
|
155
154
|
}
|
|
@@ -185,5 +184,7 @@ export function republish(engine: ClientSidePublishEngine, callback: () => void)
|
|
|
185
184
|
}
|
|
186
185
|
};
|
|
187
186
|
|
|
188
|
-
processAll()
|
|
187
|
+
processAll()
|
|
188
|
+
.then(() => callback())
|
|
189
|
+
.catch(() => callback());
|
|
189
190
|
}
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
type CreateMonitoredItemsResponse,
|
|
14
14
|
type MonitoredItemCreateRequestOptions
|
|
15
15
|
} from "node-opcua-types";
|
|
16
|
-
import type { ClientSubscription } from "../../client_subscription";
|
|
16
|
+
import type { ClientMonitoredItemBaseMap, ClientSubscription } from "../../client_subscription";
|
|
17
17
|
import type { ClientMonitoredItemImpl } from "../client_monitored_item_impl";
|
|
18
18
|
import { __create_subscription, type ClientSubscriptionImpl, TERMINATED_SUBSCRIPTION_ID } from "../client_subscription_impl";
|
|
19
19
|
import { _shouldNotContinue } from "./reconnection";
|
|
@@ -35,7 +35,7 @@ async function createMonitoredItemsAndRespectOperationalLimits(
|
|
|
35
35
|
return createMonitoredItemResponse;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
async function adjustMonitoredItemNodeIds(
|
|
38
|
+
async function adjustMonitoredItemNodeIds(_subscription: ClientSubscription, _oldMonitoredItems: ClientMonitoredItemBaseMap) {
|
|
39
39
|
// to Do
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
@@ -43,11 +43,13 @@ async function adjustMonitoredItemNodeIds(subscription: ClientSubscription, oldM
|
|
|
43
43
|
|
|
44
44
|
*/
|
|
45
45
|
export async function recreateSubscriptionAndMonitoredItem(_subscription: ClientSubscription): Promise<void> {
|
|
46
|
-
|
|
46
|
+
// c8 ignore next
|
|
47
|
+
doDebug && debugLog("recreateSubscriptionAndMonitoredItem", _subscription.subscriptionId.toString());
|
|
47
48
|
|
|
48
49
|
const subscription = _subscription as ClientSubscriptionImpl;
|
|
49
50
|
if (subscription.subscriptionId === TERMINATED_SUBSCRIPTION_ID) {
|
|
50
|
-
|
|
51
|
+
// c8 ignore next
|
|
52
|
+
doDebug && debugLog("Subscription is not in a valid state");
|
|
51
53
|
return;
|
|
52
54
|
}
|
|
53
55
|
|
|
@@ -63,9 +65,10 @@ export async function recreateSubscriptionAndMonitoredItem(_subscription: Client
|
|
|
63
65
|
throw _err;
|
|
64
66
|
}
|
|
65
67
|
|
|
66
|
-
const
|
|
68
|
+
const _test = subscription.publishEngine.getSubscription(subscription.subscriptionId);
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
// c8 ignore next
|
|
71
|
+
doDebug && debugLog("recreating ", Object.keys(oldMonitoredItems).length, " monitored Items");
|
|
69
72
|
// re-create monitored items
|
|
70
73
|
const itemsToCreate: MonitoredItemCreateRequestOptions[] = [];
|
|
71
74
|
|
|
@@ -92,7 +95,8 @@ export async function recreateSubscriptionAndMonitoredItem(_subscription: Client
|
|
|
92
95
|
throw new Error("no session");
|
|
93
96
|
}
|
|
94
97
|
|
|
95
|
-
|
|
98
|
+
// c8 ignore next
|
|
99
|
+
doDebug && debugLog("Recreating ", itemsToCreate.length, " monitored items");
|
|
96
100
|
|
|
97
101
|
const response = await createMonitoredItemsAndRespectOperationalLimits(session, createMonitorItemsRequest);
|
|
98
102
|
const monitoredItemResults = response.results || [];
|
|
@@ -101,7 +105,7 @@ export async function recreateSubscriptionAndMonitoredItem(_subscription: Client
|
|
|
101
105
|
monitoredItemResults.forEach((monitoredItemResult, index) => {
|
|
102
106
|
const itemToCreate = itemsToCreate[index];
|
|
103
107
|
/* c8 ignore next */
|
|
104
|
-
if (!itemToCreate
|
|
108
|
+
if (!itemToCreate?.requestedParameters) {
|
|
105
109
|
_errCount++;
|
|
106
110
|
return;
|
|
107
111
|
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* @module node-opcua-client-private
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
// tslint:disable:only-arrow-functions
|
|
6
5
|
import chalk from "chalk";
|
|
7
6
|
import { assert } from "node-opcua-assert";
|
|
8
7
|
import { invalidateExtraDataTypeManager } from "node-opcua-client-dynamic-extension-object";
|
|
@@ -158,7 +157,10 @@ function _ask_for_subscription_republish(session: ClientSessionImpl, callback: (
|
|
|
158
157
|
if (err) {
|
|
159
158
|
warningLog("republish has failed with error :", err.message);
|
|
160
159
|
doDebug && debugLog("_ask_for_subscription_republish has : recreating subscription");
|
|
161
|
-
|
|
160
|
+
if (!session._client) {
|
|
161
|
+
return callback(new Error("_ask_for_subscription_republish: session has no client"));
|
|
162
|
+
}
|
|
163
|
+
return repair_client_session_by_recreating_a_new_session(session._client, session, callback);
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
// Republish has completed: resume normal Publish handling. During the connection break the
|
|
@@ -206,7 +208,11 @@ function activate_session(client: IClientBase, session: ClientSessionImpl, newSe
|
|
|
206
208
|
|
|
207
209
|
doDebug && debugLog(chalk.bgWhite.red(" => activating a new session ...."));
|
|
208
210
|
|
|
209
|
-
|
|
211
|
+
if (!newSession.userIdentityInfo) {
|
|
212
|
+
reject(new Error("activate_session: newSession has no userIdentityInfo"));
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
client._activateSession(newSession, newSession.userIdentityInfo, (err: Error | null, _session1?: ClientSessionImpl) => {
|
|
210
216
|
// c8 ignore next
|
|
211
217
|
doDebug && debugLog(" => activating a new session .... Done err=", err ? err.message : "null");
|
|
212
218
|
if (err) {
|
|
@@ -467,11 +473,16 @@ function _repair_client_session(client: IClientBase, session: ClientSessionImpl,
|
|
|
467
473
|
{
|
|
468
474
|
const err = _shouldNotContinue(session);
|
|
469
475
|
if (err) {
|
|
470
|
-
|
|
476
|
+
callback(err);
|
|
477
|
+
return;
|
|
471
478
|
}
|
|
472
479
|
}
|
|
473
480
|
|
|
474
|
-
|
|
481
|
+
if (!session.userIdentityInfo) {
|
|
482
|
+
callback(new Error("_repair_client_session: session has no userIdentityInfo"));
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
client._activateSession(session, session.userIdentityInfo, (err: Error | null, _session2?: ClientSessionImpl) => {
|
|
475
486
|
// prettier-ignore
|
|
476
487
|
{
|
|
477
488
|
const err = _shouldNotContinue(session);
|
|
@@ -499,17 +510,24 @@ type EmptyCallback = (err?: Error) => void;
|
|
|
499
510
|
export function repair_client_session(client: IClientBase, session: ClientSessionImpl, callback: EmptyCallback): void {
|
|
500
511
|
if (!client) {
|
|
501
512
|
doDebug && debugLog("Aborting reactivation of old session because user requested session to be close");
|
|
502
|
-
|
|
513
|
+
callback();
|
|
514
|
+
return;
|
|
503
515
|
}
|
|
504
516
|
doDebug && debugLog(chalk.yellow("Starting client session repair"));
|
|
505
517
|
|
|
506
518
|
const privateSession = session as unknown as Reconnectable;
|
|
507
|
-
privateSession._reconnecting = privateSession._reconnecting || {
|
|
519
|
+
privateSession._reconnecting = privateSession._reconnecting || {
|
|
520
|
+
reconnecting: false,
|
|
521
|
+
pendingCallbacks: [],
|
|
522
|
+
pendingTransactions: []
|
|
523
|
+
};
|
|
508
524
|
|
|
509
525
|
if (session.hasBeenClosed()) {
|
|
510
526
|
privateSession._reconnecting.reconnecting = false;
|
|
511
527
|
doDebug && debugLog("Aborting reactivation of old session because session has been closed");
|
|
512
|
-
|
|
528
|
+
session.flushPendingTransactions(new Error("Session has been closed while the transaction was waiting for reconnection"));
|
|
529
|
+
callback();
|
|
530
|
+
return;
|
|
513
531
|
}
|
|
514
532
|
if (privateSession._reconnecting.reconnecting) {
|
|
515
533
|
doDebug && debugLog(chalk.bgCyan("Reconnection is already happening for session"), session.sessionId.toString());
|
|
@@ -519,9 +537,6 @@ export function repair_client_session(client: IClientBase, session: ClientSessio
|
|
|
519
537
|
|
|
520
538
|
privateSession._reconnecting.reconnecting = true;
|
|
521
539
|
|
|
522
|
-
// get old transaction queue ...
|
|
523
|
-
const transactionQueue = privateSession._reconnecting.pendingTransactions.splice(0);
|
|
524
|
-
|
|
525
540
|
const repeatedAction = (callback: EmptyCallback) => {
|
|
526
541
|
// prettier-ignore
|
|
527
542
|
{
|
|
@@ -579,15 +594,17 @@ export function repair_client_session(client: IClientBase, session: ClientSessio
|
|
|
579
594
|
repeatedAction((err) => {
|
|
580
595
|
privateSession._reconnecting.reconnecting = false;
|
|
581
596
|
const otherCallbacks = privateSession._reconnecting.pendingCallbacks.splice(0);
|
|
582
|
-
// re-inject element in queue
|
|
583
597
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
transactionQueue.forEach((e) => privateSession._reconnecting.pendingTransactions.push(e));
|
|
588
|
-
}
|
|
589
|
-
otherCallbacks.forEach((c: EmptyCallback) => c(err));
|
|
598
|
+
otherCallbacks.forEach((c: EmptyCallback) => {
|
|
599
|
+
c(err);
|
|
600
|
+
});
|
|
590
601
|
callback(err);
|
|
602
|
+
|
|
603
|
+
// Resolve what the application asked for while the channel was down - after the repair
|
|
604
|
+
// callbacks have run, so the session is fully usable by the time anything is replayed.
|
|
605
|
+
// On failure they are failed rather than held across the client's retry delay: a caller
|
|
606
|
+
// waiting without bound is worse than an error it can act on.
|
|
607
|
+
session.flushPendingTransactions(err ?? null);
|
|
591
608
|
});
|
|
592
609
|
}
|
|
593
610
|
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
import { createServer, type Server, type Socket } from "node:net";
|
|
24
24
|
|
|
25
25
|
import { checkDebugFlag, make_debugLog, make_warningLog } from "node-opcua-debug";
|
|
26
|
+
import type { StatusCode } from "node-opcua-status-code";
|
|
26
27
|
import {
|
|
27
28
|
decodeReverseHello,
|
|
28
29
|
type IAcceptedReverseConnection,
|
|
@@ -33,7 +34,6 @@ import {
|
|
|
33
34
|
StatusCodes2,
|
|
34
35
|
TCPErrorMessage
|
|
35
36
|
} from "node-opcua-transport";
|
|
36
|
-
import type { StatusCode } from "node-opcua-status-code";
|
|
37
37
|
|
|
38
38
|
const doDebug = checkDebugFlag("ReverseConnect");
|
|
39
39
|
const debugLog = make_debugLog("ReverseConnect");
|
|
@@ -193,7 +193,10 @@ export class ClientReverseConnect {
|
|
|
193
193
|
* Register interest in the next reverse connection matching `expectation`.
|
|
194
194
|
* The callback fires once with an accepted, RHE-validated connection (or an error).
|
|
195
195
|
*/
|
|
196
|
-
public waitForConnection(
|
|
196
|
+
public waitForConnection(
|
|
197
|
+
expectation: ReverseConnectExpectation | undefined,
|
|
198
|
+
callback: AcceptedCallback
|
|
199
|
+
): ICancelableRegistration {
|
|
197
200
|
// a validated connection may already be waiting
|
|
198
201
|
for (let i = 0; i < this.#held.length; i++) {
|
|
199
202
|
const h = this.#held[i];
|
|
@@ -341,7 +344,12 @@ export class ClientReverseConnect {
|
|
|
341
344
|
doDebug && debugLog(`reverse connection from ${serverUri} held (no matching client yet)`);
|
|
342
345
|
timer = setTimeout(() => {
|
|
343
346
|
timer = undefined;
|
|
344
|
-
this.#rejectSocket(
|
|
347
|
+
this.#rejectSocket(
|
|
348
|
+
socket,
|
|
349
|
+
forget,
|
|
350
|
+
StatusCodes2.BadTcpServerTooBusy,
|
|
351
|
+
"no client is waiting for this reverse connection"
|
|
352
|
+
);
|
|
345
353
|
}, this.#matchTimeout);
|
|
346
354
|
timer.unref?.();
|
|
347
355
|
this.#held.push({ accepted, handOff });
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
import type { ServerOnNetwork } from "node-opcua-service-discovery";
|
|
6
6
|
import type { ApplicationDescription, EndpointDescription } from "node-opcua-service-endpoints";
|
|
7
7
|
|
|
8
|
-
|
|
9
8
|
import { ClientBaseImpl } from "../private/client_base_impl";
|
|
10
9
|
|
|
11
10
|
export interface FindServerResults {
|
|
@@ -8,14 +8,13 @@ import type { DataValue } from "node-opcua-data-value";
|
|
|
8
8
|
import { NodeId } from "node-opcua-nodeid";
|
|
9
9
|
import type { ReadValueIdOptions } from "node-opcua-service-read";
|
|
10
10
|
import { type BrowsePath, type BrowsePathResult, makeBrowsePath } from "node-opcua-service-translate-browse-path";
|
|
11
|
-
import { StatusCodes } from "node-opcua-status-code";
|
|
12
11
|
import { lowerFirstLetter } from "node-opcua-utils";
|
|
13
12
|
import type { Variant } from "node-opcua-variant";
|
|
14
13
|
|
|
15
14
|
import type { ClientSession } from "../client_session";
|
|
16
15
|
|
|
17
16
|
export interface HistoryServerCapabilities {
|
|
18
|
-
[key: string]:
|
|
17
|
+
[key: string]: Variant;
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
export function readHistoryServerCapabilities(session: ClientSession): Promise<HistoryServerCapabilities>;
|
|
@@ -26,21 +25,24 @@ export function readHistoryServerCapabilities(
|
|
|
26
25
|
export function readHistoryServerCapabilities(
|
|
27
26
|
session: ClientSession,
|
|
28
27
|
callback?: (err: Error | null, capabilities?: HistoryServerCapabilities) => void
|
|
29
|
-
):
|
|
28
|
+
): unknown {
|
|
30
29
|
if (!callback) {
|
|
31
30
|
return new Promise<HistoryServerCapabilities>((resolve, reject) => {
|
|
32
31
|
readHistoryServerCapabilities(session, (err, capabilities) => {
|
|
33
32
|
if (err) {
|
|
34
33
|
return reject(err);
|
|
35
34
|
}
|
|
36
|
-
|
|
35
|
+
if (!capabilities) {
|
|
36
|
+
return reject(new Error("readHistoryServerCapabilities: internal error - no capabilities"));
|
|
37
|
+
}
|
|
38
|
+
resolve(capabilities);
|
|
37
39
|
});
|
|
38
40
|
});
|
|
39
41
|
}
|
|
40
42
|
// display HistoryCapabilities of server
|
|
41
43
|
const browsePath: BrowsePath = makeBrowsePath(ObjectIds.ObjectsFolder, "/Server/ServerCapabilities.HistoryServerCapabilities");
|
|
42
44
|
|
|
43
|
-
session.translateBrowsePath(browsePath, (err: Error | null, result?: BrowsePathResult) => {
|
|
45
|
+
return session.translateBrowsePath(browsePath, (err: Error | null, result?: BrowsePathResult) => {
|
|
44
46
|
if (err) {
|
|
45
47
|
return callback(err);
|
|
46
48
|
}
|
|
@@ -49,7 +51,7 @@ export function readHistoryServerCapabilities(
|
|
|
49
51
|
return callback(new Error("Internal Error"));
|
|
50
52
|
}
|
|
51
53
|
if (result.statusCode.isNotGood()) {
|
|
52
|
-
return callback(new Error(
|
|
54
|
+
return callback(new Error(`StatusCode = ${result.statusCode.toString()}`));
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
result.targets = result.targets || [];
|
|
@@ -90,7 +92,7 @@ export function readHistoryServerCapabilities(
|
|
|
90
92
|
"AggregateFunctions/DurationStateNonZero"
|
|
91
93
|
// etc....
|
|
92
94
|
];
|
|
93
|
-
const browsePaths = properties.map((prop: string) => makeBrowsePath(historyServerCapabilitiesNodeId,
|
|
95
|
+
const browsePaths = properties.map((prop: string) => makeBrowsePath(historyServerCapabilitiesNodeId, `.${prop}`));
|
|
94
96
|
|
|
95
97
|
session.translateBrowsePath(browsePaths, (innerErr: Error | null, results?: BrowsePathResult[]) => {
|
|
96
98
|
if (innerErr) {
|