node-opcua-client 2.123.0 → 2.125.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.
Files changed (51) hide show
  1. package/dist/alarms_and_conditions/client_alarm_tools.js.map +1 -1
  2. package/dist/alarms_and_conditions/client_tools.js.map +1 -1
  3. package/dist/client_monitored_item_toolbox.js.map +1 -1
  4. package/dist/client_session_keepalive_manager.js.map +1 -1
  5. package/dist/client_utils.js.map +1 -1
  6. package/dist/private/client_base_impl.d.ts +11 -4
  7. package/dist/private/client_base_impl.js +130 -70
  8. package/dist/private/client_base_impl.js.map +1 -1
  9. package/dist/private/client_monitored_item_group_impl.js.map +1 -1
  10. package/dist/private/client_monitored_item_impl.js.map +1 -1
  11. package/dist/private/client_publish_engine.d.ts +9 -4
  12. package/dist/private/client_publish_engine.js +5 -99
  13. package/dist/private/client_publish_engine.js.map +1 -1
  14. package/dist/private/client_session_impl.d.ts +20 -4
  15. package/dist/private/client_session_impl.js +5 -24
  16. package/dist/private/client_session_impl.js.map +1 -1
  17. package/dist/private/client_subscription_impl.d.ts +5 -8
  18. package/dist/private/client_subscription_impl.js +83 -174
  19. package/dist/private/client_subscription_impl.js.map +1 -1
  20. package/dist/private/i_private_client.d.ts +0 -1
  21. package/dist/private/opcua_client_impl.js +1 -1
  22. package/dist/private/opcua_client_impl.js.map +1 -1
  23. package/dist/private/performance.js.map +1 -1
  24. package/dist/private/reconnection/client_publish_engine_reconnection.d.ts +2 -0
  25. package/dist/private/reconnection/client_publish_engine_reconnection.js +118 -0
  26. package/dist/private/reconnection/client_publish_engine_reconnection.js.map +1 -0
  27. package/dist/private/reconnection/client_reconnection.d.ts +2 -0
  28. package/dist/private/reconnection/client_reconnection.js +20 -0
  29. package/dist/private/reconnection/client_reconnection.js.map +1 -0
  30. package/dist/private/reconnection/client_subscription_reconnection.d.ts +7 -0
  31. package/dist/private/reconnection/client_subscription_reconnection.js +124 -0
  32. package/dist/private/reconnection/client_subscription_reconnection.js.map +1 -0
  33. package/dist/private/reconnection/reconnection.d.ts +12 -0
  34. package/dist/private/reconnection/reconnection.js +564 -0
  35. package/dist/private/reconnection/reconnection.js.map +1 -0
  36. package/dist/tools/findservers.js.map +1 -1
  37. package/dist/tools/read_history_server_capabilities.js.map +1 -1
  38. package/dist/verify.js.map +1 -1
  39. package/package.json +43 -43
  40. package/source/index.ts +4 -1
  41. package/source/opcua_client.ts +11 -5
  42. package/source/private/client_base_impl.ts +136 -81
  43. package/source/private/client_publish_engine.ts +13 -129
  44. package/source/private/client_session_impl.ts +56 -78
  45. package/source/private/client_subscription_impl.ts +85 -207
  46. package/source/private/i_private_client.ts +0 -2
  47. package/source/private/opcua_client_impl.ts +16 -149
  48. package/source/private/reconnection/client_publish_engine_reconnection.ts +146 -0
  49. package/source/private/reconnection/client_reconnection.ts +17 -0
  50. package/source/private/reconnection/client_subscription_reconnection.ts +124 -0
  51. package/source/{reconnection.ts → private/reconnection/reconnection.ts} +121 -76
@@ -0,0 +1,146 @@
1
+ import { types } from "util";
2
+ import async from "async";
3
+ import chalk from "chalk";
4
+ import assert from "node-opcua-assert";
5
+ import { checkDebugFlag, make_debugLog } from "node-opcua-debug";
6
+ import { StatusCodes } from "node-opcua-status-code";
7
+ import { RepublishRequest, RepublishResponse } from "node-opcua-types";
8
+ import { SubscriptionId } from "../../client_session";
9
+ import { ClientSessionImpl } from "../client_session_impl";
10
+ import { ClientSubscriptionImpl } from "../client_subscription_impl";
11
+ import { ClientSidePublishEngine } from "../client_publish_engine";
12
+ import { recreateSubscriptionAndMonitoredItem } from "./client_subscription_reconnection";
13
+ import { _shouldNotContinue2 } from "./reconnection";
14
+
15
+ const debugLog = make_debugLog("RECONNECTION");
16
+ const doDebug = checkDebugFlag("RECONNECTION");
17
+
18
+ function _republish(
19
+ engine: ClientSidePublishEngine,
20
+ subscription: ClientSubscriptionImpl,
21
+ callback: (err?: Error) => void
22
+ ) {
23
+
24
+ let isDone = false;
25
+ const session = engine.session as ClientSessionImpl;
26
+
27
+ const sendRepublishFunc = (callback2: (err?: Error) => void) => {
28
+ assert(isFinite(subscription.lastSequenceNumber) && subscription.lastSequenceNumber + 1 >= 0);
29
+
30
+ const request = new RepublishRequest({
31
+ retransmitSequenceNumber: subscription.lastSequenceNumber + 1,
32
+ subscriptionId: subscription.subscriptionId
33
+ });
34
+
35
+ // istanbul ignore next
36
+ if (doDebug) {
37
+ // istanbul ignore next
38
+ debugLog(
39
+ chalk.bgCyan.yellow.bold(" republish Request for subscription"),
40
+ request.subscriptionId,
41
+ " retransmitSequenceNumber=",
42
+ request.retransmitSequenceNumber
43
+ );
44
+ }
45
+
46
+ if (!session || session!._closeEventHasBeenEmitted) {
47
+ debugLog("ClientPublishEngine#_republish aborted ");
48
+ // has client been disconnected in the mean time ?
49
+ isDone = true;
50
+ return callback2();
51
+ }
52
+ session.republish(request, (err: Error | null, response?: RepublishResponse) => {
53
+ const statusCode = err ? StatusCodes.Bad : response!.responseHeader.serviceResult;
54
+ if (!err && (statusCode.equals(StatusCodes.Good) || statusCode.equals(StatusCodes.BadMessageNotAvailable))) {
55
+ // reprocess notification message and keep going
56
+ if (statusCode.equals(StatusCodes.Good)) {
57
+ subscription.onNotificationMessage(response!.notificationMessage);
58
+ }
59
+ } else {
60
+ if (!err) {
61
+ err = new Error(response!.responseHeader.serviceResult.toString());
62
+ }
63
+ debugLog(" _send_republish ends with ", err.message);
64
+ isDone = true;
65
+ }
66
+ callback2(err ? err : undefined);
67
+ });
68
+ };
69
+
70
+ const sendRepublishUntilDone = () => {
71
+ async.whilst(
72
+ (cb: (err: null, truth: boolean) => void) => cb(null, !isDone),
73
+ sendRepublishFunc,
74
+ ((err?: Error | null): void => {
75
+ debugLog("nbPendingPublishRequest = ", engine.nbPendingPublishRequests);
76
+ debugLog(" _republish ends with ", err ? err.message : "null");
77
+ callback(err!);
78
+ }) as any // Wait for @type/async bug to be fixed !
79
+ );
80
+ };
81
+
82
+ setImmediate(sendRepublishUntilDone);
83
+ }
84
+
85
+ function __askSubscriptionRepublish(
86
+ engine: ClientSidePublishEngine,
87
+ subscription: ClientSubscriptionImpl,
88
+ callback: (err?: Error) => void
89
+ ) {
90
+
91
+ _republish(engine, subscription, (err?: Error) => {
92
+ // prettier-ignore
93
+ { const _err = _shouldNotContinue2(subscription); if (_err) { return callback(_err); } }
94
+
95
+ assert(!err || types.isNativeError(err));
96
+
97
+ debugLog("__askSubscriptionRepublish--------------------- err =", err ? err.message : null);
98
+
99
+ if (err && err.message.match(/BadSessionInvalid/)) {
100
+ // _republish failed because session is not valid anymore on server side.
101
+ return callback(err);
102
+ }
103
+ if (err && err.message.match(/SubscriptionIdInvalid/)) {
104
+ // _republish failed because subscriptionId is not valid anymore on server side.
105
+ //
106
+ // This could happen when the subscription has timed out and has been deleted by server
107
+ // Subscription may time out if the duration of the connection break exceed the max life time
108
+ // of the subscription.
109
+ //
110
+ // In this case, Client must recreate a subscription and recreate monitored item without altering
111
+ // the event handlers
112
+ //
113
+ debugLog(
114
+ chalk.bgWhite.red("__askSubscriptionRepublish failed " + " subscriptionId is not valid anymore on server side.")
115
+ );
116
+ return recreateSubscriptionAndMonitoredItem(subscription).then(() => callback()).catch(err => callback(err));
117
+ }
118
+ if (err && err.message.match(/|MessageNotAvailable/)) {
119
+ // start engine and start monitoring
120
+ }
121
+ callback();
122
+ });
123
+ }
124
+
125
+ export function republish(engine: ClientSidePublishEngine, callback: () => void): void {
126
+ // After re-establishing the connection the Client shall call Republish in a loop, starting with
127
+ // the next expected sequence number and incrementing the sequence number until the Server returns
128
+ // the status BadMessageNotAvailable.
129
+ // After receiving this status, the Client shall start sending Publish requests with the normal Publish
130
+ // handling.
131
+ // This sequence ensures that the lost NotificationMessages queued in the Server are not overwritten
132
+ // by newPublish responses
133
+ /**
134
+ * call Republish continuously until all Notification messages of
135
+ * un-acknowledged notifications are reprocessed.
136
+ */
137
+ const askSubscriptionRepublish = (
138
+ subscription: ClientSubscriptionImpl,
139
+ subscriptionId: SubscriptionId | string,
140
+ innerCallback: () => void
141
+ ) => {
142
+ __askSubscriptionRepublish(engine, subscription, innerCallback);
143
+ };
144
+
145
+ async.forEachOf(engine.subscriptionMap, askSubscriptionRepublish, callback);
146
+ }
@@ -0,0 +1,17 @@
1
+ import { warn } from "console";
2
+ import { ClientBaseImpl } from "../client_base_impl";
3
+
4
+ export function waitUntilReconnectionIsCanceled(client: ClientBaseImpl, callback: () => void) {
5
+ const interval = 100;
6
+ const maxIntervalCount = 100;
7
+ let intervalCount = 0;
8
+ const timer = setInterval(() => {
9
+ if (!client.isReconnecting || ++intervalCount > maxIntervalCount) {
10
+ clearInterval(timer);
11
+ if (intervalCount > maxIntervalCount) {
12
+ warn("waitUntilReconnectionIsCanceled: timeout");
13
+ }
14
+ callback();
15
+ }
16
+ }, interval);
17
+ }
@@ -0,0 +1,124 @@
1
+ import { promisify } from "util";
2
+ import assert from "node-opcua-assert";
3
+ import { TimestampsToReturn } from "node-opcua-data-value";
4
+ import { make_debugLog, checkDebugFlag } from "node-opcua-debug";
5
+ import { warningLog } from "node-opcua-pki";
6
+ import { MonitoredItemCreateRequestOptions, CreateMonitoredItemsRequest, CreateMonitoredItemsResponse } from "node-opcua-types";
7
+
8
+ import {
9
+ IBasicSessionReadAsync,
10
+ IBasicSessionWithSubscription,
11
+ createMonitoredItemsLimit,
12
+ readOperationLimits
13
+ } from "node-opcua-pseudo-session";
14
+ import { ClientSubscription } from "../../client_subscription";
15
+ import { ClientMonitoredItemImpl } from "../client_monitored_item_impl";
16
+ import { ClientSubscriptionImpl, TERMINATED_SUBSCRIPTION_ID, __create_subscription } from "../client_subscription_impl";
17
+ import { _shouldNotContinue } from "./reconnection";
18
+
19
+ const debugLog = make_debugLog("RECONNECTION");
20
+ const doDebug = checkDebugFlag("RECONNECTION");
21
+
22
+ async function createMonitoredItemsAndRespectOperationalLimits(
23
+ session: IBasicSessionReadAsync & IBasicSessionWithSubscription,
24
+ createMonitorItemsRequest: CreateMonitoredItemsRequest
25
+ ): Promise<CreateMonitoredItemsResponse> {
26
+ const operationalLimits = await readOperationLimits(session);
27
+ const createMonitoredItemResponse = await createMonitoredItemsLimit(
28
+ operationalLimits.maxMonitoredItemsPerCall || 0,
29
+ session,
30
+ createMonitorItemsRequest
31
+ );
32
+ return createMonitoredItemResponse;
33
+ }
34
+
35
+ async function adjustMonitoredItemNodeIds(subscription: ClientSubscription, oldMonitoredItems: any) {
36
+ // to Do
37
+ }
38
+ /**
39
+ * utility function to recreate new subscription
40
+ * @method recreateSubscriptionAndMonitoredItem
41
+ */
42
+ export async function recreateSubscriptionAndMonitoredItem(_subscription: ClientSubscription): Promise<void>
43
+ {
44
+ debugLog("recreateSubscriptionAndMonitoredItem", _subscription.subscriptionId.toString());
45
+
46
+ const subscription = _subscription as ClientSubscriptionImpl;
47
+ if (subscription.subscriptionId === TERMINATED_SUBSCRIPTION_ID) {
48
+ debugLog("Subscription is not in a valid state");
49
+ return;
50
+ }
51
+
52
+ const oldMonitoredItems = subscription.monitoredItems;
53
+ const oldSubscriptionId = subscription.subscriptionId;
54
+
55
+ subscription.publishEngine.unregisterSubscription(oldSubscriptionId);
56
+
57
+ await promisify(__create_subscription)(subscription);
58
+
59
+ const _err = _shouldNotContinue(subscription.session);
60
+ if (_err) { throw _err; }
61
+
62
+ const test = subscription.publishEngine.getSubscription(subscription.subscriptionId);
63
+
64
+ debugLog("recreating ", Object.keys(oldMonitoredItems).length, " monitored Items");
65
+ // re-create monitored items
66
+ const itemsToCreate: MonitoredItemCreateRequestOptions[] = [];
67
+
68
+ await adjustMonitoredItemNodeIds(subscription, oldMonitoredItems);
69
+
70
+ for (const monitoredItem of Object.values(oldMonitoredItems)) {
71
+ assert(monitoredItem.monitoringParameters.clientHandle > 0);
72
+ itemsToCreate.push({
73
+ itemToMonitor: monitoredItem.itemToMonitor,
74
+ monitoringMode: monitoredItem.monitoringMode,
75
+ requestedParameters: monitoredItem.monitoringParameters
76
+ });
77
+ }
78
+
79
+ const createMonitorItemsRequest = new CreateMonitoredItemsRequest({
80
+ itemsToCreate,
81
+ subscriptionId: subscription.subscriptionId,
82
+ timestampsToReturn: TimestampsToReturn.Both // this.timestampsToReturn,
83
+ });
84
+
85
+ const session = subscription.session;
86
+ // istanbul ignore next
87
+ if (!session) {
88
+ throw new Error("no session");
89
+ }
90
+
91
+ debugLog("Recreating ", itemsToCreate.length, " monitored items");
92
+
93
+ const response = await createMonitoredItemsAndRespectOperationalLimits(
94
+ session,
95
+ createMonitorItemsRequest);
96
+ const monitoredItemResults = response.results || [];
97
+
98
+ let _errCount = 0;
99
+ monitoredItemResults.forEach((monitoredItemResult, index) => {
100
+ const itemToCreate = itemsToCreate[index];
101
+ /* istanbul ignore next */
102
+ if (!itemToCreate || !itemToCreate.requestedParameters) {
103
+ _errCount++;
104
+ return;
105
+ }
106
+ const clientHandle = itemToCreate.requestedParameters.clientHandle;
107
+ /* istanbul ignore next */
108
+ if (!clientHandle) {
109
+ _errCount++;
110
+ return;
111
+ }
112
+ const monitoredItem = subscription.monitoredItems[clientHandle] as ClientMonitoredItemImpl;
113
+ if (monitoredItem) {
114
+ monitoredItem._applyResult(monitoredItemResult);
115
+ } else {
116
+ _errCount++;
117
+ warningLog("cannot find monitored item for clientHandle !:", clientHandle);
118
+ warningLog("itemsToCreate = ", itemsToCreate[index].toString());
119
+ }
120
+ });
121
+ if (_errCount > 0) {
122
+ warningLog("Warning: some monitored items have not been recreated properly");
123
+ }
124
+ }