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:no-unused-expression
|
|
5
4
|
import fs from "node:fs";
|
|
6
5
|
import path from "node:path";
|
|
7
6
|
|
|
@@ -53,7 +52,11 @@ import {
|
|
|
53
52
|
import { type ChannelSecurityToken, coerceMessageSecurityMode, MessageSecurityMode } from "node-opcua-service-secure-channel";
|
|
54
53
|
import { CloseSessionRequest, type CloseSessionResponse } from "node-opcua-service-session";
|
|
55
54
|
import { type ErrorCallback, StatusCodes } from "node-opcua-status-code";
|
|
56
|
-
import {
|
|
55
|
+
import {
|
|
56
|
+
type IAcceptedReverseConnection,
|
|
57
|
+
type IClientTransportFactory,
|
|
58
|
+
makeReverseClientTransportFactory
|
|
59
|
+
} from "node-opcua-transport";
|
|
57
60
|
import { checkFileExistsAndIsNotEmpty, matchUri } from "node-opcua-utils";
|
|
58
61
|
import {
|
|
59
62
|
type CreateSecureChannelCallbackFunc,
|
|
@@ -222,10 +225,10 @@ function __findEndpoint(this: ClientBaseImpl, endpointUrl: string, params: FindE
|
|
|
222
225
|
return callback(
|
|
223
226
|
new Error(
|
|
224
227
|
"Cannot find an Endpoint matching " +
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
228
|
+
" security mode: " +
|
|
229
|
+
securityMode.toString() +
|
|
230
|
+
" policy: " +
|
|
231
|
+
securityPolicy.toString()
|
|
229
232
|
)
|
|
230
233
|
);
|
|
231
234
|
}
|
|
@@ -263,16 +266,19 @@ async function _verify_serverCertificate(certificateManager: ICertificateStore,
|
|
|
263
266
|
}
|
|
264
267
|
|
|
265
268
|
const forceEndpointDiscoveryOnConnect = !!parseInt(process.env.NODEOPCUA_CLIENT_FORCE_ENDPOINT_DISCOVERY || "0", 10);
|
|
266
|
-
|
|
269
|
+
// c8 ignore next
|
|
270
|
+
doDebug && debugLog("forceEndpointDiscoveryOnConnect = ", forceEndpointDiscoveryOnConnect);
|
|
267
271
|
|
|
268
272
|
class ClockAdjustment {
|
|
269
273
|
constructor() {
|
|
270
|
-
|
|
274
|
+
// c8 ignore next
|
|
275
|
+
doDebug && debugLog("installPeriodicClockAdjustment ", periodicClockAdjustment.timerInstallationCount);
|
|
271
276
|
installPeriodicClockAdjustment();
|
|
272
277
|
}
|
|
273
278
|
dispose() {
|
|
274
279
|
uninstallPeriodicClockAdjustment();
|
|
275
|
-
|
|
280
|
+
// c8 ignore next
|
|
281
|
+
doDebug && debugLog("uninstallPeriodicClockAdjustment ", periodicClockAdjustment.timerInstallationCount);
|
|
276
282
|
}
|
|
277
283
|
}
|
|
278
284
|
|
|
@@ -311,10 +317,10 @@ let g_ClientCounter = 0;
|
|
|
311
317
|
/**
|
|
312
318
|
* @internal
|
|
313
319
|
*/
|
|
314
|
-
// tslint:disable-next-line: max-classes-per-file
|
|
315
320
|
export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBaseEvents>
|
|
316
321
|
extends OPCUASecureObject<Events>
|
|
317
|
-
implements OPCUAClientBase<Events>, IClientBase
|
|
322
|
+
implements OPCUAClientBase<Events>, IClientBase
|
|
323
|
+
{
|
|
318
324
|
/**
|
|
319
325
|
* total number of requests that been canceled due to timeout
|
|
320
326
|
*/
|
|
@@ -473,8 +479,7 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
473
479
|
}
|
|
474
480
|
const cm = options.clientCertificateManager as OPCUACertificateManager;
|
|
475
481
|
options.privateKeyFile = options.privateKeyFile || cm.privateKey;
|
|
476
|
-
options.certificateFile =
|
|
477
|
-
options.certificateFile || path.join(cm.rootDir, "own/certs/client_certificate.pem");
|
|
482
|
+
options.certificateFile = options.certificateFile || path.join(cm.rootDir, "own/certs/client_certificate.pem");
|
|
478
483
|
super(options as IOPCUASecureObjectOptions);
|
|
479
484
|
}
|
|
480
485
|
|
|
@@ -491,7 +496,10 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
491
496
|
// we need to delay _applicationUri initialization
|
|
492
497
|
this._applicationUri = options.applicationUri || this._getBuiltApplicationUri();
|
|
493
498
|
|
|
494
|
-
|
|
499
|
+
if (!options.clientCertificateManager) {
|
|
500
|
+
throw new Error("internal error: clientCertificateManager should have been set above");
|
|
501
|
+
}
|
|
502
|
+
this.clientCertificateManager = options.clientCertificateManager;
|
|
495
503
|
this.clientCertificateManager.referenceCounter++;
|
|
496
504
|
|
|
497
505
|
this._secureChannel = null;
|
|
@@ -555,13 +563,16 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
555
563
|
warningLog("internal error: _cancel_reconnection should only be used when reconnecting is in progress");
|
|
556
564
|
}
|
|
557
565
|
|
|
558
|
-
|
|
566
|
+
// c8 ignore next
|
|
567
|
+
doDebug && debugLog("canceling reconnection : ", this.clientName);
|
|
559
568
|
|
|
560
569
|
this._reconnectionIsCanceled = true;
|
|
561
570
|
|
|
562
571
|
// c8 ignore next
|
|
563
572
|
if (!this._secureChannel) {
|
|
564
|
-
|
|
573
|
+
// c8 ignore next
|
|
574
|
+
doDebug &&
|
|
575
|
+
debugLog("_cancel_reconnection: Nothing to do for !", this.clientName, " because secure channel doesn't exist");
|
|
565
576
|
return callback(); // nothing to do
|
|
566
577
|
}
|
|
567
578
|
|
|
@@ -572,10 +583,12 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
572
583
|
}
|
|
573
584
|
|
|
574
585
|
public _recreate_secure_channel(callback: ErrorCallback): void {
|
|
575
|
-
|
|
586
|
+
// c8 ignore next
|
|
587
|
+
doDebug && debugLog("_recreate_secure_channel... while internalState is", this._internalState);
|
|
576
588
|
|
|
577
589
|
if (!this.knowsServerEndpoint) {
|
|
578
|
-
|
|
590
|
+
// c8 ignore next
|
|
591
|
+
doDebug && debugLog("Cannot reconnect , server endpoint is unknown");
|
|
579
592
|
callback(new Error("Cannot reconnect, server endpoint is unknown - this.knowsServerEndpoint = false"));
|
|
580
593
|
return;
|
|
581
594
|
}
|
|
@@ -602,7 +615,8 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
602
615
|
};
|
|
603
616
|
|
|
604
617
|
const _failAndRetry = (err: Error, message: string, callback: ErrorCallback) => {
|
|
605
|
-
|
|
618
|
+
// c8 ignore next
|
|
619
|
+
doDebug && debugLog("failAndRetry; ", message);
|
|
606
620
|
if (this._reconnectionIsCanceled) {
|
|
607
621
|
return _when_reconnectionIsCanceled(callback);
|
|
608
622
|
}
|
|
@@ -618,13 +632,15 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
618
632
|
this.emit("after_reconnection", null); // send after callback
|
|
619
633
|
assert(this._secureChannel, "expecting a secureChannel here ");
|
|
620
634
|
// a new channel has be created and a new connection is established
|
|
621
|
-
|
|
635
|
+
// c8 ignore next
|
|
636
|
+
doDebug && debugLog(chalk.bgWhite.red("ClientBaseImpl: RECONNECTED !!!"));
|
|
622
637
|
this._setInternalState("reconnecting_newchannel_connected");
|
|
623
638
|
return callback();
|
|
624
639
|
};
|
|
625
640
|
|
|
626
641
|
const _attempt_to_recreate_secure_channel = (callback: ErrorCallback) => {
|
|
627
|
-
|
|
642
|
+
// c8 ignore next
|
|
643
|
+
doDebug && debugLog("attempt to recreate a new secure channel");
|
|
628
644
|
if (this._reconnectionIsCanceled) {
|
|
629
645
|
return _when_reconnectionIsCanceled(callback);
|
|
630
646
|
}
|
|
@@ -727,11 +743,13 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
727
743
|
assert(this._secureChannel === null);
|
|
728
744
|
assert(typeof this.endpointUrl === "string");
|
|
729
745
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
746
|
+
// c8 ignore next
|
|
747
|
+
doDebug &&
|
|
748
|
+
debugLog(
|
|
749
|
+
"_internal_create_secure_channel creating new ClientSecureChannelLayer _internalState =",
|
|
750
|
+
this._internalState,
|
|
751
|
+
this.clientName
|
|
752
|
+
);
|
|
735
753
|
const secureChannel = new ClientSecureChannelLayer({
|
|
736
754
|
connectionStrategy,
|
|
737
755
|
defaultSecureTokenLifetime: this.defaultSecureTokenLifetime,
|
|
@@ -761,12 +779,15 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
761
779
|
|
|
762
780
|
const step2_openSecureChannel = (): Promise<void> => {
|
|
763
781
|
return new Promise<void>((resolve, reject) => {
|
|
764
|
-
|
|
782
|
+
// c8 ignore next
|
|
783
|
+
doDebug && debugLog("_internal_create_secure_channel before secureChannel.create");
|
|
765
784
|
|
|
766
785
|
secureChannel.create(this.endpointUrl, (err?: Error) => {
|
|
767
|
-
|
|
786
|
+
// c8 ignore next
|
|
787
|
+
doDebug && debugLog("_internal_create_secure_channel after secureChannel.create");
|
|
768
788
|
if (!this._secureChannel) {
|
|
769
|
-
|
|
789
|
+
// c8 ignore next
|
|
790
|
+
doDebug && debugLog("_secureChannel has been closed during the transaction !");
|
|
770
791
|
return reject(new Error("Secure Channel Closed"));
|
|
771
792
|
}
|
|
772
793
|
if (err) {
|
|
@@ -786,7 +807,8 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
786
807
|
|
|
787
808
|
this.getEndpoints((err: Error | null /*, endpoints?: EndpointDescription[]*/) => {
|
|
788
809
|
if (!this._secureChannel) {
|
|
789
|
-
|
|
810
|
+
// c8 ignore next
|
|
811
|
+
doDebug && debugLog("_secureChannel has been closed during the transaction ! (while getEndpoints)");
|
|
790
812
|
return reject(new Error("Secure Channel Closed"));
|
|
791
813
|
}
|
|
792
814
|
if (err) {
|
|
@@ -870,11 +892,18 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
870
892
|
this.applicationName,
|
|
871
893
|
this._getBuiltApplicationUri()
|
|
872
894
|
);
|
|
873
|
-
|
|
895
|
+
// c8 ignore next
|
|
896
|
+
doDebug && debugLog("privateKey = ", this.privateKeyFile);
|
|
874
897
|
if ("privateKey" in this.clientCertificateManager) {
|
|
875
|
-
|
|
898
|
+
// c8 ignore next
|
|
899
|
+
doDebug &&
|
|
900
|
+
debugLog(
|
|
901
|
+
" = ",
|
|
902
|
+
(this.clientCertificateManager as unknown as OPCUACertificateManager).privateKey
|
|
903
|
+
);
|
|
876
904
|
}
|
|
877
|
-
|
|
905
|
+
// c8 ignore next
|
|
906
|
+
doDebug && debugLog("certificateFile = ", this.certificateFile);
|
|
878
907
|
const _certificate = this.getCertificate();
|
|
879
908
|
// Note: do NOT eagerly call this.getPrivateKey() here. A freshly
|
|
880
909
|
// generated key may already be passphrase-encrypted on disk (when
|
|
@@ -900,8 +929,8 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
900
929
|
// disconnect
|
|
901
930
|
errorLog(
|
|
902
931
|
"[NODE-OPCUA-E08] initializeCM: clientCertificateManager is null\n" +
|
|
903
|
-
|
|
904
|
-
|
|
932
|
+
" This happen when you disconnected the client, to free resources.\n" +
|
|
933
|
+
" Please create a new OPCUAClient instance if you want to reconnect"
|
|
905
934
|
);
|
|
906
935
|
return;
|
|
907
936
|
}
|
|
@@ -938,7 +967,7 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
938
967
|
if (err instanceof PrivateKeyPassphraseRequiredError) {
|
|
939
968
|
throw new Error(
|
|
940
969
|
`[NODE-OPCUA-E31] private key ${this.privateKeyFile} is encrypted; ` +
|
|
941
|
-
|
|
970
|
+
"set privateKeyPassphrase on the OPCUACertificateManager passed as clientCertificateManager"
|
|
942
971
|
);
|
|
943
972
|
}
|
|
944
973
|
throw err;
|
|
@@ -955,7 +984,8 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
955
984
|
protected _internalState: InternalClientState = "uninitialized";
|
|
956
985
|
|
|
957
986
|
protected _handleUnrecoverableConnectionFailure(err: Error, callback: ErrorCallback): void {
|
|
958
|
-
|
|
987
|
+
// c8 ignore next
|
|
988
|
+
doDebug && debugLog(err.message);
|
|
959
989
|
// Terminal connect failure: make sure the client is removed from the leak registry. A
|
|
960
990
|
// synchronous throw out of _connectStep2 (e.g. an invalid SecureChannel config) lands here
|
|
961
991
|
// WITHOUT the error-branch unregister ever running, which would otherwise leak the client.
|
|
@@ -966,7 +996,8 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
966
996
|
callback(err);
|
|
967
997
|
}
|
|
968
998
|
private _handleDisconnectionWhileConnecting(err: Error, callback: ErrorCallback) {
|
|
969
|
-
|
|
999
|
+
// c8 ignore next
|
|
1000
|
+
doDebug && debugLog(err.message);
|
|
970
1001
|
// see _handleUnrecoverableConnectionFailure: idempotent unregister guards against a leaked
|
|
971
1002
|
// client when a connect attempt fails before the error-branch unregister runs.
|
|
972
1003
|
OPCUAClientBase.registry.unregister(this);
|
|
@@ -975,7 +1006,8 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
975
1006
|
callback(err);
|
|
976
1007
|
}
|
|
977
1008
|
private _handleSuccessfulConnection(callback: ErrorCallback) {
|
|
978
|
-
|
|
1009
|
+
// c8 ignore next
|
|
1010
|
+
doDebug && debugLog(" Connected successfully to ", this.endpointUrl);
|
|
979
1011
|
this.emit("connected");
|
|
980
1012
|
this._setInternalState("connected");
|
|
981
1013
|
callback();
|
|
@@ -1013,7 +1045,8 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1013
1045
|
|
|
1014
1046
|
this.initializeCM()
|
|
1015
1047
|
.then(() => {
|
|
1016
|
-
|
|
1048
|
+
// c8 ignore next
|
|
1049
|
+
doDebug && debugLog("ClientBaseImpl#connect ", endpointUrl, this.clientName);
|
|
1017
1050
|
if (this._internalState === "disconnecting" || this._internalState === "disconnected") {
|
|
1018
1051
|
return this._handleDisconnectionWhileConnecting(new Error("premature disconnection 1"), callback);
|
|
1019
1052
|
}
|
|
@@ -1022,7 +1055,8 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1022
1055
|
!this.serverCertificate &&
|
|
1023
1056
|
(forceEndpointDiscoveryOnConnect || this.securityMode !== MessageSecurityMode.None)
|
|
1024
1057
|
) {
|
|
1025
|
-
|
|
1058
|
+
// c8 ignore next
|
|
1059
|
+
doDebug && debugLog("Fetching certificates from endpoints");
|
|
1026
1060
|
this.fetchServerCertificate(endpointUrl, (err: Error | null, adjustedEndpointUrl?: string) => {
|
|
1027
1061
|
if (err) {
|
|
1028
1062
|
return this._handleUnrecoverableConnectionFailure(err, callback);
|
|
@@ -1031,10 +1065,13 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1031
1065
|
return this._handleDisconnectionWhileConnecting(new Error("premature disconnection 2"), callback);
|
|
1032
1066
|
}
|
|
1033
1067
|
if (forceEndpointDiscoveryOnConnect) {
|
|
1034
|
-
|
|
1068
|
+
// c8 ignore next
|
|
1069
|
+
doDebug &&
|
|
1070
|
+
debugLog("connecting with adjusted endpoint : ", adjustedEndpointUrl, " was =", endpointUrl);
|
|
1035
1071
|
this._connectStep2(adjustedEndpointUrl || "", callback);
|
|
1036
1072
|
} else {
|
|
1037
|
-
|
|
1073
|
+
// c8 ignore next
|
|
1074
|
+
doDebug && debugLog("connecting with endpoint : ", endpointUrl);
|
|
1038
1075
|
this._connectStep2(endpointUrl, callback);
|
|
1039
1076
|
}
|
|
1040
1077
|
});
|
|
@@ -1049,7 +1086,11 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1049
1086
|
|
|
1050
1087
|
public connectReverse(reverseConnect: ClientReverseConnect, expectation?: ReverseConnectExpectation): Promise<void>;
|
|
1051
1088
|
public connectReverse(reverseConnect: ClientReverseConnect, callback: ErrorCallback): void;
|
|
1052
|
-
public connectReverse(
|
|
1089
|
+
public connectReverse(
|
|
1090
|
+
reverseConnect: ClientReverseConnect,
|
|
1091
|
+
expectation: ReverseConnectExpectation,
|
|
1092
|
+
callback: ErrorCallback
|
|
1093
|
+
): void;
|
|
1053
1094
|
public connectReverse(...args: unknown[]): Promise<void> | void {
|
|
1054
1095
|
const reverseConnect = args[0] as ClientReverseConnect;
|
|
1055
1096
|
const callback = args[args.length - 1] as ErrorCallback;
|
|
@@ -1122,7 +1163,8 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1122
1163
|
|
|
1123
1164
|
this.initializeCM()
|
|
1124
1165
|
.then(() => {
|
|
1125
|
-
|
|
1166
|
+
// c8 ignore next
|
|
1167
|
+
doDebug && debugLog("ClientBaseImpl#connectReverse ", placeholderEndpointUrl, this.clientName);
|
|
1126
1168
|
if (this._internalState === "disconnecting" || this._internalState === "disconnected") {
|
|
1127
1169
|
return this._handleDisconnectionWhileConnecting(new Error("premature disconnection 1"), callback);
|
|
1128
1170
|
}
|
|
@@ -1146,7 +1188,8 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1146
1188
|
this._clockAdjuster = this._clockAdjuster || new ClockAdjustment();
|
|
1147
1189
|
OPCUAClientBase.registry.register(this);
|
|
1148
1190
|
|
|
1149
|
-
|
|
1191
|
+
// c8 ignore next
|
|
1192
|
+
doDebug && debugLog("__connectStep2 ", this._internalState);
|
|
1150
1193
|
|
|
1151
1194
|
this._internal_create_secure_channel(this.connectionStrategy, (err: Error | null) => {
|
|
1152
1195
|
if (!err) {
|
|
@@ -1157,19 +1200,22 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1157
1200
|
this._clockAdjuster.dispose();
|
|
1158
1201
|
this._clockAdjuster = undefined;
|
|
1159
1202
|
}
|
|
1160
|
-
|
|
1203
|
+
// c8 ignore next
|
|
1204
|
+
doDebug && debugLog(chalk.red("SecureChannel creation has failed with error :", err.message));
|
|
1161
1205
|
if (err.message.match(/ECONNABORTED/)) {
|
|
1162
|
-
|
|
1206
|
+
// c8 ignore next
|
|
1207
|
+
doDebug && debugLog(chalk.yellow(`- The client cannot to :${endpointUrl}. Connection has been aborted.`));
|
|
1163
1208
|
err = new Error("The connection has been aborted");
|
|
1164
1209
|
this._handleUnrecoverableConnectionFailure(err, callback);
|
|
1165
1210
|
} else if (err.message.match(/ECONNREF/)) {
|
|
1166
|
-
|
|
1211
|
+
// c8 ignore next
|
|
1212
|
+
doDebug && debugLog(chalk.yellow(`- The client cannot to :${endpointUrl}. Server is not reachable.`));
|
|
1167
1213
|
err = new Error(
|
|
1168
1214
|
`The connection cannot be established with server ${endpointUrl} .\n` +
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1215
|
+
"Please check that the server is up and running or your network configuration.\n" +
|
|
1216
|
+
"Err = (" +
|
|
1217
|
+
err.message +
|
|
1218
|
+
")"
|
|
1173
1219
|
);
|
|
1174
1220
|
this._handleUnrecoverableConnectionFailure(err, callback);
|
|
1175
1221
|
} else if (err.message.match(/disconnecting/)) {
|
|
@@ -1190,7 +1236,8 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1190
1236
|
callback(
|
|
1191
1237
|
new Error(
|
|
1192
1238
|
"performMessageTransaction: No SecureChannel , connection may have been canceled abruptly by server" +
|
|
1193
|
-
|
|
1239
|
+
" while performing " +
|
|
1240
|
+
request.schema.name
|
|
1194
1241
|
)
|
|
1195
1242
|
);
|
|
1196
1243
|
return;
|
|
@@ -1204,9 +1251,9 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1204
1251
|
callback(
|
|
1205
1252
|
new Error(
|
|
1206
1253
|
"performMessageTransaction: Invalid client state = " +
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1254
|
+
this._internalState +
|
|
1255
|
+
" while performing a transaction " +
|
|
1256
|
+
request.schema.name
|
|
1210
1257
|
)
|
|
1211
1258
|
);
|
|
1212
1259
|
return;
|
|
@@ -1405,7 +1452,8 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1405
1452
|
return callback(null);
|
|
1406
1453
|
}
|
|
1407
1454
|
|
|
1408
|
-
|
|
1455
|
+
// c8 ignore next
|
|
1456
|
+
doDebug && debugLog(chalk.bgWhite.green("_closeSession ") + this._secureChannel.channelId);
|
|
1409
1457
|
|
|
1410
1458
|
const request = new CloseSessionRequest({
|
|
1411
1459
|
deleteSubscriptions
|
|
@@ -1444,7 +1492,6 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1444
1492
|
|
|
1445
1493
|
public disconnect(): Promise<void>;
|
|
1446
1494
|
public disconnect(callback: ErrorCallback): void;
|
|
1447
|
-
// eslint-disable-next-line max-statements
|
|
1448
1495
|
public disconnect(...args: unknown[]): undefined | Promise<void> {
|
|
1449
1496
|
const callback = args[0] as ErrorCallback;
|
|
1450
1497
|
assert(typeof callback === "function", "expecting a callback function here");
|
|
@@ -1475,16 +1522,20 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1475
1522
|
callback();
|
|
1476
1523
|
return undefined;
|
|
1477
1524
|
}
|
|
1478
|
-
|
|
1525
|
+
// c8 ignore next
|
|
1526
|
+
doDebug && debugLog("disconnecting client! (will set reconnectionIsCanceled to true");
|
|
1479
1527
|
this._reconnectionIsCanceled = true;
|
|
1480
1528
|
|
|
1481
|
-
|
|
1529
|
+
// c8 ignore next
|
|
1530
|
+
doDebug && debugLog("ClientBaseImpl#disconnect", this.endpointUrl);
|
|
1482
1531
|
|
|
1483
1532
|
if (this.isReconnecting && !this._reconnectionIsCanceled) {
|
|
1484
|
-
|
|
1533
|
+
// c8 ignore next
|
|
1534
|
+
doDebug && debugLog("ClientBaseImpl#disconnect called while reconnection is in progress");
|
|
1485
1535
|
// let's abort the reconnection process
|
|
1486
1536
|
this._cancel_reconnection((err?: Error) => {
|
|
1487
|
-
|
|
1537
|
+
// c8 ignore next
|
|
1538
|
+
doDebug && debugLog("ClientBaseImpl#disconnect reconnection has been canceled", this.applicationName);
|
|
1488
1539
|
assert(!err, " why would this fail ?");
|
|
1489
1540
|
// sessions cannot be cancelled properly and must be discarded.
|
|
1490
1541
|
this.disconnect(callback);
|
|
@@ -1493,7 +1544,8 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1493
1544
|
}
|
|
1494
1545
|
|
|
1495
1546
|
if (this._sessions.length && !this.keepPendingSessionsOnDisconnect) {
|
|
1496
|
-
|
|
1547
|
+
// c8 ignore next
|
|
1548
|
+
doDebug && debugLog("warning : disconnection : closing pending sessions");
|
|
1497
1549
|
// disconnect has been called whereas living session exists
|
|
1498
1550
|
// we need to close them first .... (unless keepPendingSessionsOnDisconnect)
|
|
1499
1551
|
this._close_pending_sessions((/*err*/) => {
|
|
@@ -1502,13 +1554,15 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1502
1554
|
return undefined;
|
|
1503
1555
|
}
|
|
1504
1556
|
|
|
1505
|
-
|
|
1557
|
+
// c8 ignore next
|
|
1558
|
+
doDebug && debugLog("Disconnecting !");
|
|
1506
1559
|
this._setInternalState("disconnecting");
|
|
1507
1560
|
if (this.clientCertificateManager) {
|
|
1508
1561
|
const tmp = this.clientCertificateManager;
|
|
1509
1562
|
// (this as any).clientCertificateManager = null;
|
|
1510
1563
|
tmp.dispose().catch((err: Error) => {
|
|
1511
|
-
|
|
1564
|
+
// c8 ignore next
|
|
1565
|
+
doDebug && debugLog("Error disposing clientCertificateManager", err.message);
|
|
1512
1566
|
});
|
|
1513
1567
|
}
|
|
1514
1568
|
|
|
@@ -1531,7 +1585,8 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1531
1585
|
if (this._secureChannel) {
|
|
1532
1586
|
let tmpChannel: ClientSecureChannelLayer | null = this._secureChannel;
|
|
1533
1587
|
this._secureChannel = null;
|
|
1534
|
-
|
|
1588
|
+
// c8 ignore next
|
|
1589
|
+
doDebug && debugLog("Closing channel");
|
|
1535
1590
|
tmpChannel.close(() => {
|
|
1536
1591
|
this._secureChannel = tmpChannel;
|
|
1537
1592
|
tmpChannel = null;
|
|
@@ -1603,7 +1658,8 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1603
1658
|
|
|
1604
1659
|
private fetchServerCertificate(endpointUrl: string, callback: (err: Error | null, adjustedEndpointUrl?: string) => void): void {
|
|
1605
1660
|
const discoveryUrl = this.discoveryUrl.length > 0 ? this.discoveryUrl : endpointUrl;
|
|
1606
|
-
|
|
1661
|
+
// c8 ignore next
|
|
1662
|
+
doDebug && debugLog("OPCUAClientImpl : getting serverCertificate");
|
|
1607
1663
|
// we have not been given the serverCertificate but this certificate
|
|
1608
1664
|
// is required as the connection is to be secured.
|
|
1609
1665
|
//
|
|
@@ -1656,9 +1712,9 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1656
1712
|
// no matching end point can be found ...
|
|
1657
1713
|
const err1 = new Error(
|
|
1658
1714
|
"cannot find endpoint for securityMode=" +
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1715
|
+
MessageSecurityMode[this.securityMode] +
|
|
1716
|
+
" policy = " +
|
|
1717
|
+
this.securityPolicy
|
|
1662
1718
|
);
|
|
1663
1719
|
callback(err1);
|
|
1664
1720
|
return;
|
|
@@ -1674,7 +1730,10 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1674
1730
|
.catch((err1: Error) => {
|
|
1675
1731
|
warningLog("[NODE-OPCUA-W25] client's server certificate verification has failed ", err1.message);
|
|
1676
1732
|
if ("rootDir" in this.clientCertificateManager) {
|
|
1677
|
-
warningLog(
|
|
1733
|
+
warningLog(
|
|
1734
|
+
" clientCertificateManager.rootDir = ",
|
|
1735
|
+
(this.clientCertificateManager as unknown as { rootDir: string }).rootDir
|
|
1736
|
+
);
|
|
1678
1737
|
}
|
|
1679
1738
|
|
|
1680
1739
|
const chain = split_der(endpoint.serverCertificate);
|
|
@@ -1691,8 +1750,12 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1691
1750
|
const isCA = tbs.extensions?.basicConstraints?.cA === true;
|
|
1692
1751
|
const isSelfSigned = JSON.stringify(tbs.subject) === JSON.stringify(tbs.issuer);
|
|
1693
1752
|
const certType = isCA
|
|
1694
|
-
?
|
|
1695
|
-
|
|
1753
|
+
? isSelfSigned
|
|
1754
|
+
? "Root CA"
|
|
1755
|
+
: "Intermediate CA"
|
|
1756
|
+
: isSelfSigned
|
|
1757
|
+
? "Application (self-signed)"
|
|
1758
|
+
: "Application (CA-signed)";
|
|
1696
1759
|
const ski = tbs.extensions?.subjectKeyIdentifier ?? "-";
|
|
1697
1760
|
const aki = tbs.extensions?.authorityKeyIdentifier?.keyIdentifier ?? "-";
|
|
1698
1761
|
const notBefore = tbs.validity.notBefore.toISOString();
|
|
@@ -1704,13 +1767,15 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1704
1767
|
warningLog(` AKI: ${aki}`);
|
|
1705
1768
|
warningLog(` validity: ${notBefore} → ${notAfter}`);
|
|
1706
1769
|
} catch {
|
|
1707
|
-
warningLog(
|
|
1770
|
+
warningLog(
|
|
1771
|
+
` [${i + 1}/${chain.length}] thumbprint: ${thumbprint} (details unavailable)`
|
|
1772
|
+
);
|
|
1708
1773
|
}
|
|
1709
1774
|
}
|
|
1710
1775
|
if (chain.length > 1) {
|
|
1711
1776
|
warningLog(
|
|
1712
1777
|
" verify also that the issuer certificate is trusted and the issuer's certificate is present in the issuer.cert folder\n" +
|
|
1713
|
-
|
|
1778
|
+
" of the client certificate manager located in ",
|
|
1714
1779
|
"rootDir" in this.clientCertificateManager
|
|
1715
1780
|
? (this.clientCertificateManager as unknown as { rootDir: string }).rootDir
|
|
1716
1781
|
: "<in-memory>"
|
|
@@ -1772,7 +1837,8 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1772
1837
|
resolved = true;
|
|
1773
1838
|
if (err) {
|
|
1774
1839
|
const msg = session.authenticationToken ? session.authenticationToken.toString() : "";
|
|
1775
|
-
|
|
1840
|
+
// c8 ignore next
|
|
1841
|
+
doDebug && debugLog(` failing to close session ${msg}`);
|
|
1776
1842
|
}
|
|
1777
1843
|
resolve();
|
|
1778
1844
|
});
|
|
@@ -1784,11 +1850,13 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1784
1850
|
.then(() => {
|
|
1785
1851
|
// c8 ignore next
|
|
1786
1852
|
if (this._sessions.length > 0) {
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
.
|
|
1791
|
-
|
|
1853
|
+
// c8 ignore next
|
|
1854
|
+
doDebug &&
|
|
1855
|
+
debugLog(
|
|
1856
|
+
this._sessions
|
|
1857
|
+
.map((s: ClientSessionImpl) => (s.authenticationToken ? s.authenticationToken.toString() : ""))
|
|
1858
|
+
.join(" ")
|
|
1859
|
+
);
|
|
1792
1860
|
}
|
|
1793
1861
|
assert(this._sessions.length === 0, " failed to disconnect exiting sessions ");
|
|
1794
1862
|
callback();
|
|
@@ -1837,13 +1905,15 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1837
1905
|
|
|
1838
1906
|
secureChannel.on("lifetime_75", (token: ChannelSecurityToken) => {
|
|
1839
1907
|
// secureChannel requests a new token
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1908
|
+
// c8 ignore next
|
|
1909
|
+
doDebug &&
|
|
1910
|
+
debugLog(
|
|
1911
|
+
"SecureChannel Security Token ",
|
|
1912
|
+
token.tokenId,
|
|
1913
|
+
"live time was =",
|
|
1914
|
+
token.revisedLifetime,
|
|
1915
|
+
" is about to expired , it's time to request a new token"
|
|
1916
|
+
);
|
|
1847
1917
|
// forward message to upper level
|
|
1848
1918
|
this.emit("lifetime_75", token);
|
|
1849
1919
|
});
|
|
@@ -1857,7 +1927,8 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1857
1927
|
if (err) {
|
|
1858
1928
|
this._setInternalState("panic");
|
|
1859
1929
|
}
|
|
1860
|
-
|
|
1930
|
+
// c8 ignore next
|
|
1931
|
+
doDebug && debugLog(chalk.yellow.bold(" ClientBaseImpl emitting close"), err?.message);
|
|
1861
1932
|
this._destroy_secure_channel();
|
|
1862
1933
|
if (!err || !this.reconnectOnFailure) {
|
|
1863
1934
|
if (err) {
|
|
@@ -1874,7 +1945,8 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1874
1945
|
this._internalState !== "reconnecting" &&
|
|
1875
1946
|
this._internalState !== "reconnecting_newchannel_connected"
|
|
1876
1947
|
) {
|
|
1877
|
-
|
|
1948
|
+
// c8 ignore next
|
|
1949
|
+
doDebug && debugLog(" ClientBaseImpl emitting connection_lost");
|
|
1878
1950
|
this._setInternalState("reconnecting");
|
|
1879
1951
|
/**
|
|
1880
1952
|
* @event connection_lost
|
|
@@ -1925,7 +1997,8 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1925
1997
|
private __innerRepairConnection() {
|
|
1926
1998
|
if (this.isUnusable()) return;
|
|
1927
1999
|
|
|
1928
|
-
|
|
2000
|
+
// c8 ignore next
|
|
2001
|
+
doDebug && debugLog("Entering _repairConnection ", this._internalState);
|
|
1929
2002
|
if (this.#insideRepairConnection) {
|
|
1930
2003
|
errorLog(
|
|
1931
2004
|
"_repairConnection already in progress internal state = ",
|
|
@@ -1938,11 +2011,14 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1938
2011
|
}
|
|
1939
2012
|
this.emit("repairConnectionStarted");
|
|
1940
2013
|
this.#insideRepairConnection = true;
|
|
1941
|
-
|
|
2014
|
+
// c8 ignore next
|
|
2015
|
+
doDebug && debugLog("recreating new secure channel ", this._internalState);
|
|
1942
2016
|
this._recreate_secure_channel((err1?: Error) => {
|
|
1943
|
-
|
|
2017
|
+
// c8 ignore next
|
|
2018
|
+
doDebug && debugLog("secureChannel#on(close) => _recreate_secure_channel returns ", err1 ? err1.message : "OK");
|
|
1944
2019
|
if (err1) {
|
|
1945
|
-
|
|
2020
|
+
// c8 ignore next
|
|
2021
|
+
doDebug && debugLog("_recreate_secure_channel has failed: err = ", err1.message);
|
|
1946
2022
|
this.emit("close", err1);
|
|
1947
2023
|
this.#insideRepairConnection = false;
|
|
1948
2024
|
if (this.#shouldRepairAgain) {
|
|
@@ -1962,8 +2038,11 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
1962
2038
|
debugLog("err= ", err2.message);
|
|
1963
2039
|
}
|
|
1964
2040
|
// we still need to retry connecting here !!!
|
|
1965
|
-
|
|
1966
|
-
|
|
2041
|
+
// c8 ignore next
|
|
2042
|
+
if (doDebug) {
|
|
2043
|
+
debugLog("Disconnected following reconnection failure", err2.message);
|
|
2044
|
+
debugLog(`I will retry OPCUA client reconnection in ${OPCUAClientBase.retryDelay / 1000} seconds`);
|
|
2045
|
+
}
|
|
1967
2046
|
this.#insideRepairConnection = false;
|
|
1968
2047
|
this.#shouldRepairAgain = false;
|
|
1969
2048
|
|
|
@@ -2018,7 +2097,6 @@ export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBa
|
|
|
2018
2097
|
}
|
|
2019
2098
|
}
|
|
2020
2099
|
|
|
2021
|
-
// tslint:disable-next-line: max-classes-per-file
|
|
2022
2100
|
class TmpClient extends ClientBaseImpl {
|
|
2023
2101
|
constructor(options: OPCUAClientBaseOptions) {
|
|
2024
2102
|
options.clientName = `${options.clientName || ""}_TmpClient`;
|
|
@@ -2028,7 +2106,8 @@ class TmpClient extends ClientBaseImpl {
|
|
|
2028
2106
|
async connect(endpoint: string): Promise<void>;
|
|
2029
2107
|
connect(endpoint: string, callback: ErrorCallback): void;
|
|
2030
2108
|
connect(endpoint: string, callback?: ErrorCallback): Promise<void> | void {
|
|
2031
|
-
|
|
2109
|
+
// c8 ignore next
|
|
2110
|
+
doDebug && debugLog("connecting to TmpClient");
|
|
2032
2111
|
|
|
2033
2112
|
// c8 ignore next
|
|
2034
2113
|
if (this._internalState !== "disconnected") {
|
|
@@ -2039,7 +2118,7 @@ class TmpClient extends ClientBaseImpl {
|
|
|
2039
2118
|
this._setInternalState("connecting");
|
|
2040
2119
|
this._connectStep2(endpoint, (err?: Error) => {
|
|
2041
2120
|
if (this.isUnusable()) {
|
|
2042
|
-
this._handleUnrecoverableConnectionFailure(new Error("premature disconnection 4"), callback || (() => {
|
|
2121
|
+
this._handleUnrecoverableConnectionFailure(new Error("premature disconnection 4"), callback || (() => {}));
|
|
2043
2122
|
return;
|
|
2044
2123
|
}
|
|
2045
2124
|
callback?.(err);
|
|
@@ -2047,8 +2126,6 @@ class TmpClient extends ClientBaseImpl {
|
|
|
2047
2126
|
}
|
|
2048
2127
|
}
|
|
2049
2128
|
|
|
2050
|
-
// tslint:disable:no-var-requires
|
|
2051
|
-
// tslint:disable:max-line-length
|
|
2052
2129
|
import { withCallback } from "thenify-ex";
|
|
2053
2130
|
|
|
2054
2131
|
ClientBaseImpl.prototype.connect = withCallback(ClientBaseImpl.prototype.connect);
|