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.
- package/dist/alarms_and_conditions/client_alarm_tools.js.map +1 -1
- package/dist/alarms_and_conditions/client_tools.js.map +1 -1
- package/dist/client_monitored_item_toolbox.js.map +1 -1
- package/dist/client_session_keepalive_manager.js.map +1 -1
- package/dist/client_utils.js.map +1 -1
- package/dist/private/client_base_impl.d.ts +11 -4
- package/dist/private/client_base_impl.js +130 -70
- package/dist/private/client_base_impl.js.map +1 -1
- package/dist/private/client_monitored_item_group_impl.js.map +1 -1
- package/dist/private/client_monitored_item_impl.js.map +1 -1
- package/dist/private/client_publish_engine.d.ts +9 -4
- package/dist/private/client_publish_engine.js +5 -99
- package/dist/private/client_publish_engine.js.map +1 -1
- package/dist/private/client_session_impl.d.ts +20 -4
- package/dist/private/client_session_impl.js +5 -24
- package/dist/private/client_session_impl.js.map +1 -1
- package/dist/private/client_subscription_impl.d.ts +5 -8
- package/dist/private/client_subscription_impl.js +83 -174
- package/dist/private/client_subscription_impl.js.map +1 -1
- package/dist/private/i_private_client.d.ts +0 -1
- package/dist/private/opcua_client_impl.js +1 -1
- package/dist/private/opcua_client_impl.js.map +1 -1
- package/dist/private/performance.js.map +1 -1
- package/dist/private/reconnection/client_publish_engine_reconnection.d.ts +2 -0
- package/dist/private/reconnection/client_publish_engine_reconnection.js +118 -0
- package/dist/private/reconnection/client_publish_engine_reconnection.js.map +1 -0
- package/dist/private/reconnection/client_reconnection.d.ts +2 -0
- package/dist/private/reconnection/client_reconnection.js +20 -0
- package/dist/private/reconnection/client_reconnection.js.map +1 -0
- package/dist/private/reconnection/client_subscription_reconnection.d.ts +7 -0
- package/dist/private/reconnection/client_subscription_reconnection.js +124 -0
- package/dist/private/reconnection/client_subscription_reconnection.js.map +1 -0
- package/dist/private/reconnection/reconnection.d.ts +12 -0
- package/dist/private/reconnection/reconnection.js +564 -0
- package/dist/private/reconnection/reconnection.js.map +1 -0
- package/dist/tools/findservers.js.map +1 -1
- package/dist/tools/read_history_server_capabilities.js.map +1 -1
- package/dist/verify.js.map +1 -1
- package/package.json +43 -43
- package/source/index.ts +4 -1
- package/source/opcua_client.ts +11 -5
- package/source/private/client_base_impl.ts +136 -81
- package/source/private/client_publish_engine.ts +13 -129
- package/source/private/client_session_impl.ts +56 -78
- package/source/private/client_subscription_impl.ts +85 -207
- package/source/private/i_private_client.ts +0 -2
- package/source/private/opcua_client_impl.ts +16 -149
- package/source/private/reconnection/client_publish_engine_reconnection.ts +146 -0
- package/source/private/reconnection/client_reconnection.ts +17 -0
- package/source/private/reconnection/client_subscription_reconnection.ts +124 -0
- 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
|
+
}
|