• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

node-opcua / node-opcua / 23974043205

04 Apr 2026 07:17AM UTC coverage: 92.589% (+0.01%) from 92.576%
23974043205

push

github

erossignon
chore: fix Mocha.Suite.settimeout misused

18408 of 21832 branches covered (84.32%)

161708 of 174651 relevant lines covered (92.59%)

461089.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.07
/packages/node-opcua-client/source/private/client_base_impl.ts
1
/**
2✔
2
 * @module node-opcua-client-private
2✔
3
 */
2✔
4
// tslint:disable:no-unused-expression
2✔
5
import fs from "node:fs";
2✔
6
import path from "node:path";
2✔
7

2✔
8
import { withLock } from "@ster5/global-mutex";
2✔
9

2✔
10
import chalk from "chalk";
2✔
11
import { assert } from "node-opcua-assert";
2✔
12
import { getDefaultCertificateManager, makeSubject, type OPCUACertificateManager } from "node-opcua-certificate-manager";
2✔
13
import { type IOPCUASecureObjectOptions, makeApplicationUrn, OPCUASecureObject } from "node-opcua-common";
2✔
14
import { type Certificate, makeSHA1Thumbprint, split_der } from "node-opcua-crypto/web";
2✔
15
import { installPeriodicClockAdjustment, periodicClockAdjustment, uninstallPeriodicClockAdjustment } from "node-opcua-date-time";
2✔
16
import { checkDebugFlag, make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
2✔
17
import { getHostname } from "node-opcua-hostname";
2✔
18
import type { IBasicTransportSettings, ResponseCallback } from "node-opcua-pseudo-session";
2✔
19
import {
2✔
20
    ClientSecureChannelLayer,
2✔
21
    type ConnectionStrategy,
2✔
22
    type ConnectionStrategyOptions,
2✔
23
    coerceConnectionStrategy,
2✔
24
    coerceSecurityPolicy,
2✔
25
    type PerformTransactionCallback,
2✔
26
    type Request as Request1,
2✔
27
    type Response as Response1,
2✔
28
    type SecurityPolicy
2✔
29
} from "node-opcua-secure-channel";
2✔
30
import {
2✔
31
    FindServersOnNetworkRequest,
2✔
32
    type FindServersOnNetworkRequestOptions,
2✔
33
    FindServersOnNetworkResponse,
2✔
34
    FindServersRequest,
2✔
35
    FindServersResponse,
2✔
36
    type ServerOnNetwork
2✔
37
} from "node-opcua-service-discovery";
2✔
38
import {
2✔
39
    type ApplicationDescription,
2✔
40
    type EndpointDescription,
2✔
41
    GetEndpointsRequest,
2✔
42
    GetEndpointsResponse
2✔
43
} from "node-opcua-service-endpoints";
2✔
44
import { type ChannelSecurityToken, coerceMessageSecurityMode, MessageSecurityMode } from "node-opcua-service-secure-channel";
2✔
45
import { CloseSessionRequest, type CloseSessionResponse } from "node-opcua-service-session";
2✔
46
import { type ErrorCallback, StatusCodes } from "node-opcua-status-code";
2✔
47
import { checkFileExistsAndIsNotEmpty, matchUri } from "node-opcua-utils";
2✔
48
import {
2✔
49
    type CreateSecureChannelCallbackFunc,
2✔
50
    type FindEndpointCallback,
2✔
51
    type FindEndpointOptions,
2✔
52
    type FindEndpointResult,
2✔
53
    type FindServersOnNetworkRequestLike,
2✔
54
    type FindServersRequestLike,
2✔
55
    type GetEndpointsOptions,
2✔
56
    OPCUAClientBase,
2✔
57
    type OPCUAClientBaseEvents,
2✔
58
    type OPCUAClientBaseOptions,
2✔
59
    type TransportSettings
2✔
60
} from "../client_base";
2✔
61
import type { Request, Response } from "../common";
2✔
62
import type { UserIdentityInfo } from "../user_identity_info";
2✔
63
import { performCertificateSanityCheck } from "../verify";
2✔
64
import type { ClientSessionImpl } from "./client_session_impl";
2✔
65
import type { IClientBase } from "./i_private_client";
2✔
66

2✔
67
const debugLog = make_debugLog(__filename);
2✔
68
const doDebug = checkDebugFlag(__filename);
2✔
69
const errorLog = make_errorLog(__filename);
2✔
70
const warningLog = make_warningLog(__filename);
2✔
71

2✔
72
function makeCertificateThumbPrint(certificate: Certificate | Certificate[] | null | undefined): Buffer | null {
19✔
73
    if (!certificate) return null;
19✔
74
    return makeSHA1Thumbprint(Array.isArray(certificate) ? certificate[0] : certificate);
19!
75
}
19✔
76

2✔
77
const traceInternalState = false;
2✔
78

2✔
79
const defaultConnectionStrategy: ConnectionStrategyOptions = {
2✔
80
    initialDelay: 1000,
2✔
81
    maxDelay: 20 * 1000, // 20 seconds
2✔
82
    maxRetry: -1, // infinite
2✔
83
    randomisationFactor: 0.1
2✔
84
};
2✔
85

2✔
86
function __findEndpoint(this: ClientBaseImpl, endpointUrl: string, params: FindEndpointOptions, _callback: FindEndpointCallback) {
154✔
87
    if (this.isUnusable()) {
154!
88
        return _callback(new Error("Client is not usable"));
×
89
    }
×
90
    const masterClient = this as ClientBaseImpl;
154✔
91
    doDebug && debugLog("findEndpoint : endpointUrl = ", endpointUrl);
154!
92
    doDebug && debugLog(" params ", params);
154!
93
    assert(!masterClient._tmpClient);
154✔
94

154✔
95
    const callback = (err: Error | null, result?: FindEndpointResult) => {
154✔
96
        masterClient._tmpClient = undefined;
154✔
97
        _callback(err, result);
154✔
98
    };
154✔
99

154✔
100
    const securityMode = params.securityMode;
154✔
101
    const securityPolicy = params.securityPolicy;
154✔
102
    const _connectionStrategy = params.connectionStrategy;
154✔
103
    const options: OPCUAClientBaseOptions = {
154✔
104
        applicationName: params.applicationName,
154✔
105
        applicationUri: params.applicationUri,
154✔
106
        certificateFile: params.certificateFile,
154✔
107
        clientCertificateManager: params.clientCertificateManager,
154✔
108

154✔
109
        clientName: "EndpointFetcher",
154✔
110

154✔
111
        // use same connectionStrategy as parent
154✔
112
        connectionStrategy: params.connectionStrategy,
154✔
113
        // connectionStrategy: {
154✔
114
        //     maxRetry: 0 /* no- retry */,
154✔
115
        //     maxDelay: 2000
154✔
116
        // },
154✔
117
        privateKeyFile: params.privateKeyFile
154✔
118
    };
154✔
119

154✔
120
    const client = new TmpClient(options);
154✔
121
    masterClient._tmpClient = client;
154✔
122

154✔
123
    let selectedEndpoint: EndpointDescription | undefined;
154✔
124
    const allEndpoints: EndpointDescription[] = [];
154✔
125
    const step1_connect = (): Promise<void> => {
154✔
126
        return new Promise<void>((resolve, reject) => {
154✔
127
            // rebind backoff handler
154✔
128
            masterClient.listeners("backoff").forEach((handler) => {
154✔
129
                client.on("backoff", handler as unknown as (retryCount: number, delay: number) => void);
142✔
130
            });
154✔
131

154✔
132
            // c8 ignore next
154✔
133
            if (doDebug) {
154!
134
                client.on("backoff", (retryCount: number, delay: number) => {
×
135
                    debugLog(
×
136
                        "finding Endpoint => reconnecting ",
×
137
                        " retry count",
×
138
                        retryCount,
×
139
                        " next attempt in ",
×
140
                        delay / 1000,
×
141
                        "seconds"
×
142
                    );
×
143
                });
×
144
            }
×
145

154✔
146
            client.connect(endpointUrl, (err?: Error) => {
154✔
147
                if (err) {
154✔
148
                    err.message =
2✔
149
                        "Fail to connect to server at " +
2✔
150
                        endpointUrl +
2✔
151
                        " to collect server's certificate (in findEndpoint) \n" +
2✔
152
                        " (err =" +
2✔
153
                        err.message +
2✔
154
                        ")";
2✔
155
                    warningLog(err.message);
2✔
156
                    return reject(err);
2✔
157
                }
2✔
158
                resolve();
152✔
159
            });
154✔
160
        });
154✔
161
    };
154✔
162

154✔
163
    const step2_getEndpoints = (): Promise<void> => {
154✔
164
        return new Promise<void>((resolve, reject) => {
152✔
165
            client.getEndpoints((err: Error | null, endpoints?: EndpointDescription[]) => {
152✔
166
                if (err) {
152!
167
                    err.message = `error in getEndpoints \n${err.message}`;
✔
168
                    return reject(err);
1✔
169
                }
1✔
170
                // c8 ignore next
153✔
171
                if (!endpoints) {
153✔
172
                    return reject(new Error("Internal Error"));
1✔
173
                }
1✔
174

153✔
175
                for (const endpoint of endpoints) {
153✔
176
                    if (endpoint.securityMode === securityMode && endpoint.securityPolicyUri === securityPolicy) {
1,456✔
177
                        if (selectedEndpoint) {
153✔
178
                            errorLog(
1✔
179
                                "Warning more than one endpoint matching !",
1✔
180
                                endpoint.endpointUrl,
1✔
181
                                selectedEndpoint.endpointUrl
1✔
182
                            );
1✔
183
                        }
1✔
184
                        selectedEndpoint = endpoint; // found it
153✔
185
                    }
153✔
186
                }
1,456✔
187
                resolve();
153✔
188
            });
153✔
189
        });
153✔
190
    };
155✔
191

155✔
192
    const step3_disconnect = (): Promise<void> => {
155✔
193
        return new Promise<void>((resolve) => {
153✔
194
            client.disconnect(() => resolve());
153✔
195
        });
153✔
196
    };
155✔
197

155✔
198
    step1_connect()
155✔
199
        .then(() => step2_getEndpoints())
155✔
200
        .then(() => step3_disconnect())
155✔
201
        .then(() => {
155✔
202
            if (!selectedEndpoint) {
153✔
203
                return callback(
1✔
204
                    new Error(
1✔
205
                        "Cannot find an Endpoint matching " +
1✔
206
                        " security mode: " +
1✔
207
                        securityMode.toString() +
1✔
208
                        " policy: " +
1✔
209
                        securityPolicy.toString()
1✔
210
                    )
1✔
211
                );
1✔
212
            }
1✔
213

153✔
214
            // c8 ignore next
153✔
215
            if (doDebug) {
153✔
216
                debugLog(chalk.bgWhite.red("xxxxxxxxxxxxxxxxxxxxx => selected EndPoint = "), selectedEndpoint.toString());
1✔
217
            }
1✔
218

153✔
219
            callback(null, { endpoints: allEndpoints, selectedEndpoint });
153✔
220
        })
155✔
221
        .catch((err) => {
155✔
222
            client.disconnect(() => {
3✔
223
                callback(err);
3✔
224
            });
3✔
225
        });
155✔
226
}
155✔
227

2✔
228
/**
2✔
229
 * check if certificate is trusted or untrusted
2✔
230
 */
2✔
231
async function _verify_serverCertificate(certificateManager: OPCUACertificateManager, serverCertificate: Certificate) {
153✔
232
    const status = await certificateManager.checkCertificate(serverCertificate);
153✔
233
    if (status !== StatusCodes.Good) {
153✔
234
        // c8 ignore next
2✔
235
        if (doDebug) {
2✔
236
            // do it again for debug purposes
1✔
237
            const status1 = await certificateManager.verifyCertificate(serverCertificate);
1✔
238
            debugLog(status1);
1✔
239
        }
1✔
240
        warningLog("serverCertificate = ", makeCertificateThumbPrint(serverCertificate)?.toString("hex") || "none");
2✔
241
        warningLog("serverCertificate = ", serverCertificate.toString("base64"));
2✔
242
        throw new Error(`server Certificate verification failed with err ${status?.toString()}`);
2✔
243
    }
2✔
244
}
153✔
245

2✔
246
const forceEndpointDiscoveryOnConnect = !!parseInt(process.env.NODEOPCUA_CLIENT_FORCE_ENDPOINT_DISCOVERY || "0", 10);
2✔
247
debugLog("forceEndpointDiscoveryOnConnect = ", forceEndpointDiscoveryOnConnect);
2✔
248

2✔
249
class ClockAdjustment {
2✔
250
    constructor() {
2✔
251
        debugLog("installPeriodicClockAdjustment ", periodicClockAdjustment.timerInstallationCount);
1,505✔
252
        installPeriodicClockAdjustment();
1,505✔
253
    }
1,505✔
254
    dispose() {
2✔
255
        uninstallPeriodicClockAdjustment();
1,501✔
256
        debugLog("uninstallPeriodicClockAdjustment ", periodicClockAdjustment.timerInstallationCount);
1,501✔
257
    }
1,501✔
258
}
2✔
259

2✔
260
type InternalClientState =
2✔
261
    | "uninitialized"
2✔
262
    | "disconnected"
2✔
263
    | "connecting"
2✔
264
    | "connected"
2✔
265
    | "panic"
2✔
266
    | "reconnecting"
2✔
267
    | "reconnecting_newchannel_connected"
2✔
268
    | "disconnecting";
2✔
269

2✔
270
/*
2✔
271
 *    "disconnected"  ---[connect]----------------------> "connecting"
2✔
272
 *
2✔
273
 *    "connecting"    ---[(connection successful)]------> "connected"
2✔
274
 *
2✔
275
 *    "connecting"    ---[(connection failure)]---------> "disconnected"
2✔
276
 *
2✔
277
 *    "connecting"    ---[disconnect]-------------------> "disconnecting" --> "disconnected"
2✔
278
 *
2✔
279
 *    "connecting"    ---[lost of connection]-----------> "reconnecting" ->[reconnection]
2✔
280
 *
2✔
281
 *    "reconnecting"  ---[reconnection successful]------> "reconnecting_newchannel_connected"
2✔
282
 *
2✔
283
 *    "reconnecting_newchannel_connected" --(session failure) -->"reconnecting"
2✔
284
 *
2✔
285
 *    "reconnecting"  ---[reconnection failure]---------> [reconnection] ---> "reconnecting"
2✔
286
 *
2✔
287
 *    "reconnecting"  ---[disconnect]-------------------> "disconnecting" --> "disconnected"
2✔
288
 */
2✔
289

2✔
290
let g_ClientCounter = 0;
2✔
291

2✔
292
/**
2✔
293
 * @internal
2✔
294
 */
2✔
295
// tslint:disable-next-line: max-classes-per-file
2✔
296
export class ClientBaseImpl<Events extends OPCUAClientBaseEvents = OPCUAClientBaseEvents>
2!
297
    extends OPCUASecureObject<Events>
1✔
298
    implements OPCUAClientBase<Events>, IClientBase {
1✔
299
    /**
1✔
300
     * total number of requests that been canceled due to timeout
1✔
301
     */
1✔
302
    public get timedOutRequestCount(): number {
1✔
303
        return this._timedOutRequestCount + (this._secureChannel ? this._secureChannel.timedOutRequestCount : 0);
3!
304
    }
3✔
305

1✔
306
    /**
1✔
307
     * total number of transactions performed by the client
1✔
308
   x  */
1✔
309
    public get transactionsPerformed(): number {
1✔
310
        return this._transactionsPerformed + (this._secureChannel ? this._secureChannel.transactionsPerformed : 0);
16✔
311
    }
16✔
312

1✔
313
    /**
1✔
314
     * is true when the client has already requested the server end points.
1✔
315
     */
1✔
316
    get knowsServerEndpoint(): boolean {
1✔
317
        return this._serverEndpoints && this._serverEndpoints.length > 0;
2,734✔
318
    }
2,734✔
319

1✔
320
    /**
1✔
321
     * true if the client is trying to reconnect to the server after a connection break.
1✔
322
     */
1✔
323
    get isReconnecting(): boolean {
1✔
324
        return (
2,085✔
325
            !!this._secureChannel?.isConnecting ||
2,085✔
326
            this._internalState === "reconnecting_newchannel_connected" ||
2,085✔
327
            this._internalState === "reconnecting"
1,890✔
328
        );
2,085✔
329
    }
2,085✔
330

1✔
331
    /**
1✔
332
     * true if the connection strategy is set to automatically try to reconnect in case of failure
1✔
333
     */
1✔
334
    get reconnectOnFailure(): boolean {
1✔
335
        return this.connectionStrategy.maxRetry > 0 || this.connectionStrategy.maxRetry === -1;
96✔
336
    }
96✔
337

1✔
338
    /**
1✔
339
     * total number of bytes read by the client
1✔
340
     */
1✔
341
    get bytesRead(): number {
1✔
342
        return this._byteRead + (this._secureChannel ? this._secureChannel.bytesRead : 0);
6!
343
    }
6✔
344

1✔
345
    /**
1✔
346
     * total number of bytes written by the client
1✔
347
     */
1✔
348
    public get bytesWritten(): number {
1✔
349
        return this._byteWritten + (this._secureChannel ? this._secureChannel.bytesWritten : 0);
5!
350
    }
5✔
351

1✔
352
    public securityMode: MessageSecurityMode;
1✔
353
    public securityPolicy: SecurityPolicy;
1✔
354
    public serverCertificate?: Certificate | Certificate[];
1✔
355
    public clientName: string;
1✔
356
    public protocolVersion: 0;
1✔
357
    public defaultSecureTokenLifetime: number;
1✔
358
    public tokenRenewalInterval: number;
1✔
359
    public connectionStrategy: ConnectionStrategy;
1✔
360
    public keepPendingSessionsOnDisconnect: boolean;
1✔
361
    public endpointUrl: string;
1✔
362
    public discoveryUrl: string;
1✔
363
    public readonly applicationName: string;
1✔
364
    private _applicationUri: string;
1✔
365
    public defaultTransactionTimeout?: number;
1✔
366

1✔
367
    /**
1✔
368
     * true if session shall periodically probe the server to keep the session alive and prevent timeout
1✔
369
     */
1✔
370
    public keepSessionAlive: boolean;
1✔
371
    public readonly keepAliveInterval?: number;
1✔
372

1✔
373
    public _sessions: ClientSessionImpl[];
1✔
374
    protected _serverEndpoints: EndpointDescription[];
1✔
375
    public _secureChannel: ClientSecureChannelLayer | null;
1✔
376

1✔
377
    // statistics...
1✔
378
    private _byteRead: number;
1✔
379
    private _byteWritten: number;
1✔
380

1✔
381
    private _timedOutRequestCount: number;
1✔
382

1✔
383
    private _transactionsPerformed: number;
1✔
384
    private _reconnectionIsCanceled: boolean;
1✔
385
    private _clockAdjuster?: ClockAdjustment;
1✔
386
    protected _tmpClient?: OPCUAClientBase;
1✔
387
    private _instanceNumber: number;
1✔
388
    private _transportSettings: TransportSettings;
1✔
389
    private _transportTimeout?: number;
1✔
390

1✔
391
    public clientCertificateManager: OPCUACertificateManager;
1✔
392

1✔
393
    public isUnusable() {
1✔
394
        return (
2,624✔
395
            this._internalState === "disconnected" ||
2,624✔
396
            this._internalState === "disconnecting" ||
2,624✔
397
            this._internalState === "panic" ||
2,624✔
398
            this._internalState === "uninitialized"
2,607✔
399
        );
2,624✔
400
    }
2,624✔
401

1✔
402
    protected _setInternalState(internalState: InternalClientState): void {
1✔
403
        const previousState = this._internalState;
10,730✔
404
        if (doDebug || traceInternalState) {
10,730!
405
            (traceInternalState ? warningLog : debugLog)(
×
406
                chalk.cyan(`  Client ${this._instanceNumber} ${this.clientName} : _internalState from    `),
×
407
                chalk.yellow(previousState),
×
408
                "to",
×
409
                chalk.yellow(internalState)
×
410
            );
×
411
        }
×
412
        if (this._internalState === "disconnecting" || this._internalState === "disconnected") {
10,730✔
413
            if (internalState === "reconnecting") {
2,981!
414
                errorLog("Internal error, cannot switch to reconnecting when already disconnecting");
×
415
            } // when disconnecting, we cannot accept any other state
2,981✔
416
        }
2,981✔
417

10,730✔
418
        this._internalState = internalState;
10,730✔
419
    }
10,730✔
420
    public emit<K>(eventName: K, ...others: unknown[]): boolean {
1✔
421
        // c8 ignore next
194,611✔
422
        if (doDebug) {
194,611!
423
            debugLog(
×
424
                chalk.cyan(`  Client ${this._instanceNumber} ${this.clientName} emitting `),
×
425
                chalk.magentaBright(eventName as string)
×
426
            );
×
427
        }
×
428
        // @ts-expect-error
194,611✔
429
        return super.emit(eventName, ...others);
194,611✔
430
    }
194,611✔
431
    constructor(options?: OPCUAClientBaseOptions) {
1✔
432
        options = options || {};
1,670!
433
        if (!options.clientCertificateManager) {
1,670✔
434
            options.clientCertificateManager = getDefaultCertificateManager("PKI");
633✔
435
        }
633✔
436
        options.privateKeyFile = options.privateKeyFile || options.clientCertificateManager.privateKey;
1,670✔
437
        options.certificateFile =
1,670✔
438
            options.certificateFile || path.join(options.clientCertificateManager.rootDir, "own/certs/client_certificate.pem");
1,670✔
439

1,670✔
440
        super(options as IOPCUASecureObjectOptions);
1,670✔
441

1,670✔
442
        this._setInternalState("uninitialized");
1,670✔
443

1,670✔
444
        this._instanceNumber = g_ClientCounter++;
1,670✔
445

1,670✔
446
        this.applicationName = options.applicationName || "NodeOPCUA-Client";
1,670✔
447
        assert(!this.applicationName.match(/^locale=/), "applicationName badly converted from LocalizedText");
1,670✔
448
        assert(!this.applicationName.match(/urn:/), "applicationName should not be a URI");
1,670✔
449

1,670✔
450
        // we need to delay _applicationUri initialization
1,670✔
451
        this._applicationUri = options.applicationUri || this._getBuiltApplicationUri();
1,670✔
452

1,670✔
453
        this.clientCertificateManager = options.clientCertificateManager;
1,670✔
454
        this.clientCertificateManager.referenceCounter++;
1,670✔
455

1,670✔
456
        this._secureChannel = null;
1,670✔
457

1,670✔
458
        this._reconnectionIsCanceled = false;
1,670✔
459

1,670✔
460
        this.endpointUrl = "";
1,670✔
461

1,670✔
462
        this.clientName = options.clientName || "ClientSession";
1,670✔
463

1,670✔
464
        // must be ZERO with Spec 1.0.2
1,670✔
465
        this.protocolVersion = 0;
1,670✔
466

1,670✔
467
        this._sessions = [];
1,670✔
468

1,670✔
469
        this._serverEndpoints = [];
1,670✔
470

1,670✔
471
        this.defaultSecureTokenLifetime = options.defaultSecureTokenLifetime || 600000;
1,670✔
472

1,670✔
473
        this.defaultTransactionTimeout = options.defaultTransactionTimeout;
1,670✔
474

1,670✔
475
        this.tokenRenewalInterval = options.tokenRenewalInterval || 0;
1,670✔
476
        assert(Number.isFinite(this.tokenRenewalInterval) && this.tokenRenewalInterval >= 0);
1,670✔
477
        this.securityMode = coerceMessageSecurityMode(options.securityMode);
1,670✔
478
        this.securityPolicy = coerceSecurityPolicy(options.securityPolicy);
1,670✔
479

1,670✔
480
        this.serverCertificate = options.serverCertificate;
1,670✔
481

1,670✔
482
        this.keepSessionAlive = typeof options.keepSessionAlive === "boolean" ? options.keepSessionAlive : false;
1,670✔
483
        this.keepAliveInterval = options.keepAliveInterval;
1,670✔
484

1,670✔
485
        // statistics...
1,670✔
486
        this._byteRead = 0;
1,670✔
487
        this._byteWritten = 0;
1,670✔
488
        this._transactionsPerformed = 0;
1,670✔
489
        this._timedOutRequestCount = 0;
1,670✔
490

1,670✔
491
        this.connectionStrategy = coerceConnectionStrategy(options.connectionStrategy || defaultConnectionStrategy);
1,670✔
492

1,670✔
493
        /***
1,670✔
494
         * @property keepPendingSessionsOnDisconnect²
1,670✔
495
         * @type {boolean}
1,670✔
496
         */
1,670✔
497
        this.keepPendingSessionsOnDisconnect = options.keepPendingSessionsOnDisconnect || false;
1,670✔
498

1,670✔
499
        this.discoveryUrl = options.discoveryUrl || "";
1,670✔
500

1,670✔
501
        this._setInternalState("disconnected");
1,670✔
502

1,670✔
503
        this._transportSettings = options.transportSettings || {};
1,670✔
504
        this._transportTimeout = options.transportTimeout;
1,670✔
505
    }
1,670✔
506

1✔
507
    private _cancel_reconnection(callback: ErrorCallback) {
1✔
508
        // _cancel_reconnection is invoked during disconnection
×
509
        // when we detect that a reconnection is in progress...
×
510

×
511
        // c8 ignore next
×
512
        if (!this.isReconnecting) {
×
513
            warningLog("internal error: _cancel_reconnection should only be used when reconnecting is in progress");
×
514
        }
×
515

×
516
        debugLog("canceling reconnection : ", this.clientName);
×
517

×
518
        this._reconnectionIsCanceled = true;
×
519

×
520
        // c8 ignore next
×
521
        if (!this._secureChannel) {
×
522
            debugLog("_cancel_reconnection:  Nothing to do for !", this.clientName, " because secure channel doesn't exist");
×
523
            return callback(); // nothing to do
×
524
        }
×
525

×
526
        this._secureChannel.abortConnection((/*err?: Error*/) => {
×
527
            this._secureChannel = null;
×
528
            callback();
×
529
        });
×
530
    }
×
531

1✔
532
    public _recreate_secure_channel(callback: ErrorCallback): void {
1✔
533
        debugLog("_recreate_secure_channel... while internalState is", this._internalState);
37✔
534

37✔
535
        if (!this.knowsServerEndpoint) {
37!
536
            debugLog("Cannot reconnect , server endpoint is unknown");
×
537
            callback(new Error("Cannot reconnect, server endpoint is unknown - this.knowsServerEndpoint = false"));
×
538
            return;
×
539
        }
×
540
        assert(this.knowsServerEndpoint);
37✔
541

37✔
542
        this._setInternalState("reconnecting");
37✔
543
        this.emit("start_reconnection"); // send after callback
37✔
544

37✔
545
        const infiniteConnectionRetry: ConnectionStrategyOptions = {
37✔
546
            initialDelay: this.connectionStrategy.initialDelay,
37✔
547
            maxDelay: this.connectionStrategy.maxDelay,
37✔
548
            maxRetry: -1
37✔
549
        };
37✔
550

37✔
551
        const _when_internal_error = (err: Error, callback: ErrorCallback) => {
37✔
552
            errorLog("INTERNAL ERROR", err.message);
×
553
            callback(err);
×
554
        };
37✔
555

37✔
556
        const _when_reconnectionIsCanceled = (callback: ErrorCallback) => {
37✔
557
            doDebug && debugLog("attempt to recreate a new secure channel : suspended because reconnection is canceled !");
2!
558
            this.emit("reconnection_canceled");
2✔
559
            return callback(new Error(`Reconnection has been canceled - ${this.clientName}`));
2✔
560
        };
37✔
561

37✔
562
        const _failAndRetry = (err: Error, message: string, callback: ErrorCallback) => {
37✔
563
            debugLog("failAndRetry; ", message);
2✔
564
            if (this._reconnectionIsCanceled) {
2✔
565
                return _when_reconnectionIsCanceled(callback);
2✔
566
            }
2✔
567
            this._destroy_secure_channel();
×
568
            warningLog("client = ", this.clientName, message, err.message);
×
569
            // else
×
570
            // let retry a little bit later
×
571
            this.emit("reconnection_attempt_has_failed", err, message); // send after callback
×
572
            setImmediate(_attempt_to_recreate_secure_channel, callback);
×
573
        };
37✔
574

37✔
575
        const _when_connected = (callback: ErrorCallback) => {
37✔
576
            this.emit("after_reconnection", null); // send after callback
35✔
577
            assert(this._secureChannel, "expecting a secureChannel here ");
35✔
578
            // a new channel has be created and a new connection is established
35✔
579
            debugLog(chalk.bgWhite.red("ClientBaseImpl:  RECONNECTED                !!!"));
35✔
580
            this._setInternalState("reconnecting_newchannel_connected");
35✔
581
            return callback();
35✔
582
        };
37✔
583

37✔
584
        const _attempt_to_recreate_secure_channel = (callback: ErrorCallback) => {
37✔
585
            debugLog("attempt to recreate a new secure channel");
37✔
586
            if (this._reconnectionIsCanceled) {
37!
587
                return _when_reconnectionIsCanceled(callback);
×
588
            }
×
589
            if (this._secureChannel) {
37!
590
                doDebug && debugLog("attempt to recreate a new secure channel, while channel already exists");
×
591
                // are we reentrant ?
×
592
                const err = new Error("_internal_create_secure_channel failed, this._secureChannel is supposed to be null");
×
593
                return _when_internal_error(err, callback);
×
594
            }
×
595
            assert(!this._secureChannel, "_attempt_to_recreate_secure_channel,  expecting this._secureChannel not to exist");
37✔
596

37✔
597
            this._internal_create_secure_channel(infiniteConnectionRetry, (err?: Error | null) => {
37✔
598
                if (err) {
37✔
599
                    // c8 ignore next
6✔
600
                    if (this._secureChannel) {
6!
601
                        const err = new Error("_internal_create_secure_channel failed, expecting this._secureChannel not to exist");
×
602
                        return _when_internal_error(err, callback);
×
603
                    }
×
604

6✔
605
                    if (err.message.match(/ECONNREFUSED|ECONNABORTED|ETIMEDOUT/)) {
6!
606
                        return _failAndRetry(
×
607
                            err,
×
608
                            `create secure channel failed with ECONNREFUSED|ECONNABORTED|ETIMEDOUT\n${err.message}`,
×
609
                            callback
×
610
                        );
×
611
                    }
×
612

6✔
613
                    if (err.message.match("Backoff aborted.")) {
6!
614
                        _failAndRetry(err, "cannot create secure channel (backoff aborted)", callback);
×
615
                        return;
×
616
                    }
×
617

6✔
618
                    if (
6✔
619
                        err?.message.match("BadCertificateInvalid") ||
6✔
620
                        err?.message.match(/socket has been disconnected by third party/)
6✔
621
                    ) {
6✔
622
                        // it is possible also that hte server has shutdown innapropriately the connection
4✔
623
                        warningLog(
4✔
624
                            "the server certificate has changed,  we need to retrieve server certificate again: ",
4✔
625
                            err.message
4✔
626
                        );
4✔
627
                        const oldServerCertificate = this.serverCertificate;
4✔
628
                        warningLog(
4✔
629
                            "old server certificate ",
4✔
630
                            makeCertificateThumbPrint(oldServerCertificate)?.toString("hex") || "undefined"
4!
631
                        );
4✔
632
                        // the server may have shut down the channel because its certificate
4✔
633
                        // has changed ....
4✔
634
                        // let request the server certificate again ....
4✔
635
                        return this.fetchServerCertificate(this.endpointUrl, (err1?: Error | null) => {
4✔
636
                            if (err1) {
4!
637
                                return _failAndRetry(err1, "Failing to fetch new server certificate", callback);
×
638
                            }
×
639
                            const newServerCertificate = this.serverCertificate;
4✔
640
                            warningLog(
4✔
641
                                "new server certificate ",
4✔
642
                                makeCertificateThumbPrint(newServerCertificate)?.toString("hex") || "none"
4!
643
                            );
4✔
644

4✔
645
                            const sha1Old = makeCertificateThumbPrint(oldServerCertificate)?.toString("hex") || null;
4!
646
                            const sha1New = makeCertificateThumbPrint(newServerCertificate)?.toString("hex") || null;
4!
647
                            if (sha1Old === sha1New) {
4!
648
                                warningLog("server certificate has not changed, but was expected to have changed");
×
649
                                return _failAndRetry(
×
650
                                    new Error("Server Certificate not changed"),
×
651
                                    "Failing to fetch new server certificate",
×
652
                                    callback
×
653
                                );
×
654
                            }
×
655
                            this._internal_create_secure_channel(infiniteConnectionRetry, (err3?: Error | null) => {
4✔
656
                                if (err3) {
4!
657
                                    return _failAndRetry(err3, "trying to create new channel with new certificate", callback);
×
658
                                }
×
659
                                return _when_connected(callback);
4✔
660
                            });
4✔
661
                        });
4✔
662
                    } else {
6✔
663
                        return _failAndRetry(err, "cannot create secure channel", callback);
2✔
664
                    }
2✔
665
                } else {
37✔
666
                    return _when_connected(callback);
31✔
667
                }
31✔
668
            });
37✔
669
        };
37✔
670

37✔
671
        // create a secure channel
37✔
672
        // a new secure channel must be established
37✔
673
        _attempt_to_recreate_secure_channel(callback);
37✔
674
    }
37✔
675

1✔
676
    public _internal_create_secure_channel(
1✔
677
        connectionStrategy: ConnectionStrategyOptions,
1,546✔
678
        callback: CreateSecureChannelCallbackFunc
1,546✔
679
    ): void {
1,546✔
680
        assert(this._secureChannel === null);
1,546✔
681
        assert(typeof this.endpointUrl === "string");
1,546✔
682

1,546✔
683
        debugLog(
1,546✔
684
            "_internal_create_secure_channel creating new ClientSecureChannelLayer _internalState =",
1,546✔
685
            this._internalState,
1,546✔
686
            this.clientName
1,546✔
687
        );
1,546✔
688
        const secureChannel = new ClientSecureChannelLayer({
1,546✔
689
            connectionStrategy,
1,546✔
690
            defaultSecureTokenLifetime: this.defaultSecureTokenLifetime,
1,546✔
691
            parent: this,
1,546✔
692
            securityMode: this.securityMode,
1,546✔
693
            securityPolicy: this.securityPolicy,
1,546✔
694
            serverCertificate: this.serverCertificate,
1,546✔
695
            tokenRenewalInterval: this.tokenRenewalInterval,
1,546✔
696
            transportSettings: this._transportSettings,
1,546✔
697
            transportTimeout: this._transportTimeout,
1,546✔
698
            defaultTransactionTimeout: this.defaultTransactionTimeout
1,546✔
699
        });
1,546✔
700
        secureChannel.on("backoff", (count: number, delay: number) => {
1,546✔
701
            this.emit("backoff", count, delay);
435✔
702
        });
1,546✔
703

1,546✔
704
        secureChannel.on("abort", () => {
1,546✔
705
            this.emit("abort");
13✔
706
        });
1,546✔
707

1,546✔
708
        secureChannel.protocolVersion = this.protocolVersion;
1,546✔
709

1,546✔
710
        this._secureChannel = secureChannel;
1,546✔
711

1,546✔
712
        this.emit("secure_channel_created", secureChannel);
1,546✔
713

1,546✔
714
        const step2_openSecureChannel = (): Promise<void> => {
1,546✔
715
            return new Promise<void>((resolve, reject) => {
1,546✔
716
                debugLog("_internal_create_secure_channel before secureChannel.create");
1,546✔
717

1,546✔
718
                secureChannel.create(this.endpointUrl, (err?: Error) => {
1,546✔
719
                    debugLog("_internal_create_secure_channel after secureChannel.create");
1,546✔
720
                    if (!this._secureChannel) {
1,546✔
721
                        debugLog("_secureChannel has been closed during the transaction !");
13✔
722
                        return reject(new Error("Secure Channel Closed"));
13✔
723
                    }
13✔
724
                    if (err) {
1,546✔
725
                        return reject(err);
76✔
726
                    }
76✔
727
                    this._install_secure_channel_event_handlers(secureChannel);
1,457✔
728
                    resolve();
1,457✔
729
                });
1,546✔
730
            });
1,546✔
731
        };
1,546✔
732

1,546✔
733
        const step3_getEndpoints = (): Promise<void> => {
1,546✔
734
            return new Promise<void>((resolve, reject) => {
1,457✔
735
                assert(this._secureChannel !== null);
1,457✔
736
                if (!this.knowsServerEndpoint) {
1,457✔
737
                    this._setInternalState("connecting");
1,280✔
738

1,280✔
739
                    this.getEndpoints((err: Error | null /*, endpoints?: EndpointDescription[]*/) => {
1,280✔
740
                        if (!this._secureChannel) {
1,280✔
741
                            debugLog("_secureChannel has been closed during the transaction ! (while getEndpoints)");
6✔
742
                            return reject(new Error("Secure Channel Closed"));
6✔
743
                        }
6✔
744
                        if (err) {
1,280✔
745
                            return reject(err);
1✔
746
                        }
1✔
747
                        resolve();
1,273✔
748
                    });
1,280✔
749
                } else {
1,457✔
750
                    // end points are already known
177✔
751
                    resolve();
177✔
752
                }
177✔
753
            });
1,457✔
754
        };
1,546✔
755

1,546✔
756
        step2_openSecureChannel()
1,546✔
757
            .then(() => step3_getEndpoints())
1,546✔
758
            .then(() => {
1,546✔
759
                assert(this._secureChannel !== null);
1,450✔
760
                callback(null, secureChannel);
1,450✔
761
            })
1,546✔
762
            .catch((err) => {
1,546✔
763
                doDebug && debugLog(this.clientName, " : Inner create secure channel has failed", err.message);
96!
764
                if (this._secureChannel) {
96✔
765
                    this._secureChannel.abortConnection(() => {
77✔
766
                        this._destroy_secure_channel();
77✔
767
                        callback(err);
77✔
768
                    });
77✔
769
                } else {
96✔
770
                    callback(err);
19✔
771
                }
19✔
772
            });
1,546✔
773
    }
1,546✔
774

1✔
775
    static async createCertificate(
1✔
776
        clientCertificateManager: OPCUACertificateManager,
9✔
777
        certificateFile: string,
9✔
778
        applicationName: string,
9✔
779
        applicationUri: string
9✔
780
    ): Promise<void> {
9✔
781
        if (!fs.existsSync(certificateFile)) {
9✔
782
            const hostname = getHostname();
9✔
783
            // this.serverInfo.applicationUri!;
9✔
784
            await clientCertificateManager.createSelfSignedCertificate({
9✔
785
                applicationUri,
9✔
786
                dns: [hostname],
9✔
787
                // ip: await getIpAddresses(),
9✔
788
                outputFile: certificateFile,
9✔
789
                subject: makeSubject(applicationName, hostname),
9✔
790
                startDate: new Date(),
9✔
791
                validity: 365 * 10 // 10 years
9✔
792
            });
9✔
793
        }
9✔
794
        // c8 ignore next
9✔
795
        if (!fs.existsSync(certificateFile)) {
9!
796
            throw new Error(` cannot locate certificate file ${certificateFile}`);
×
797
        }
×
798
    }
9✔
799

1✔
800
    public async createDefaultCertificate(): Promise<void> {
1✔
801
        // c8 ignore next
1,661✔
802
        if ((this as unknown as { _inCreateDefaultCertificate: boolean })._inCreateDefaultCertificate) {
1,661!
803
            errorLog("Internal error : re-entrancy in createDefaultCertificate!");
×
804
        }
×
805
        (this as unknown as { _inCreateDefaultCertificate: boolean })._inCreateDefaultCertificate = true;
1,661✔
806

1,661✔
807
        if (!checkFileExistsAndIsNotEmpty(this.certificateFile)) {
1,661✔
808
            await withLock({ fileToLock: `${this.certificateFile}.mutex` }, async () => {
14✔
809
                if (checkFileExistsAndIsNotEmpty(this.certificateFile)) {
14✔
810
                    // the file may have been created in between
5✔
811
                    return;
5✔
812
                }
5✔
813
                warningLog("Creating default certificate ... please wait");
9✔
814

9✔
815
                await ClientBaseImpl.createCertificate(
9✔
816
                    this.clientCertificateManager,
9✔
817
                    this.certificateFile,
9✔
818
                    this.applicationName,
9✔
819
                    this._getBuiltApplicationUri()
9✔
820
                );
9✔
821
                debugLog("privateKey      = ", this.privateKeyFile);
9✔
822
                debugLog("                = ", this.clientCertificateManager.privateKey);
9✔
823
                debugLog("certificateFile = ", this.certificateFile);
9✔
824
                const _certificate = this.getCertificate();
9✔
825
                const _privateKey = this.getPrivateKey();
9✔
826
            });
14✔
827
        }
14✔
828
        (this as unknown as { _inCreateDefaultCertificate: boolean })._inCreateDefaultCertificate = false;
1,661✔
829
    }
1,661✔
830

1✔
831
    protected _getBuiltApplicationUri(): string {
1✔
832
        if (!this._applicationUri) {
2,532✔
833
            this._applicationUri = makeApplicationUrn(getHostname(), this.applicationName);
1,166✔
834
        }
1,166✔
835
        return this._applicationUri;
2,532✔
836
    }
2,532✔
837

1✔
838
    protected async initializeCM(): Promise<void> {
1✔
839
        if (!this.clientCertificateManager) {
1,364!
840
            // this usually happen when the client  has been already disconnected,
×
841
            // disconnect
×
842
            errorLog(
×
843
                "[NODE-OPCUA-E08] initializeCM: clientCertificateManager is null\n" +
×
844
                "                 This happen when you disconnected the client, to free resources.\n" +
×
845
                "                 Please create a new OPCUAClient instance if you want to reconnect"
×
846
            );
×
847
            return;
×
848
        }
×
849
        await this.clientCertificateManager.initialize();
1,364✔
850
        await this.createDefaultCertificate();
1,364✔
851
        // c8 ignore next
1,364✔
852
        if (!fs.existsSync(this.privateKeyFile)) {
1,364!
853
            throw new Error(` cannot locate private key file ${this.privateKeyFile}`);
×
854
        }
×
855
        if (this.isUnusable()) return;
1,364✔
856

1,356✔
857
        // Note: do NOT wrap this in withLock2 — performCertificateSanityCheck
1,356✔
858
        // calls trustCertificate and verifyCertificate which each acquire
1,356✔
859
        // withLock2 internally, so an outer withLock2 would cause a deadlock
1,356✔
860
        // on the same file-based mutex.
1,356✔
861
        await performCertificateSanityCheck(this, "client", this.clientCertificateManager, this._getBuiltApplicationUri());
1,356✔
862
    }
1,356✔
863

1✔
864
    protected _internalState: InternalClientState = "uninitialized";
1✔
865

1✔
866
    protected _handleUnrecoverableConnectionFailure(err: Error, callback: ErrorCallback): void {
1✔
867
        debugLog(err.message);
95✔
868
        this.emit("connection_failed", err);
95✔
869
        this._setInternalState("disconnected");
95✔
870
        callback(err);
95✔
871
    }
95✔
872
    private _handleDisconnectionWhileConnecting(err: Error, callback: ErrorCallback) {
1✔
873
        debugLog(err.message);
10✔
874
        this.emit("connection_failed", err);
10✔
875
        this._setInternalState("disconnected");
10✔
876
        callback(err);
10✔
877
    }
10✔
878
    private _handleSuccessfulConnection(callback: ErrorCallback) {
1✔
879
        debugLog(" Connected successfully  to ", this.endpointUrl);
1,415✔
880
        this.emit("connected");
1,415✔
881
        this._setInternalState("connected");
1,415✔
882
        callback();
1,415✔
883
    }
1,415✔
884

1✔
885
    /**
1✔
886
     * connect the OPC-UA client to a server end point.
1✔
887
     */
1✔
888
    public connect(endpointUrl: string): Promise<void>;
1✔
889
    public connect(endpointUrl: string, callback: ErrorCallback): void;
1✔
890
    public connect(...args: unknown[]): Promise<void> | void {
1✔
891
        const endpointUrl = args[0];
1,368✔
892
        const callback = args[1] as ErrorCallback;
1,368✔
893
        assert(typeof callback === "function", "expecting a callback");
1,368✔
894
        if (typeof endpointUrl !== "string" || endpointUrl.length <= 0) {
1,368✔
895
            errorLog(`[NODE-OPCUA-E03] OPCUAClient#connect expects a valid endpoint : ${endpointUrl}`);
1✔
896
            callback(new Error("Invalid endpoint"));
1✔
897
            return;
1✔
898
        }
1✔
899
        assert(typeof endpointUrl === "string" && endpointUrl.length > 0);
1,368✔
900

1,368✔
901
        // c8 ignore next
1,368✔
902
        if (this._internalState !== "disconnected") {
1,368✔
903
            callback(new Error(`client#connect failed, as invalid internal state = ${this._internalState}`));
3✔
904
            return;
3✔
905
        }
3✔
906

1,364✔
907
        // prevent illegal call to connect
1,364✔
908
        if (this._secureChannel !== null) {
1,368!
909
            setImmediate(() => callback(new Error("connect already called")));
×
910
            return;
×
911
        }
×
912

1,364✔
913
        this._setInternalState("connecting");
1,364✔
914

1,364✔
915
        this.initializeCM()
1,364✔
916
            .then(() => {
1,364✔
917
                debugLog("ClientBaseImpl#connect ", endpointUrl, this.clientName);
1,364✔
918
                if (this._internalState === "disconnecting" || this._internalState === "disconnected") {
1,364✔
919
                    return this._handleDisconnectionWhileConnecting(new Error("premature disconnection 1"), callback);
10✔
920
                }
10✔
921

1,354✔
922
                if (
1,354✔
923
                    !this.serverCertificate &&
1,354✔
924
                    (forceEndpointDiscoveryOnConnect || this.securityMode !== MessageSecurityMode.None)
1,000✔
925
                ) {
1,364✔
926
                    debugLog("Fetching certificates from endpoints");
150✔
927
                    this.fetchServerCertificate(endpointUrl, (err: Error | null, adjustedEndpointUrl?: string) => {
150✔
928
                        if (err) {
150✔
929
                            return this._handleUnrecoverableConnectionFailure(err, callback);
3✔
930
                        }
3✔
931
                        if (this.isUnusable()) {
150!
932
                            return this._handleDisconnectionWhileConnecting(new Error("premature disconnection 2"), callback);
×
933
                        }
×
934
                        if (forceEndpointDiscoveryOnConnect) {
150!
935
                            debugLog("connecting with adjusted endpoint : ", adjustedEndpointUrl, "  was =", endpointUrl);
×
936
                            this._connectStep2(adjustedEndpointUrl || "", callback);
×
937
                        } else {
150✔
938
                            debugLog("connecting with endpoint : ", endpointUrl);
147✔
939
                            this._connectStep2(endpointUrl, callback);
147✔
940
                        }
147✔
941
                    });
150✔
942
                } else {
1,364✔
943
                    this._connectStep2(endpointUrl, callback);
1,204✔
944
                }
1,204✔
945
            })
1,364✔
946
            .catch((err) => {
1,364✔
947
                return this._handleUnrecoverableConnectionFailure(err, callback);
×
948
            });
1,364✔
949
    }
1,364✔
950

1✔
951
    /**
1✔
952
     * @private
1✔
953
     */
1✔
954
    public _connectStep2(endpointUrl: string, callback: ErrorCallback): void {
1✔
955
        // prevent illegal call to connect
1,505✔
956
        assert(this._secureChannel === null);
1,505✔
957
        this.endpointUrl = endpointUrl;
1,505✔
958

1,505✔
959
        this._clockAdjuster = this._clockAdjuster || new ClockAdjustment();
1,505✔
960
        OPCUAClientBase.registry.register(this);
1,505✔
961

1,505✔
962
        debugLog("__connectStep2 ", this._internalState);
1,505✔
963

1,505✔
964
        this._internal_create_secure_channel(this.connectionStrategy, (err: Error | null) => {
1,505✔
965
            if (!err) {
1,505✔
966
                this._handleSuccessfulConnection(callback);
1,415✔
967
            } else {
1,505✔
968
                OPCUAClientBase.registry.unregister(this);
90✔
969
                if (this._clockAdjuster) {
90✔
970
                    this._clockAdjuster.dispose();
76✔
971
                    this._clockAdjuster = undefined;
76✔
972
                }
76✔
973
                debugLog(chalk.red("SecureChannel creation has failed with error :", err.message));
90✔
974
                if (err.message.match(/ECONNABORTED/)) {
90!
975
                    debugLog(chalk.yellow(`- The client cannot to :${endpointUrl}. Connection has been aborted.`));
×
976
                    err = new Error("The connection has been aborted");
×
977
                    this._handleUnrecoverableConnectionFailure(err, callback);
×
978
                } else if (err.message.match(/ECONNREF/)) {
90✔
979
                    debugLog(chalk.yellow(`- The client cannot to :${endpointUrl}. Server is not reachable.`));
5✔
980
                    err = new Error(
5✔
981
                        `The connection cannot be established with server ${endpointUrl} .\n` +
5✔
982
                        "Please check that the server is up and running or your network configuration.\n" +
5✔
983
                        "Err = (" +
5✔
984
                        err.message +
5✔
985
                        ")"
5✔
986
                    );
5✔
987
                    this._handleUnrecoverableConnectionFailure(err, callback);
5✔
988
                } else if (err.message.match(/disconnecting/)) {
90!
989
                    /* */
×
990
                    this._handleDisconnectionWhileConnecting(err, callback);
×
991
                } else {
85✔
992
                    err = new Error(`The connection may have been rejected by server,\n Err = (${err.message})`);
85✔
993
                    this._handleUnrecoverableConnectionFailure(err, callback);
85✔
994
                }
85✔
995
            }
90✔
996
        });
1,505✔
997
    }
1,505✔
998

1✔
999
    public performMessageTransaction(request: Request, callback: ResponseCallback<Response>): void {
1✔
1000
        if (!this._secureChannel) {
46,793✔
1001
            // this may happen if the Server has closed the connection abruptly for some unknown reason
1✔
1002
            // or if the tcp connection has been broken.
1✔
1003
            callback(
1✔
1004
                new Error("performMessageTransaction: No SecureChannel , connection may have been canceled abruptly by server")
1✔
1005
            );
1✔
1006
            return;
1✔
1007
        }
1✔
1008
        if (
46,792✔
1009
            this._internalState !== "connected" &&
46,792✔
1010
            this._internalState !== "reconnecting_newchannel_connected" &&
46,793✔
1011
            this._internalState !== "connecting" &&
46,793!
1012
            this._internalState !== "reconnecting"
×
1013
        ) {
46,793!
1014
            callback(
×
1015
                new Error(
×
1016
                    "performMessageTransaction: Invalid client state = " +
×
1017
                    this._internalState +
×
1018
                    " while performing a transaction " +
×
1019
                    request.schema.name
×
1020
                )
×
1021
            );
×
1022
            return;
×
1023
        }
×
1024
        assert(this._secureChannel);
46,792✔
1025
        assert(request);
46,792✔
1026
        assert(request.requestHeader);
46,792✔
1027
        assert(typeof callback === "function");
46,792✔
1028
        this._secureChannel.performMessageTransaction(request, callback as unknown as PerformTransactionCallback);
46,792✔
1029
    }
46,792✔
1030

1✔
1031
    /**
1✔
1032
     *
1✔
1033
     * return the endpoint information matching  security mode and security policy.
1✔
1034

1✔
1035
     */
1✔
1036
    public findEndpointForSecurity(
1✔
1037
        securityMode: MessageSecurityMode,
37✔
1038
        securityPolicy: SecurityPolicy
37✔
1039
    ): EndpointDescription | undefined {
37✔
1040
        securityMode = coerceMessageSecurityMode(securityMode);
37✔
1041
        securityPolicy = coerceSecurityPolicy(securityPolicy);
37✔
1042
        assert(this.knowsServerEndpoint, "Server end point are not known yet");
37✔
1043
        return this._serverEndpoints.find((endpoint) => {
37✔
1044
            return endpoint.securityMode === securityMode && endpoint.securityPolicyUri === securityPolicy;
39✔
1045
        });
37✔
1046
    }
37✔
1047

1✔
1048
    /**
1✔
1049
     *
1✔
1050
     * return the endpoint information matching the specified url , security mode and security policy.
1✔
1051

1✔
1052
     */
1✔
1053
    public findEndpoint(
1✔
1054
        endpointUrl: string,
1,046✔
1055
        securityMode: MessageSecurityMode,
1,046✔
1056
        securityPolicy: SecurityPolicy
1,046✔
1057
    ): EndpointDescription | undefined {
1,046✔
1058
        assert(this.knowsServerEndpoint, "Server end point are not known yet");
1,046✔
1059
        if (!this._serverEndpoints || this._serverEndpoints.length === 0) {
1,046!
1060
            return undefined;
×
1061
        }
×
1062
        return this._serverEndpoints.find((endpoint: EndpointDescription) => {
1,046✔
1063
            return (
1,996✔
1064
                matchUri(endpoint.endpointUrl, endpointUrl) &&
1,996✔
1065
                endpoint.securityMode === securityMode &&
1,996✔
1066
                endpoint.securityPolicyUri === securityPolicy
1,176✔
1067
            );
1,996✔
1068
        });
1,046✔
1069
    }
1,046✔
1070

1✔
1071
    public async getEndpoints(options?: GetEndpointsOptions): Promise<EndpointDescription[]>;
1✔
1072
    public getEndpoints(options: GetEndpointsOptions, callback: ResponseCallback<EndpointDescription[]>): void;
1✔
1073
    public getEndpoints(callback: ResponseCallback<EndpointDescription[]>): void;
1✔
1074
    public getEndpoints(...args: unknown[]): Promise<EndpointDescription[]> | undefined {
1✔
1075
        if (args.length === 1) {
3,106✔
1076
            this.getEndpoints({} as GetEndpointsOptions, args[0] as unknown as ResponseCallback<EndpointDescription[]>);
1,546✔
1077
            return;
1,546✔
1078
        }
1,546✔
1079
        const options = args[0] as GetEndpointsOptions;
1,560✔
1080
        const callback = args[1] as ResponseCallback<EndpointDescription[]>;
1,560✔
1081
        assert(typeof callback === "function");
1,560✔
1082

1,560✔
1083
        options.localeIds = options.localeIds || [];
3,106✔
1084
        options.profileUris = options.profileUris || [];
3,106✔
1085

3,106✔
1086
        const request = new GetEndpointsRequest({
3,106✔
1087
            endpointUrl: options.endpointUrl || this.endpointUrl,
3,106✔
1088
            localeIds: options.localeIds,
3,106✔
1089
            profileUris: options.profileUris,
3,106✔
1090
            requestHeader: {
3,106✔
1091
                auditEntryId: null
3,106✔
1092
            }
3,106✔
1093
        });
3,106✔
1094

3,106✔
1095
        this.performMessageTransaction(request, (err: Error | null, response?: Response) => {
3,106✔
1096
            if (err) {
1,560✔
1097
                callback(err);
8✔
1098
                return;
8✔
1099
            }
8✔
1100
            this._serverEndpoints = [];
1,552✔
1101
            // c8 ignore next
1,552✔
1102
            if (!response || !(response instanceof GetEndpointsResponse)) {
1,560!
1103
                callback(new Error("Internal Error"));
×
1104
                return;
×
1105
            }
×
1106
            if (response?.endpoints) {
1,560✔
1107
                this._serverEndpoints = response.endpoints;
1,552✔
1108
            }
1,552✔
1109
            callback(null, this._serverEndpoints);
1,552✔
1110
        });
3,106✔
1111
        return;
3,106✔
1112
    }
3,106✔
1113

1✔
1114
    /**
1✔
1115
     * @deprecated
1✔
1116
     */
1✔
1117
    public getEndpointsRequest(options: GetEndpointsOptions, callback: ResponseCallback<EndpointDescription[]>): void {
1✔
1118
        warningLog("note: ClientBaseImpl#getEndpointsRequest is deprecated, use ClientBaseImpl#getEndpoints instead");
×
1119
        this.getEndpoints(options, callback);
×
1120
    }
×
1121

1✔
1122
    /**
1✔
1123

1✔
1124
     */
1✔
1125
    public findServers(options?: FindServersRequestLike): Promise<ApplicationDescription[]>;
1✔
1126
    public findServers(options: FindServersRequestLike, callback: ResponseCallback<ApplicationDescription[]>): void;
1✔
1127
    public findServers(callback: ResponseCallback<ApplicationDescription[]>): void;
1✔
1128
    public findServers(...args: unknown[]): Promise<ApplicationDescription[]> | undefined {
1✔
1129
        if (args.length === 1) {
27✔
1130
            if (typeof args[0] === "function") {
12✔
1131
                this.findServers({} as FindServersRequestLike, args[0] as ResponseCallback<ApplicationDescription[]>);
12✔
1132
                return;
12✔
1133
            }
12✔
1134
            throw new Error("Invalid arguments");
×
1135
        }
×
1136
        const options = args[0] as FindServersRequestLike;
15✔
1137
        const callback = args[1] as ResponseCallback<ApplicationDescription[]>;
15✔
1138

15✔
1139
        const request = new FindServersRequest({
15✔
1140
            endpointUrl: options.endpointUrl || this.endpointUrl,
27✔
1141
            localeIds: options.localeIds || [],
27✔
1142
            serverUris: options.serverUris || []
27✔
1143
        });
27✔
1144

27✔
1145
        this.performMessageTransaction(request, (err: Error | null, response?: Response) => {
27✔
1146
            if (err) {
15!
1147
                callback(err);
×
1148
                return;
×
1149
            }
×
1150
            /* c8 ignore next */
2✔
1151
            if (!response || !(response instanceof FindServersResponse)) {
2✔
1152
                callback(new Error("Internal Error"));
×
1153
                return;
×
1154
            }
×
1155
            response.servers = response.servers || [];
15!
1156

15✔
1157
            callback(null, response.servers);
15✔
1158
        });
27✔
1159
        return undefined;
27✔
1160
    }
27✔
1161

1✔
1162
    public findServersOnNetwork(options?: FindServersOnNetworkRequestLike): Promise<ServerOnNetwork[]>;
1✔
1163
    public findServersOnNetwork(callback: ResponseCallback<ServerOnNetwork[]>): void;
1✔
1164
    public findServersOnNetwork(options: FindServersOnNetworkRequestLike, callback: ResponseCallback<ServerOnNetwork[]>): void;
1✔
1165
    public findServersOnNetwork(...args: unknown[]): Promise<ServerOnNetwork[]> | undefined {
1✔
1166
        if (args.length === 1) {
8✔
1167
            this.findServersOnNetwork({} as FindServersOnNetworkRequestOptions, args[0] as ResponseCallback<ServerOnNetwork[]>);
4✔
1168
            return undefined;
4✔
1169
        }
4✔
1170
        const options = args[0] as FindServersOnNetworkRequestOptions;
4✔
1171
        const callback = args[1] as ResponseCallback<ServerOnNetwork[]>;
4✔
1172
        const request = new FindServersOnNetworkRequest(options);
4✔
1173

4✔
1174
        this.performMessageTransaction(request, (err: Error | null, response?: Response) => {
4✔
1175
            if (err) {
4!
1176
                callback(err);
×
1177
                return;
×
1178
            }
×
1179
            /* c8 ignore next */
2✔
1180
            if (!response || !(response instanceof FindServersOnNetworkResponse)) {
2✔
1181
                callback(new Error("Internal Error"));
×
1182
                return;
×
1183
            }
×
1184
            response.servers = response.servers || [];
4!
1185
            callback(null, response.servers);
4✔
1186
        });
4✔
1187
        return undefined;
4✔
1188
    }
4✔
1189

1✔
1190
    public _removeSession(session: ClientSessionImpl): void {
1✔
1191
        const index = this._sessions.indexOf(session);
1,019✔
1192
        if (index >= 0) {
1,019✔
1193
            const _s = this._sessions.splice(index, 1)[0];
995✔
1194
            // assert(s === session);
995✔
1195
            // assert(session._client === this);
995✔
1196
            session._client = null;
995✔
1197
        }
995✔
1198
        assert(this._sessions.indexOf(session) === -1);
1,019✔
1199
    }
1,019✔
1200

1✔
1201
    private _closeSession(
1✔
1202
        session: ClientSessionImpl,
1,015✔
1203
        deleteSubscriptions: boolean,
1,015✔
1204
        callback: (err: Error | null, response?: CloseSessionResponse) => void
1,015✔
1205
    ) {
1,015✔
1206
        assert(typeof callback === "function");
1,015✔
1207
        assert(typeof deleteSubscriptions === "boolean");
1,015✔
1208

1,015✔
1209
        // c8 ignore next
1,015✔
1210
        if (!this._secureChannel) {
1,015✔
1211
            return callback(null); // new Error("no channel"));
10✔
1212
        }
10✔
1213
        assert(this._secureChannel);
1,005✔
1214
        if (!this._secureChannel.isValid()) {
1,015✔
1215
            return callback(null);
1✔
1216
        }
1✔
1217

1,004✔
1218
        debugLog(chalk.bgWhite.green("_closeSession ") + this._secureChannel.channelId);
1,004✔
1219

1,004✔
1220
        const request = new CloseSessionRequest({
1,004✔
1221
            deleteSubscriptions
1,004✔
1222
        });
1,004✔
1223

1,004✔
1224
        session.performMessageTransaction(request, (err: Error | null, response?: Response) => {
1,004✔
1225
            if (err) {
1,004✔
1226
                callback(err);
24✔
1227
            } else {
1,004✔
1228
                callback(err, response as CloseSessionResponse);
980✔
1229
            }
980✔
1230
        });
1,004✔
1231
    }
1,004✔
1232

1✔
1233
    public closeSession(session: ClientSessionImpl, deleteSubscriptions: boolean): Promise<void>;
1✔
1234
    public closeSession(session: ClientSessionImpl, deleteSubscriptions: boolean, callback: ErrorCallback): void;
1✔
1235
    public closeSession(session: ClientSessionImpl, deleteSubscriptions: boolean, callback?: ErrorCallback): Promise<void> | void {
1✔
1236
        assert(typeof deleteSubscriptions === "boolean");
1,015✔
1237
        assert(session);
1,015✔
1238
        assert(session._client === this, "session must be attached to this");
1,015✔
1239
        session._closed = true;
1,015✔
1240

1,015✔
1241
        // todo : send close session on secure channel
1,015✔
1242
        this._closeSession(session, deleteSubscriptions, (err?: Error | null, _response?: CloseSessionResponse) => {
1,015✔
1243
            session.emitCloseEvent(StatusCodes.Good);
1,015✔
1244

1,015✔
1245
            this._removeSession(session);
1,015✔
1246
            session.dispose();
1,015✔
1247

1,015✔
1248
            assert(this._sessions.indexOf(session) === -1);
1,015✔
1249
            assert(session._closed, "session must indicate it is closed");
1,015✔
1250

1,015✔
1251
            callback?.(err ? err : undefined);
1,015✔
1252
        });
1,015✔
1253
    }
1,015✔
1254

1✔
1255
    public disconnect(): Promise<void>;
1✔
1256
    public disconnect(callback: ErrorCallback): void;
1✔
1257
    // eslint-disable-next-line max-statements
1✔
1258
    public disconnect(...args: unknown[]): undefined | Promise<void> {
1✔
1259
        const callback = args[0] as ErrorCallback;
1,612✔
1260
        assert(typeof callback === "function", "expecting a callback function here");
1,612✔
1261
        this._reconnectionIsCanceled = true;
1,612✔
1262
        if (this._tmpClient) {
1,612✔
1263
            warningLog("disconnecting client while tmpClient exists", this._tmpClient.clientName);
2✔
1264
            this._tmpClient.disconnect((_err) => {
2✔
1265
                this._tmpClient = undefined;
2✔
1266
                // retry disconnect on main client
2✔
1267
                this.disconnect(callback);
2✔
1268
            });
2✔
1269
            return undefined;
2✔
1270
        }
2✔
1271
        if (this._internalState === "disconnected" || this._internalState === "disconnecting") {
1,612✔
1272
            if (this._internalState === "disconnecting") {
158✔
1273
                warningLog(
2✔
1274
                    "[NODE-OPCUA-W26] OPCUAClient#disconnect called while already disconnecting. clientName=",
2✔
1275
                    this.clientName
2✔
1276
                );
2✔
1277
            }
2✔
1278
            if (!this._reconnectionIsCanceled && this.isReconnecting) {
158!
1279
                errorLog("Internal Error : _reconnectionIsCanceled should be true if isReconnecting is true");
×
1280
            }
×
1281
            callback();
158✔
1282
            return undefined;
158✔
1283
        }
158✔
1284
        debugLog("disconnecting client! (will set reconnectionIsCanceled to true");
1,452✔
1285
        this._reconnectionIsCanceled = true;
1,452✔
1286

1,452✔
1287
        debugLog("ClientBaseImpl#disconnect", this.endpointUrl);
1,452✔
1288

1,452✔
1289
        if (this.isReconnecting && !this._reconnectionIsCanceled) {
1,612!
1290
            debugLog("ClientBaseImpl#disconnect called while reconnection is in progress");
×
1291
            // let's abort the reconnection process
×
1292
            this._cancel_reconnection((err?: Error) => {
×
1293
                debugLog("ClientBaseImpl#disconnect reconnection has been canceled", this.applicationName);
×
1294
                assert(!err, " why would this fail ?");
×
1295
                // sessions cannot be cancelled properly and must be discarded.
×
1296
                this.disconnect(callback);
×
1297
            });
×
1298
            return undefined;
×
1299
        }
×
1300

1,452✔
1301
        if (this._sessions.length && !this.keepPendingSessionsOnDisconnect) {
1,612✔
1302
            debugLog("warning : disconnection : closing pending sessions");
17✔
1303
            // disconnect has been called whereas living session exists
17✔
1304
            // we need to close them first .... (unless keepPendingSessionsOnDisconnect)
17✔
1305
            this._close_pending_sessions((/*err*/) => {
17✔
1306
                this.disconnect(callback);
17✔
1307
            });
17✔
1308
            return undefined;
17✔
1309
        }
17✔
1310

1,435✔
1311
        debugLog("Disconnecting !");
1,435✔
1312
        this._setInternalState("disconnecting");
1,435✔
1313
        if (this.clientCertificateManager) {
1,435✔
1314
            const tmp = this.clientCertificateManager;
1,435✔
1315
            // (this as any).clientCertificateManager = null;
1,435✔
1316
            tmp.dispose().catch((err: Error) => {
1,435✔
1317
                debugLog("Error disposing clientCertificateManager", err.message);
×
1318
            });
1,435✔
1319
        }
1,435✔
1320

1,435✔
1321
        if (this._sessions.length) {
1,612✔
1322
            // transfer active session to  orphan and detach them from channel
2✔
1323
            const tmp = [...this._sessions];
2✔
1324
            for (const session of tmp) {
2✔
1325
                this._removeSession(session);
2✔
1326
            }
2✔
1327
            this._sessions = [];
2✔
1328
        }
2✔
1329
        assert(this._sessions.length === 0, " attempt to disconnect a client with live sessions ");
1,435✔
1330

1,435✔
1331
        OPCUAClientBase.registry.unregister(this);
1,435✔
1332
        if (this._clockAdjuster) {
1,612✔
1333
            this._clockAdjuster.dispose();
1,425✔
1334
            this._clockAdjuster = undefined;
1,425✔
1335
        }
1,425✔
1336

1,435✔
1337
        if (this._secureChannel) {
1,612✔
1338
            let tmpChannel: ClientSecureChannelLayer | null = this._secureChannel;
1,410✔
1339
            this._secureChannel = null;
1,410✔
1340
            debugLog("Closing channel");
1,410✔
1341
            tmpChannel.close(() => {
1,410✔
1342
                this._secureChannel = tmpChannel;
1,410✔
1343
                tmpChannel = null;
1,410✔
1344
                this._destroy_secure_channel();
1,410✔
1345
                this._setInternalState("disconnected");
1,410✔
1346
                callback();
1,410✔
1347
            });
1,410✔
1348
        } else {
1,612✔
1349
            this._setInternalState("disconnected");
25✔
1350
            //    this.emit("close", null);
25✔
1351
            callback();
25✔
1352
        }
25✔
1353
        return undefined;
1,435✔
1354
    }
1,435✔
1355

1✔
1356
    // override me !
1✔
1357
    public _on_connection_reestablished(callback: ErrorCallback): void {
1✔
1358
        callback();
35✔
1359
    }
35✔
1360

1✔
1361
    public toString(): string {
1✔
1362
        let str = "\n";
1✔
1363
        str += `  defaultSecureTokenLifetime.... ${this.defaultSecureTokenLifetime}\n`;
1✔
1364
        str += `  securityMode.................. ${MessageSecurityMode[this.securityMode]}\n`;
1✔
1365
        str += `  securityPolicy................ ${this.securityPolicy.toString()}\n`;
1✔
1366
        str += `  certificate fingerprint....... ${makeCertificateThumbPrint(this.getCertificate())?.toString("hex") || "none"}\n`;
1!
1367

1✔
1368
        str += `  server certificate fingerprint ${makeCertificateThumbPrint(this.serverCertificate)?.toString("hex") || "none"}\n`;
1!
1369
        // this.serverCertificate = options.serverCertificate || null + "\n";
1✔
1370
        str += `  keepSessionAlive.............. ${this.keepSessionAlive}\n`;
1✔
1371
        str += `  bytesRead..................... ${this.bytesRead}\n`;
1✔
1372
        str += `  bytesWritten.................. ${this.bytesWritten}\n`;
1✔
1373
        str += `  transactionsPerformed......... ${this.transactionsPerformed}\n`;
1✔
1374
        str += `  timedOutRequestCount.......... ${this.timedOutRequestCount}\n`;
1✔
1375
        str += "  connectionStrategy." + "\n";
1✔
1376
        str += `        .maxRetry............... ${this.connectionStrategy.maxRetry}\n`;
1✔
1377
        str += `        .initialDelay........... ${this.connectionStrategy.initialDelay}\n`;
1✔
1378
        str += `        .maxDelay............... ${this.connectionStrategy.maxDelay}\n`;
1✔
1379
        str += `        .randomisationFactor.... ${this.connectionStrategy.randomisationFactor}\n`;
1✔
1380
        str += `  keepSessionAlive.............. ${this.keepSessionAlive}\n`;
1✔
1381
        str += `  applicationName............... ${this.applicationName}\n`;
1✔
1382
        str += `  applicationUri................ ${this._getBuiltApplicationUri()}\n`;
1✔
1383
        str += `  clientName.................... ${this.clientName}\n`;
1✔
1384
        str += `  reconnectOnFailure............ ${this.reconnectOnFailure}\n`;
1✔
1385
        str += `  isReconnecting................ ${this.isReconnecting}\n`;
1✔
1386
        str += `  (internal state).............. ${this._internalState}\n`;
1✔
1387
        str += `  sessions count................ ${this.getSessions().length}\n`;
1✔
1388

1✔
1389
        if (this._secureChannel) {
1✔
1390
            str += `secureChannel:\n${this._secureChannel.toString()}`;
1✔
1391
        }
1✔
1392
        return str;
1✔
1393
    }
1✔
1394

1✔
1395
    public getSessions(): ClientSessionImpl[] {
1✔
1396
        return this._sessions;
36✔
1397
    }
36✔
1398

1✔
1399
    public getTransportSettings(): IBasicTransportSettings {
1✔
1400
        return this._secureChannel?.getTransportSettings() || ({} as IBasicTransportSettings);
2!
1401
    }
2✔
1402

1✔
1403
    protected _addSession(session: ClientSessionImpl): void {
1✔
1404
        assert(!session._client || session._client === this);
999✔
1405
        assert(this._sessions.indexOf(session) === -1, "session already added");
999✔
1406
        session._client = this;
999✔
1407
        this._sessions.push(session);
999✔
1408
    }
999✔
1409

1✔
1410
    private fetchServerCertificate(endpointUrl: string, callback: (err: Error | null, adjustedEndpointUrl?: string) => void): void {
1✔
1411
        const discoveryUrl = this.discoveryUrl.length > 0 ? this.discoveryUrl : endpointUrl;
154!
1412
        debugLog("OPCUAClientImpl : getting serverCertificate");
154✔
1413
        // we have not been given the serverCertificate but this certificate
154✔
1414
        // is required as the connection is to be secured.
154✔
1415
        //
154✔
1416
        // Let's explore the server endpoint that matches our security settings
154✔
1417
        // This will give us the missing Certificate as well from the server.
154✔
1418
        // todo :
154✔
1419
        // Once we have the certificate, we cannot trust it straight away
154✔
1420
        // we have to verify that the certificate is valid and not outdated and not revoked.
154✔
1421
        // if the certificate is self-signed the certificate must appear in the trust certificate
154✔
1422
        // list.
154✔
1423
        // if the certificate has been certified by an Certificate Authority we have to
154✔
1424
        // verify that the certificates in the chain are valid and not revoked.
154✔
1425
        //
154✔
1426
        const certificateFile = this.certificateFile;
154✔
1427
        const privateKeyFile = this.privateKeyFile;
154✔
1428
        const applicationName = this.applicationName;
154✔
1429
        const applicationUri = this._applicationUri;
154✔
1430
        const params = {
154✔
1431
            connectionStrategy: this.connectionStrategy,
154✔
1432
            endpointMustExist: false,
154✔
1433

154✔
1434
            // Node: May be the discovery endpoint only support security mode NONE
154✔
1435
            //       what should we do ?
154✔
1436
            securityMode: this.securityMode,
154✔
1437
            securityPolicy: this.securityPolicy,
154✔
1438

154✔
1439
            applicationName,
154✔
1440
            applicationUri,
154✔
1441

154✔
1442
            certificateFile,
154✔
1443
            privateKeyFile,
154✔
1444

154✔
1445
            clientCertificateManager: this.clientCertificateManager
154✔
1446
        };
154✔
1447
        __findEndpoint.call(this, discoveryUrl, params, (err: Error | null, result?: FindEndpointResult) => {
154✔
1448
            if (err) {
154✔
1449
                callback(err);
2✔
1450
                return;
2✔
1451
            }
2✔
1452

152✔
1453
            // c8 ignore next
152✔
1454
            if (!result) {
154!
1455
                const err1 = new Error("internal error");
×
1456
                callback(err1);
×
1457
                return;
×
1458
            }
×
1459

152✔
1460
            const endpoint = result.selectedEndpoint;
152✔
1461
            if (!endpoint) {
154!
1462
                // no matching end point can be found ...
×
1463
                const err1 = new Error(
×
1464
                    "cannot find endpoint for securityMode=" +
×
1465
                    MessageSecurityMode[this.securityMode] +
×
1466
                    " policy = " +
×
1467
                    this.securityPolicy
×
1468
                );
×
1469
                callback(err1);
×
1470
                return;
×
1471
            }
×
1472

152✔
1473
            assert(endpoint);
152✔
1474

152✔
1475
            _verify_serverCertificate(this.clientCertificateManager, endpoint.serverCertificate)
152✔
1476
                .then(() => {
152✔
1477
                    this.serverCertificate = endpoint.serverCertificate;
151✔
1478
                    callback(null, endpoint.endpointUrl || "");
151!
1479
                })
152✔
1480
                .catch((err1: Error) => {
152✔
1481
                    warningLog("[NODE-OPCUA-W25] client's server certificate verification has failed ", err1.message);
1✔
1482
                    warningLog("                 clientCertificateManager.rootDir = ", this.clientCertificateManager.rootDir);
1✔
1483

1✔
1484
                    const f = (b: Buffer) =>
1✔
1485
                        `                 ${b.toString("base64").replace(/(.{80})/g, "$1\n                 ")}`;
1✔
1486

1✔
1487
                    const chain = split_der(endpoint.serverCertificate);
1✔
1488
                    warningLog(`                  server certificate contains ${chain.length} elements`);
1✔
1489
                    for (let i = 0; i < chain.length; i++) {
1✔
1490
                        const c = chain[i];
1✔
1491
                        warningLog("certificate", i, `\n${f(c)}`);
1✔
1492
                    }
2✔
1493
                    if (chain.length > 1) {
2✔
1494
                        warningLog(
1✔
1495
                            "                 verify also that the issuer certificate is trusted and the issuer's certificate is present in the issuer.cert folder\n" +
1✔
1496
                            "                 of the client certificate manager located in ",
1✔
1497
                            this.clientCertificateManager.rootDir
1✔
1498
                        );
1✔
1499
                    } else {
2✔
1500
                        warningLog(
2✔
1501
                            "                 verify that the server certificate is trusted or that server certificate issuer's certificate is present in the issuer folder"
2✔
1502
                        );
2✔
1503
                    }
2✔
1504
                    callback(err1);
2✔
1505
                });
153✔
1506
        });
155✔
1507
    }
155✔
1508
    private _accumulate_statistics() {
2✔
1509
        if (this._secureChannel) {
1,543✔
1510
            // keep accumulated statistics
1,543✔
1511
            this._byteWritten += this._secureChannel.bytesWritten;
1,543✔
1512
            this._byteRead += this._secureChannel.bytesRead;
1,543✔
1513
            this._transactionsPerformed += this._secureChannel.transactionsPerformed;
1,543✔
1514
            this._timedOutRequestCount += this._secureChannel.timedOutRequestCount;
1,543✔
1515
            // c8 ignore next
1,543✔
1516
            if (doDebug) {
1,543✔
1517
                const h = `Client ${this._instanceNumber} ${this.clientName}`;
1✔
1518
                debugLog(chalk.cyan(`${h} byteWritten          = `), this._byteWritten);
1✔
1519
                debugLog(chalk.cyan(`${h} byteRead             = `), this._byteRead);
1✔
1520
                debugLog(chalk.cyan(`${h} transactions         = `), this._transactionsPerformed);
1✔
1521
                debugLog(chalk.cyan(`${h} timedOutRequestCount = `), this._timedOutRequestCount);
1✔
1522
            }
1✔
1523
        }
1,543✔
1524
    }
1,543✔
1525
    private _destroy_secure_channel() {
2✔
1526
        if (this._secureChannel) {
2,943✔
1527
            // c8 ignore next
1,543✔
1528
            if (doDebug) {
1,543✔
1529
                debugLog(
1✔
1530
                    " DESTROYING SECURE CHANNEL (isTransactionInProgress ?",
1✔
1531
                    this._secureChannel.isTransactionInProgress(),
1✔
1532
                    ")"
1✔
1533
                );
1✔
1534
            }
1✔
1535
            this._accumulate_statistics();
1,543✔
1536
            const secureChannel = this._secureChannel;
1,543✔
1537
            this._secureChannel = null;
1,543✔
1538
            secureChannel.dispose();
1,543✔
1539
            secureChannel.removeAllListeners();
1,543✔
1540
        }
1,543✔
1541
    }
2,943✔
1542

2✔
1543
    private _close_pending_sessions(callback: ErrorCallback) {
2✔
1544
        const sessions = [...this._sessions];
18✔
1545

18✔
1546
        const closeAll = async (): Promise<void> => {
18✔
1547
            for (const session of sessions) {
18✔
1548
                assert(session._client === this);
18✔
1549
                await new Promise<void>((resolve) => {
18✔
1550
                    let resolved = false;
18✔
1551
                    session.close((err?: Error) => {
18✔
1552
                        if (resolved) return;
18✔
1553
                        resolved = true;
18✔
1554
                        if (err) {
18✔
1555
                            const msg = session.authenticationToken ? session.authenticationToken.toString() : "";
1✔
1556
                            debugLog(` failing to close session ${msg}`);
1✔
1557
                        }
1✔
1558
                        resolve();
18✔
1559
                    });
18✔
1560
                });
18✔
1561
            }
18✔
1562
        };
18✔
1563

18✔
1564
        closeAll()
18✔
1565
            .then(() => {
18✔
1566
                // c8 ignore next
18✔
1567
                if (this._sessions.length > 0) {
18✔
1568
                    debugLog(
1✔
1569
                        this._sessions
1✔
1570
                            .map((s: ClientSessionImpl) => (s.authenticationToken ? s.authenticationToken.toString() : ""))
1✔
1571
                            .join(" ")
1✔
1572
                    );
1✔
1573
                }
1✔
1574
                assert(this._sessions.length === 0, " failed to disconnect exiting sessions ");
18✔
1575
                callback();
18✔
1576
            })
18✔
1577
            .catch((err) => callback(err));
18✔
1578
    }
18✔
1579

2✔
1580
    private _install_secure_channel_event_handlers(secureChannel: ClientSecureChannelLayer) {
2✔
1581
        assert(this instanceof ClientBaseImpl);
1,458✔
1582

1,458✔
1583
        secureChannel.on("send_chunk", (chunk: Buffer) => {
1,458✔
1584
            /**
48,596✔
1585
             * notify the observer that a message_chunk has been sent
48,596✔
1586
             * @event send_chunk
48,596✔
1587
             * @param message_chunk
48,596✔
1588
             */
48,596✔
1589
            this.emit("send_chunk", chunk);
48,596✔
1590
        });
1,458✔
1591

1,458✔
1592
        secureChannel.on("receive_chunk", (chunk: Buffer) => {
1,458✔
1593
            /**
47,162✔
1594
             * notify the observer that a message_chunk has been received
47,162✔
1595
             * @event receive_chunk
47,162✔
1596
             * @param message_chunk
47,162✔
1597
             */
47,162✔
1598
            this.emit("receive_chunk", chunk);
47,162✔
1599
        });
1,458✔
1600

1,458✔
1601
        secureChannel.on("send_request", (message: Request1) => {
1,458✔
1602
            /**
48,429✔
1603
             * notify the observer that a request has been sent to the server.
48,429✔
1604
             * @event send_request
48,429✔
1605
             * @param message
48,429✔
1606
             */
48,429✔
1607
            this.emit("send_request", message as unknown as Request);
48,429✔
1608
        });
1,458✔
1609

1,458✔
1610
        secureChannel.on("receive_response", (message: Response1) => {
1,458✔
1611
            /**
44,787✔
1612
             * notify the observer that a response has been received from the server.
44,787✔
1613
             * @event receive_response
44,787✔
1614
             * @param message
44,787✔
1615
             */
44,787✔
1616
            this.emit("receive_response", message as unknown as Response);
44,787✔
1617
        });
1,458✔
1618

1,458✔
1619
        secureChannel.on("lifetime_75", (token: ChannelSecurityToken) => {
1,458✔
1620
            // secureChannel requests a new token
240✔
1621
            debugLog(
240✔
1622
                "SecureChannel Security Token ",
240✔
1623
                token.tokenId,
240✔
1624
                "live time was =",
240✔
1625
                token.revisedLifetime,
240✔
1626
                " is about to expired , it's time to request a new token"
240✔
1627
            );
240✔
1628
            // forward message to upper level
240✔
1629
            this.emit("lifetime_75", token);
240✔
1630
        });
1,458✔
1631

1,458✔
1632
        secureChannel.on("security_token_renewed", (token: ChannelSecurityToken) => {
1,458✔
1633
            // forward message to upper level
238✔
1634
            this.emit("security_token_renewed", secureChannel, token);
238✔
1635
        });
1,458✔
1636

1,458✔
1637
        secureChannel.on("close", (err?: Error | null) => {
1,458✔
1638
            if (err) {
1,454✔
1639
                this._setInternalState("panic");
52✔
1640
            }
52✔
1641
            debugLog(chalk.yellow.bold(" ClientBaseImpl emitting close"), err?.message);
1,454✔
1642
            this._destroy_secure_channel();
1,454✔
1643
            if (!err || !this.reconnectOnFailure) {
1,454✔
1644
                if (err) {
1,410✔
1645
                    /**
8✔
1646
                     * @event connection_lost
8✔
1647
                     */
8✔
1648
                    this.emit("connection_lost"); // instead of "close"
8✔
1649
                }
8✔
1650
                // this is a normal close operation initiated by us
1,410✔
1651
                this.emit("close", err); // instead of "close"
1,410✔
1652
            } else {
1,454✔
1653
                if (
45✔
1654
                    this.reconnectOnFailure &&
45✔
1655
                    this._internalState !== "reconnecting" &&
45✔
1656
                    this._internalState !== "reconnecting_newchannel_connected"
45✔
1657
                ) {
45✔
1658
                    debugLog(" ClientBaseImpl emitting connection_lost");
45✔
1659
                    this._setInternalState("reconnecting");
45✔
1660
                    /**
45✔
1661
                     * @event connection_lost
45✔
1662
                     */
45✔
1663
                    this.emit("connection_lost"); // instead of "close"
45✔
1664
                    this._repairConnection();
45✔
1665
                }
45✔
1666
            }
45✔
1667
        });
1,458✔
1668

1,458✔
1669
        secureChannel.on("timed_out_request", (request: Request1) => {
1,458✔
1670
            /**
2✔
1671
             * send when a request has timed out without receiving a response
2✔
1672
             * @event timed_out_request
2✔
1673
             * @param request
2✔
1674
             */
2✔
1675
            this.emit("timed_out_request", request as unknown as Request);
2✔
1676
        });
1,458✔
1677
    }
1,458✔
1678

2✔
1679
    #insideRepairConnection = false;
2✔
1680

2✔
1681
    #shouldRepairAgain = false;
2✔
1682

2✔
1683
    /**
2✔
1684
     * @internal
2✔
1685
     * @private
2✔
1686
     *
2✔
1687
     * timeout to wait before client attempt to reconnect in case of failure
2✔
1688
     *
2✔
1689
     */
2✔
1690
    static retryDelay = 1000;
2✔
1691
    private _repairConnection() {
2✔
1692
        doDebug && debugLog("_repairConnection = ", this._internalState);
47✔
1693
        if (this.isUnusable()) return;
47✔
1694

45✔
1695
        const duration = ClientBaseImpl.retryDelay;
45✔
1696
        if (duration) {
45✔
1697
            this.emit("startingDelayBeforeReconnection", duration);
45✔
1698
            setTimeout(() => {
45✔
1699
                if (this.isUnusable()) return;
43✔
1700
                this.__innerRepairConnection();
38✔
1701
            }, duration);
45✔
1702
        } else {
47✔
1703
            this.__innerRepairConnection();
1✔
1704
        }
1✔
1705
    }
47✔
1706
    private __innerRepairConnection() {
2✔
1707
        if (this.isUnusable()) return;
38✔
1708

38✔
1709
        debugLog("Entering _repairConnection ", this._internalState);
38✔
1710
        if (this.#insideRepairConnection) {
38✔
1711
            errorLog(
1✔
1712
                "_repairConnection already in progress internal state = ",
1✔
1713
                this._internalState,
1✔
1714
                "clientName =",
1✔
1715
                this.clientName
1✔
1716
            );
1✔
1717
            this.#shouldRepairAgain = true;
1✔
1718
            return;
1✔
1719
        }
1✔
1720
        this.emit("repairConnectionStarted");
38✔
1721
        this.#insideRepairConnection = true;
38✔
1722
        debugLog("recreating new secure channel ", this._internalState);
38✔
1723
        this._recreate_secure_channel((err1?: Error) => {
38✔
1724
            debugLog("secureChannel#on(close) => _recreate_secure_channel returns ", err1 ? err1.message : "OK");
38✔
1725
            if (err1) {
38✔
1726
                debugLog("_recreate_secure_channel has failed: err = ", err1.message);
3✔
1727
                this.emit("close", err1);
3✔
1728
                this.#insideRepairConnection = false;
3✔
1729
                if (this.#shouldRepairAgain) {
3✔
1730
                    this.#shouldRepairAgain = false;
1✔
1731
                    this._repairConnection();
1✔
1732
                } else {
3✔
1733
                    this._setInternalState("disconnected");
3✔
1734
                }
3✔
1735
                return;
3✔
1736
            } else {
38✔
1737
                if (this.isUnusable()) return;
36✔
1738
                this._finalReconnectionStep((err2?: Error | null) => {
36✔
1739
                    if (err2) {
36✔
1740
                        // c8 ignore next
3✔
1741
                        if (doDebug) {
3✔
1742
                            debugLog("connection_reestablished has failed");
1✔
1743
                            debugLog("err= ", err2.message);
1✔
1744
                        }
1✔
1745
                        // we still need to retry connecting here !!!
3✔
1746
                        debugLog("Disconnected following reconnection failure", err2.message);
3✔
1747
                        debugLog(`I will retry OPCUA client reconnection in ${OPCUAClientBase.retryDelay / 1000} seconds`);
3✔
1748
                        this.#insideRepairConnection = false;
3✔
1749
                        this.#shouldRepairAgain = false;
3✔
1750

3✔
1751
                        this._destroy_secure_channel();
3✔
1752
                        // this._setInternalState("reconnecting_failed");
3✔
1753
                        setTimeout(() => this._repairConnection(), OPCUAClientBase.retryDelay);
3✔
1754

3✔
1755
                        return;
3✔
1756
                    } else {
36✔
1757
                        /**
34✔
1758
                         * @event connection_reestablished
34✔
1759
                         *        send when the connection is reestablished after a connection break
34✔
1760
                         */
34✔
1761
                        this.#insideRepairConnection = false;
34✔
1762
                        this.#shouldRepairAgain = false;
34✔
1763
                        this._setInternalState("connected");
34✔
1764
                        this.emit("connection_reestablished");
34✔
1765
                    }
34✔
1766
                });
36✔
1767
            }
36✔
1768
        });
38✔
1769
    }
38✔
1770
    private _finalReconnectionStep(callback: ErrorCallback) {
2✔
1771
        // now delegate to upper class the
36✔
1772
        if (this._on_connection_reestablished) {
36✔
1773
            assert(typeof this._on_connection_reestablished === "function");
36✔
1774
            this._on_connection_reestablished((err2?: Error) => {
36✔
1775
                callback(err2);
36✔
1776
            });
36✔
1777
        } else {
36✔
1778
            callback();
1✔
1779
        }
1✔
1780
    }
36✔
1781

2✔
1782
    /**
2✔
1783
     *
2✔
1784
     * @internal
2✔
1785
     * @private
2✔
1786
     */
2✔
1787
    public __createSession_step2(
2✔
1788
        _session: ClientSessionImpl,
1✔
1789
        _callback: (err: Error | null, session?: ClientSessionImpl) => void
1✔
1790
    ): void {
1✔
1791
        throw new Error("Please override");
1✔
1792
    }
1✔
1793
    public _activateSession(
2✔
1794
        _session: ClientSessionImpl,
1✔
1795
        _userIdentity: UserIdentityInfo,
1✔
1796
        _callback: (err: Error | null, session?: ClientSessionImpl) => void
1✔
1797
    ): void {
1✔
1798
        throw new Error("Please override");
1✔
1799
    }
1✔
1800
}
2✔
1801

2✔
1802
// tslint:disable-next-line: max-classes-per-file
2✔
1803
class TmpClient extends ClientBaseImpl {
2✔
1804
    constructor(options: OPCUAClientBaseOptions) {
2✔
1805
        options.clientName = `${options.clientName || ""}_TmpClient`;
154!
1806
        super(options);
154✔
1807
    }
154✔
1808

2✔
1809
    async connect(endpoint: string): Promise<void>;
2✔
1810
    connect(endpoint: string, callback: ErrorCallback): void;
2✔
1811
    connect(endpoint: string, callback?: ErrorCallback): Promise<void> | void {
2✔
1812
        debugLog("connecting to TmpClient");
154✔
1813

154✔
1814
        // c8 ignore next
154✔
1815
        if (this._internalState !== "disconnected") {
154!
1816
            callback?.(new Error(`TmpClient#connect: invalid internal state = ${this._internalState}`));
×
1817
            return;
×
1818
        }
×
1819

154✔
1820
        this._setInternalState("connecting");
154✔
1821
        this._connectStep2(endpoint, (err?: Error) => {
154✔
1822
            if (this.isUnusable()) {
154✔
1823
                this._handleUnrecoverableConnectionFailure(new Error("premature disconnection 4"), callback || (() => { }));
2!
1824
                return;
2✔
1825
            }
2✔
1826
            callback?.(err);
154✔
1827
        });
154✔
1828
    }
154✔
1829
}
2✔
1830

2✔
1831
// tslint:disable:no-var-requires
2✔
1832
// tslint:disable:max-line-length
2✔
1833
import { withCallback } from "thenify-ex";
2✔
1834

2✔
1835
ClientBaseImpl.prototype.connect = withCallback(ClientBaseImpl.prototype.connect);
2✔
1836
ClientBaseImpl.prototype.disconnect = withCallback(ClientBaseImpl.prototype.disconnect);
2✔
1837
ClientBaseImpl.prototype.getEndpoints = withCallback(ClientBaseImpl.prototype.getEndpoints);
2✔
1838
ClientBaseImpl.prototype.findServers = withCallback(ClientBaseImpl.prototype.findServers);
2✔
1839
ClientBaseImpl.prototype.findServersOnNetwork = withCallback(ClientBaseImpl.prototype.findServersOnNetwork);
2✔
1840

2✔
1841
OPCUAClientBase.create = (options: OPCUAClientBaseOptions): OPCUAClientBase => {
2✔
1842
    return new ClientBaseImpl(options);
348✔
1843
};
348✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc