node-opcua-client 2.123.0 → 2.124.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 +8 -8
- 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 +23 -30
- 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 +1 -1
- 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 +147 -0
- package/source/{reconnection.ts → private/reconnection/reconnection.ts} +120 -72
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
// tslint:disable:unified-signatures
|
|
5
5
|
import { EventEmitter } from "events";
|
|
6
|
-
import async from "async";
|
|
7
6
|
import chalk from "chalk";
|
|
8
7
|
|
|
9
8
|
import { assert } from "node-opcua-assert";
|
|
@@ -13,14 +12,11 @@ import { resolveNodeId } from "node-opcua-nodeid";
|
|
|
13
12
|
|
|
14
13
|
import { ReadValueIdOptions, TimestampsToReturn } from "node-opcua-service-read";
|
|
15
14
|
import {
|
|
16
|
-
CreateMonitoredItemsRequest,
|
|
17
|
-
CreateMonitoredItemsResponse,
|
|
18
15
|
CreateSubscriptionRequest,
|
|
19
16
|
CreateSubscriptionResponse,
|
|
20
17
|
DataChangeNotification,
|
|
21
18
|
DeleteMonitoredItemsResponse,
|
|
22
19
|
DeleteSubscriptionsResponse,
|
|
23
|
-
MonitoredItemCreateRequestOptions,
|
|
24
20
|
MonitoringParametersOptions,
|
|
25
21
|
NotificationMessage,
|
|
26
22
|
StatusChangeNotification,
|
|
@@ -37,9 +33,7 @@ import { StatusCode, StatusCodes } from "node-opcua-status-code";
|
|
|
37
33
|
import { Callback, ErrorCallback } from "node-opcua-status-code";
|
|
38
34
|
import { isNullOrUndefined } from "node-opcua-utils";
|
|
39
35
|
import { promoteOpaqueStructureInNotificationData } from "node-opcua-client-dynamic-extension-object";
|
|
40
|
-
import { createMonitoredItemsLimit, IBasicSessionReadAsync, readOperationLimits } from "node-opcua-pseudo-session";
|
|
41
36
|
|
|
42
|
-
import { IBasicSessionWithSubscription } from "node-opcua-pseudo-session";
|
|
43
37
|
import { ClientMonitoredItemBase } from "../client_monitored_item_base";
|
|
44
38
|
import { ClientMonitoredItemGroup } from "../client_monitored_item_group";
|
|
45
39
|
import { ClientSession, MonitoredItemData, SubscriptionId } from "../client_session";
|
|
@@ -59,13 +53,13 @@ import { ClientSidePublishEngine } from "./client_publish_engine";
|
|
|
59
53
|
import { ClientSessionImpl } from "./client_session_impl";
|
|
60
54
|
import { detectLongOperation } from "./performance";
|
|
61
55
|
|
|
62
|
-
const debugLog = make_debugLog(
|
|
63
|
-
const doDebug = checkDebugFlag(
|
|
64
|
-
const warningLog = make_warningLog(
|
|
56
|
+
const debugLog = make_debugLog("CLIENT_SUBSCRIPTION");
|
|
57
|
+
const doDebug = checkDebugFlag("CLIENT_SUBSCRIPTION");
|
|
58
|
+
const warningLog = make_warningLog("CLIENT_SUBSCRIPTION");
|
|
65
59
|
|
|
66
|
-
const PENDING_SUBSCRIPTION_ID = 0xc0cac01a;
|
|
67
|
-
const TERMINATED_SUBSCRIPTION_ID = 0xc0cac01b;
|
|
68
|
-
const TERMINATING_SUBSCRIPTION_ID = 0xc0cac01c;
|
|
60
|
+
export const PENDING_SUBSCRIPTION_ID = 0xc0cac01a;
|
|
61
|
+
export const TERMINATED_SUBSCRIPTION_ID = 0xc0cac01b;
|
|
62
|
+
export const TERMINATING_SUBSCRIPTION_ID = 0xc0cac01c;
|
|
69
63
|
|
|
70
64
|
const minimumMaxKeepAliveCount = 3;
|
|
71
65
|
|
|
@@ -122,20 +116,6 @@ function displayKeepAliveWarning(sessionTimeout: number, maxKeepAliveCount: numb
|
|
|
122
116
|
return false;
|
|
123
117
|
}
|
|
124
118
|
|
|
125
|
-
function createMonitoredItemsAndRespectOperationalLimits(
|
|
126
|
-
session: IBasicSessionReadAsync & IBasicSessionWithSubscription,
|
|
127
|
-
createMonitorItemsRequest: CreateMonitoredItemsRequest,
|
|
128
|
-
callback: (err: Error | null, response?: CreateMonitoredItemsResponse) => void
|
|
129
|
-
) {
|
|
130
|
-
readOperationLimits(session)
|
|
131
|
-
.then((operationalLimits) => {
|
|
132
|
-
createMonitoredItemsLimit(operationalLimits.maxMonitoredItemsPerCall || 0, session, createMonitorItemsRequest)
|
|
133
|
-
.then((createMonitoredItemResponse) => callback(null, createMonitoredItemResponse))
|
|
134
|
-
.catch(callback);
|
|
135
|
-
})
|
|
136
|
-
.catch(callback);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
119
|
export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscription {
|
|
140
120
|
/**
|
|
141
121
|
* the associated session
|
|
@@ -171,9 +151,8 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
|
|
|
171
151
|
public publishEngine: ClientSidePublishEngine;
|
|
172
152
|
|
|
173
153
|
public lastSequenceNumber: number;
|
|
174
|
-
private lastRequestSentTime: Date;
|
|
175
154
|
private _nextClientHandle = 0;
|
|
176
|
-
|
|
155
|
+
public hasTimedOut: boolean;
|
|
177
156
|
|
|
178
157
|
constructor(session: ClientSession, options: ClientSubscriptionOptions) {
|
|
179
158
|
super();
|
|
@@ -219,7 +198,7 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
|
|
|
219
198
|
this.lifetimeCount = options.requestedLifetimeCount;
|
|
220
199
|
this.maxKeepAliveCount = options.requestedMaxKeepAliveCount;
|
|
221
200
|
this.maxNotificationsPerPublish = options.maxNotificationsPerPublish || 0;
|
|
222
|
-
this.publishingEnabled = options.publishingEnabled;
|
|
201
|
+
this.publishingEnabled = options.publishingEnabled === undefined ? true : options.publishingEnabled;
|
|
223
202
|
this.priority = options.priority;
|
|
224
203
|
|
|
225
204
|
this.subscriptionId = PENDING_SUBSCRIPTION_ID;
|
|
@@ -227,8 +206,6 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
|
|
|
227
206
|
this._nextClientHandle = 0;
|
|
228
207
|
this.monitoredItems = {};
|
|
229
208
|
|
|
230
|
-
this.lastRequestSentTime = new Date(1, 1, 1970);
|
|
231
|
-
|
|
232
209
|
/**
|
|
233
210
|
* set to True when the server has notified us that this subscription has timed out
|
|
234
211
|
* ( maxLifeCounter x published interval without being able to process a PublishRequest
|
|
@@ -238,7 +215,7 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
|
|
|
238
215
|
this.hasTimedOut = false;
|
|
239
216
|
|
|
240
217
|
setImmediate(() => {
|
|
241
|
-
|
|
218
|
+
__create_subscription(this, (err?: Error) => {
|
|
242
219
|
if (!err) {
|
|
243
220
|
setImmediate(() => {
|
|
244
221
|
/**
|
|
@@ -536,103 +513,6 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
|
|
|
536
513
|
this.session.getMonitoredItems(this.subscriptionId, args[0]);
|
|
537
514
|
}
|
|
538
515
|
|
|
539
|
-
/**
|
|
540
|
-
* utility function to recreate new subscription
|
|
541
|
-
* @method recreateSubscriptionAndMonitoredItem
|
|
542
|
-
*/
|
|
543
|
-
public recreateSubscriptionAndMonitoredItem(callback: ErrorCallback): void {
|
|
544
|
-
debugLog("ClientSubscription#recreateSubscriptionAndMonitoredItem");
|
|
545
|
-
|
|
546
|
-
if (this.subscriptionId === TERMINATED_SUBSCRIPTION_ID) {
|
|
547
|
-
debugLog("Subscription is not in a valid state");
|
|
548
|
-
return callback();
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
const oldMonitoredItems = this.monitoredItems;
|
|
552
|
-
|
|
553
|
-
this.publishEngine.unregisterSubscription(this.subscriptionId);
|
|
554
|
-
|
|
555
|
-
async.series(
|
|
556
|
-
[
|
|
557
|
-
(innerCallback: ErrorCallback) => {
|
|
558
|
-
this.__create_subscription(innerCallback);
|
|
559
|
-
},
|
|
560
|
-
(innerCallback: ErrorCallback) => {
|
|
561
|
-
const test = this.publishEngine.getSubscription(this.subscriptionId);
|
|
562
|
-
|
|
563
|
-
debugLog("recreating ", Object.keys(oldMonitoredItems).length, " monitored Items");
|
|
564
|
-
// re-create monitored items
|
|
565
|
-
const itemsToCreate: MonitoredItemCreateRequestOptions[] = [];
|
|
566
|
-
|
|
567
|
-
for (const monitoredItem of Object.values(oldMonitoredItems)) {
|
|
568
|
-
assert(monitoredItem.monitoringParameters.clientHandle > 0);
|
|
569
|
-
itemsToCreate.push({
|
|
570
|
-
itemToMonitor: monitoredItem.itemToMonitor,
|
|
571
|
-
monitoringMode: monitoredItem.monitoringMode,
|
|
572
|
-
requestedParameters: monitoredItem.monitoringParameters
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
const createMonitorItemsRequest = new CreateMonitoredItemsRequest({
|
|
577
|
-
itemsToCreate,
|
|
578
|
-
subscriptionId: this.subscriptionId,
|
|
579
|
-
timestampsToReturn: TimestampsToReturn.Both // this.timestampsToReturn,
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
const session = this.session;
|
|
583
|
-
// istanbul ignore next
|
|
584
|
-
if (!session) {
|
|
585
|
-
return innerCallback(new Error("no session"));
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
debugLog("Recreating ", itemsToCreate.length, " monitored items");
|
|
589
|
-
|
|
590
|
-
createMonitoredItemsAndRespectOperationalLimits(
|
|
591
|
-
session,
|
|
592
|
-
createMonitorItemsRequest,
|
|
593
|
-
(err: Error | null, response?: CreateMonitoredItemsResponse) => {
|
|
594
|
-
if (err) {
|
|
595
|
-
debugLog("Recreating monitored item has failed with ", err.message);
|
|
596
|
-
return innerCallback(err);
|
|
597
|
-
}
|
|
598
|
-
/* istanbul ignore next */
|
|
599
|
-
if (!response) {
|
|
600
|
-
return innerCallback(new Error("Internal Error"));
|
|
601
|
-
}
|
|
602
|
-
const monitoredItemResults = response.results || [];
|
|
603
|
-
|
|
604
|
-
monitoredItemResults.forEach((monitoredItemResult, index) => {
|
|
605
|
-
const itemToCreate = itemsToCreate[index];
|
|
606
|
-
/* istanbul ignore next */
|
|
607
|
-
if (!itemToCreate || !itemToCreate.requestedParameters) {
|
|
608
|
-
throw new Error("Internal Error");
|
|
609
|
-
}
|
|
610
|
-
const clientHandle = itemToCreate.requestedParameters.clientHandle;
|
|
611
|
-
/* istanbul ignore next */
|
|
612
|
-
if (!clientHandle) {
|
|
613
|
-
throw new Error("Internal Error");
|
|
614
|
-
}
|
|
615
|
-
const monitoredItem = this.monitoredItems[clientHandle] as ClientMonitoredItemImpl;
|
|
616
|
-
if (monitoredItem) {
|
|
617
|
-
monitoredItem._applyResult(monitoredItemResult);
|
|
618
|
-
} else {
|
|
619
|
-
warningLog("cannot find monitored item for clientHandle !:", clientHandle);
|
|
620
|
-
}
|
|
621
|
-
});
|
|
622
|
-
innerCallback();
|
|
623
|
-
}
|
|
624
|
-
);
|
|
625
|
-
}
|
|
626
|
-
],
|
|
627
|
-
(err) => {
|
|
628
|
-
if (err) {
|
|
629
|
-
warningLog(err.message);
|
|
630
|
-
}
|
|
631
|
-
callback(err!);
|
|
632
|
-
}
|
|
633
|
-
);
|
|
634
|
-
}
|
|
635
|
-
|
|
636
516
|
public toString(): string {
|
|
637
517
|
let str = "";
|
|
638
518
|
str += "subscriptionId : " + this.subscriptionId + "\n";
|
|
@@ -642,9 +522,12 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
|
|
|
642
522
|
str += "hasTimedOut : " + this.hasTimedOut + "\n";
|
|
643
523
|
|
|
644
524
|
const timeToLive = this.lifetimeCount * this.publishingInterval;
|
|
645
|
-
str += "
|
|
646
|
-
str += "
|
|
647
|
-
|
|
525
|
+
str += "(maxKeepAliveCount*publishingInterval: " + this.publishingInterval * this.maxKeepAliveCount + " ms)\n";
|
|
526
|
+
str += "(maxLifetimeCount*publishingInterval: " + timeToLive + " ms)\n";
|
|
527
|
+
|
|
528
|
+
const lastRequestSentTime = this.publishEngine.lastRequestSentTime;
|
|
529
|
+
str += "lastRequestSentTime : " + lastRequestSentTime.toString() + "\n";
|
|
530
|
+
const duration = Date.now() - lastRequestSentTime.getTime();
|
|
648
531
|
const extra =
|
|
649
532
|
duration - timeToLive > 0
|
|
650
533
|
? chalk.red(" expired since " + (duration - timeToLive) / 1000 + " seconds")
|
|
@@ -654,7 +537,6 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
|
|
|
654
537
|
str += "has expired : " + (duration > timeToLive) + "\n";
|
|
655
538
|
|
|
656
539
|
str += "(session timeout : " + this.session.timeout + " ms)\n";
|
|
657
|
-
str += "(maxKeepAliveCount*publishingInterval: " + this.publishingInterval * this.session.timeout + " ms)\n";
|
|
658
540
|
|
|
659
541
|
return str;
|
|
660
542
|
}
|
|
@@ -665,7 +547,8 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
|
|
|
665
547
|
public evaluateRemainingLifetime(): number {
|
|
666
548
|
const now = Date.now();
|
|
667
549
|
const timeout = this.publishingInterval * this.lifetimeCount;
|
|
668
|
-
const
|
|
550
|
+
const lastRequestSentTime = this.publishEngine.lastRequestSentTime;
|
|
551
|
+
const expiryTime = lastRequestSentTime.getTime() + timeout;
|
|
669
552
|
return Math.max(0, expiryTime - now);
|
|
670
553
|
}
|
|
671
554
|
|
|
@@ -710,75 +593,6 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
|
|
|
710
593
|
setImmediate(waitForSubscriptionAndMonitor);
|
|
711
594
|
}
|
|
712
595
|
|
|
713
|
-
private __create_subscription(callback: ErrorCallback) {
|
|
714
|
-
assert(typeof callback === "function");
|
|
715
|
-
|
|
716
|
-
// istanbul ignore next
|
|
717
|
-
if (!this.hasSession) {
|
|
718
|
-
return callback(new Error("No Session"));
|
|
719
|
-
}
|
|
720
|
-
const session = this.session;
|
|
721
|
-
|
|
722
|
-
debugLog(chalk.yellow.bold("ClientSubscription created "));
|
|
723
|
-
|
|
724
|
-
const request = new CreateSubscriptionRequest({
|
|
725
|
-
maxNotificationsPerPublish: this.maxNotificationsPerPublish,
|
|
726
|
-
priority: this.priority,
|
|
727
|
-
publishingEnabled: this.publishingEnabled,
|
|
728
|
-
requestedLifetimeCount: this.lifetimeCount,
|
|
729
|
-
requestedMaxKeepAliveCount: this.maxKeepAliveCount,
|
|
730
|
-
requestedPublishingInterval: this.publishingInterval
|
|
731
|
-
});
|
|
732
|
-
|
|
733
|
-
session.createSubscription(request, (err: Error | null, response?: CreateSubscriptionResponse) => {
|
|
734
|
-
if (err) {
|
|
735
|
-
/* istanbul ignore next */
|
|
736
|
-
this.emit("internal_error", err);
|
|
737
|
-
if (callback) {
|
|
738
|
-
return callback(err);
|
|
739
|
-
}
|
|
740
|
-
return;
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
/* istanbul ignore next */
|
|
744
|
-
if (!response) {
|
|
745
|
-
return callback(new Error("internal error"));
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
if (!this.hasSession) {
|
|
749
|
-
return callback(new Error("createSubscription has failed = > no session"));
|
|
750
|
-
}
|
|
751
|
-
assert(this.hasSession);
|
|
752
|
-
|
|
753
|
-
this.subscriptionId = response.subscriptionId;
|
|
754
|
-
this.publishingInterval = response.revisedPublishingInterval;
|
|
755
|
-
this.lifetimeCount = response.revisedLifetimeCount;
|
|
756
|
-
this.maxKeepAliveCount = response.revisedMaxKeepAliveCount;
|
|
757
|
-
|
|
758
|
-
this.timeoutHint = Math.min((this.maxKeepAliveCount + 10) * this.publishingInterval * 2, 0x7ffff);
|
|
759
|
-
|
|
760
|
-
displayKeepAliveWarning(this.session.timeout, this.maxKeepAliveCount, this.publishingInterval);
|
|
761
|
-
ClientSubscription.ignoreNextWarning = false;
|
|
762
|
-
|
|
763
|
-
// istanbul ignore next
|
|
764
|
-
if (doDebug) {
|
|
765
|
-
debugLog(chalk.yellow.bold("registering callback"));
|
|
766
|
-
debugLog(chalk.yellow.bold("publishingInterval "), this.publishingInterval);
|
|
767
|
-
debugLog(chalk.yellow.bold("lifetimeCount "), this.lifetimeCount);
|
|
768
|
-
debugLog(chalk.yellow.bold("maxKeepAliveCount "), this.maxKeepAliveCount);
|
|
769
|
-
debugLog(chalk.yellow.bold("publish request timeout hint = "), this.timeoutHint);
|
|
770
|
-
debugLog(chalk.yellow.bold("hasTimedOut "), this.hasTimedOut);
|
|
771
|
-
debugLog(chalk.yellow.bold("timeoutHint for publish request "), this.timeoutHint);
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
this.publishEngine.registerSubscription(this);
|
|
775
|
-
|
|
776
|
-
if (callback) {
|
|
777
|
-
callback();
|
|
778
|
-
}
|
|
779
|
-
});
|
|
780
|
-
}
|
|
781
|
-
|
|
782
596
|
private __on_publish_response_DataChangeNotification(notification: DataChangeNotification) {
|
|
783
597
|
assert(notification.schema.name === "DataChangeNotification");
|
|
784
598
|
|
|
@@ -939,7 +753,7 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
|
|
|
939
753
|
warningLog(
|
|
940
754
|
`[NODE-OPCUA-W32]}: monitored.item event handler takes too much time : operation duration ${duration} ms [repeated ${
|
|
941
755
|
s(this).$_slowNotifCount
|
|
942
|
-
} times]\n please ensure that your
|
|
756
|
+
} times]\n please ensure that your monitoredItem event handler is not blocking the event loop.`
|
|
943
757
|
);
|
|
944
758
|
}
|
|
945
759
|
);
|
|
@@ -1057,12 +871,76 @@ ClientSubscriptionImpl.prototype.monitor = thenify.withCallback(ClientSubscripti
|
|
|
1057
871
|
ClientSubscriptionImpl.prototype.monitorItems = thenify.withCallback(ClientSubscriptionImpl.prototype.monitorItems);
|
|
1058
872
|
ClientSubscriptionImpl.prototype.setTriggering = thenify.withCallback(ClientSubscriptionImpl.prototype.setTriggering);
|
|
1059
873
|
ClientSubscriptionImpl.prototype.modify = thenify.withCallback(ClientSubscriptionImpl.prototype.modify);
|
|
1060
|
-
ClientSubscriptionImpl.prototype.recreateSubscriptionAndMonitoredItem = thenify.withCallback(
|
|
1061
|
-
ClientSubscriptionImpl.prototype.recreateSubscriptionAndMonitoredItem
|
|
1062
|
-
);
|
|
1063
874
|
ClientSubscriptionImpl.prototype.terminate = thenify.withCallback(ClientSubscriptionImpl.prototype.terminate);
|
|
1064
875
|
ClientSubscriptionImpl.prototype.getMonitoredItems = thenify.withCallback(ClientSubscriptionImpl.prototype.getMonitoredItems);
|
|
1065
876
|
|
|
1066
877
|
ClientSubscription.create = (clientSession: ClientSession, options: ClientSubscriptionOptions) => {
|
|
1067
878
|
return new ClientSubscriptionImpl(clientSession, options);
|
|
1068
879
|
};
|
|
880
|
+
|
|
881
|
+
export function __create_subscription(subscription: ClientSubscriptionImpl, callback: ErrorCallback) {
|
|
882
|
+
// istanbul ignore next
|
|
883
|
+
if (!subscription.hasSession) {
|
|
884
|
+
return callback(new Error("__create_subscription: subscription has no Session"));
|
|
885
|
+
}
|
|
886
|
+
const session = subscription.session;
|
|
887
|
+
|
|
888
|
+
debugLog(chalk.yellow.bold("ClientSubscription created "));
|
|
889
|
+
|
|
890
|
+
const request = new CreateSubscriptionRequest({
|
|
891
|
+
maxNotificationsPerPublish: subscription.maxNotificationsPerPublish,
|
|
892
|
+
priority: subscription.priority,
|
|
893
|
+
publishingEnabled: subscription.publishingEnabled,
|
|
894
|
+
requestedLifetimeCount: subscription.lifetimeCount,
|
|
895
|
+
requestedMaxKeepAliveCount: subscription.maxKeepAliveCount,
|
|
896
|
+
requestedPublishingInterval: subscription.publishingInterval
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
session.createSubscription(request, (err: Error | null, response?: CreateSubscriptionResponse) => {
|
|
900
|
+
if (err) {
|
|
901
|
+
/* istanbul ignore next */
|
|
902
|
+
subscription.emit("internal_error", err);
|
|
903
|
+
if (callback) {
|
|
904
|
+
return callback(err);
|
|
905
|
+
}
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
/* istanbul ignore next */
|
|
910
|
+
if (!response) {
|
|
911
|
+
return callback(new Error("internal error"));
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
if (!subscription.hasSession) {
|
|
915
|
+
return callback(new Error("createSubscription has failed = > no session"));
|
|
916
|
+
}
|
|
917
|
+
assert(subscription.hasSession);
|
|
918
|
+
|
|
919
|
+
subscription.subscriptionId = response.subscriptionId;
|
|
920
|
+
subscription.publishingInterval = response.revisedPublishingInterval;
|
|
921
|
+
subscription.lifetimeCount = response.revisedLifetimeCount;
|
|
922
|
+
subscription.maxKeepAliveCount = response.revisedMaxKeepAliveCount;
|
|
923
|
+
|
|
924
|
+
subscription.timeoutHint = Math.min((subscription.maxKeepAliveCount + 10) * subscription.publishingInterval * 2, 0x7ffff);
|
|
925
|
+
|
|
926
|
+
displayKeepAliveWarning(subscription.session.timeout, subscription.maxKeepAliveCount, subscription.publishingInterval);
|
|
927
|
+
ClientSubscription.ignoreNextWarning = false;
|
|
928
|
+
|
|
929
|
+
// istanbul ignore next
|
|
930
|
+
if (doDebug) {
|
|
931
|
+
debugLog(chalk.yellow.bold("registering callback"));
|
|
932
|
+
debugLog(chalk.yellow.bold("publishingInterval "), subscription.publishingInterval);
|
|
933
|
+
debugLog(chalk.yellow.bold("lifetimeCount "), subscription.lifetimeCount);
|
|
934
|
+
debugLog(chalk.yellow.bold("maxKeepAliveCount "), subscription.maxKeepAliveCount);
|
|
935
|
+
debugLog(chalk.yellow.bold("publish request timeout hint = "), subscription.timeoutHint);
|
|
936
|
+
debugLog(chalk.yellow.bold("hasTimedOut "), subscription.hasTimedOut);
|
|
937
|
+
debugLog(chalk.yellow.bold("timeoutHint for publish request "), subscription.timeoutHint);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
subscription.publishEngine.registerSubscription(subscription);
|
|
941
|
+
|
|
942
|
+
if (callback) {
|
|
943
|
+
callback();
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
}
|
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
} from "../opcua_client";
|
|
52
52
|
import { AnonymousIdentity, UserIdentityInfo, UserIdentityInfoUserName, UserIdentityInfoX509 } from "../user_identity_info";
|
|
53
53
|
|
|
54
|
-
import { repair_client_sessions } from "
|
|
54
|
+
import { repair_client_sessions } from "./reconnection/reconnection";
|
|
55
55
|
import { ClientBaseImpl } from "./client_base_impl";
|
|
56
56
|
import { ClientSessionImpl } from "./client_session_impl";
|
|
57
57
|
import { ClientSubscriptionImpl } from "./client_subscription_impl";
|
|
@@ -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, callback);
|
|
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
|
+
}
|