node-opcua-server 2.156.0 → 2.158.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/addressSpace_accessor.js +1 -0
- package/dist/addressSpace_accessor.js.map +1 -1
- package/dist/base_server.js +7 -1
- package/dist/base_server.js.map +1 -1
- package/dist/factory.js +1 -0
- package/dist/factory.js.map +1 -1
- package/dist/history_server_capabilities.js +14 -0
- package/dist/history_server_capabilities.js.map +1 -1
- package/dist/i_register_server_manager.d.ts +63 -3
- package/dist/i_register_server_manager.js +47 -0
- package/dist/i_register_server_manager.js.map +1 -1
- package/dist/i_server_side_publish_engine.js +4 -0
- package/dist/i_server_side_publish_engine.js.map +1 -1
- package/dist/index.d.ts +22 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -1
- package/dist/monitored_item.d.ts +3 -6
- package/dist/monitored_item.js +44 -17
- package/dist/monitored_item.js.map +1 -1
- package/dist/opcua_server.js +44 -26
- package/dist/opcua_server.js.map +1 -1
- package/dist/queue.js +2 -1
- package/dist/queue.js.map +1 -1
- package/dist/register_server_manager.d.ts +14 -26
- package/dist/register_server_manager.js +282 -293
- package/dist/register_server_manager.js.map +1 -1
- package/dist/register_server_manager_hidden.d.ts +4 -3
- package/dist/register_server_manager_hidden.js +7 -5
- package/dist/register_server_manager_hidden.js.map +1 -1
- package/dist/register_server_manager_mdns_only.d.ts +5 -3
- package/dist/register_server_manager_mdns_only.js +25 -14
- package/dist/register_server_manager_mdns_only.js.map +1 -1
- package/dist/server_capabilities.js +33 -0
- package/dist/server_capabilities.js.map +1 -1
- package/dist/server_end_point.js +29 -3
- package/dist/server_end_point.js.map +1 -1
- package/dist/server_engine.js +20 -3
- package/dist/server_engine.js.map +1 -1
- package/dist/server_publish_engine.js +95 -98
- package/dist/server_publish_engine.js.map +1 -1
- package/dist/server_session.js +33 -5
- package/dist/server_session.js.map +1 -1
- package/dist/server_subscription.js +70 -15
- package/dist/server_subscription.js.map +1 -1
- package/dist/user_manager.js +1 -0
- package/dist/user_manager.js.map +1 -1
- package/package.json +49 -47
- package/source/i_register_server_manager.ts +66 -5
- package/source/index.ts +22 -0
- package/source/monitored_item.ts +28 -19
- package/source/opcua_server.ts +33 -23
- package/source/register_server_manager.ts +294 -354
- package/source/register_server_manager_hidden.ts +6 -5
- package/source/register_server_manager_mdns_only.ts +25 -14
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
// tslint:disable:no-console
|
|
5
5
|
import { EventEmitter } from "events";
|
|
6
|
-
import async from "async";
|
|
7
6
|
import chalk from "chalk";
|
|
8
7
|
|
|
9
8
|
import { assert } from "node-opcua-assert";
|
|
10
|
-
import {
|
|
9
|
+
import { UAString } from "node-opcua-basic-types";
|
|
11
10
|
import {
|
|
12
11
|
coerceLocalizedText,
|
|
13
12
|
LocalizedTextOptions,
|
|
@@ -27,19 +26,12 @@ import {
|
|
|
27
26
|
import { ApplicationType, EndpointDescription, MdnsDiscoveryConfiguration, RegisteredServerOptions } from "node-opcua-types";
|
|
28
27
|
import { exploreCertificate } from "node-opcua-crypto/web";
|
|
29
28
|
import { OPCUACertificateManager } from "node-opcua-certificate-manager";
|
|
30
|
-
import { IRegisterServerManager } from "./i_register_server_manager";
|
|
31
|
-
|
|
32
|
-
const doDebug = checkDebugFlag(
|
|
33
|
-
const debugLog = make_debugLog(
|
|
34
|
-
const warningLog = make_warningLog(
|
|
35
|
-
|
|
36
|
-
export enum RegisterServerManagerStatus {
|
|
37
|
-
INACTIVE = 1,
|
|
38
|
-
INITIALIZING = 2,
|
|
39
|
-
REGISTERING = 3,
|
|
40
|
-
WAITING = 4,
|
|
41
|
-
UNREGISTERING = 5
|
|
42
|
-
}
|
|
29
|
+
import { IRegisterServerManager, RegisterServerManagerStatus } from "./i_register_server_manager";
|
|
30
|
+
|
|
31
|
+
const doDebug = checkDebugFlag("REGISTER_LDS");
|
|
32
|
+
const debugLog = make_debugLog("REGISTER_LDS");
|
|
33
|
+
const warningLog = make_warningLog("REGISTER_LDS");
|
|
34
|
+
|
|
43
35
|
|
|
44
36
|
const g_DefaultRegistrationServerTimeout = 8 * 60 * 1000; // 8 minutes
|
|
45
37
|
|
|
@@ -115,7 +107,7 @@ function findSecureEndpoint(endpoints: EndpointDescription[]): EndpointDescripti
|
|
|
115
107
|
|
|
116
108
|
function constructRegisteredServer(server: IPartialServer, isOnline: boolean): RegisteredServerOptions {
|
|
117
109
|
const discoveryUrls = server.getDiscoveryUrls();
|
|
118
|
-
assert(!isOnline || discoveryUrls.length >= 1, "expecting some discoveryUrls if we go online ....");
|
|
110
|
+
assert(!isOnline || discoveryUrls.length >= 1, "expecting some discoveryUrls if we go online .... ");
|
|
119
111
|
|
|
120
112
|
const info = exploreCertificate(server.getCertificate());
|
|
121
113
|
const commonName = info.tbsCertificate.subject.commonName!;
|
|
@@ -184,9 +176,9 @@ function constructRegisterServer2Request(serverB: IPartialServer, isOnline: bool
|
|
|
184
176
|
});
|
|
185
177
|
}
|
|
186
178
|
|
|
187
|
-
const
|
|
188
|
-
initialDelay:
|
|
189
|
-
maxDelay:
|
|
179
|
+
const no_retry_connectivity_strategy = {
|
|
180
|
+
initialDelay: 1000,
|
|
181
|
+
maxDelay: 2000,
|
|
190
182
|
maxRetry: 1, // NO RETRY !!!
|
|
191
183
|
randomisationFactor: 0
|
|
192
184
|
};
|
|
@@ -197,6 +189,8 @@ const infinite_connectivity_strategy = {
|
|
|
197
189
|
randomisationFactor: 0
|
|
198
190
|
};
|
|
199
191
|
|
|
192
|
+
const pause = async (duration: number) => await new Promise<void>((resolve) => setTimeout(resolve, duration));
|
|
193
|
+
|
|
200
194
|
interface ClientBaseEx extends OPCUAClientBase {
|
|
201
195
|
_serverEndpoints: EndpointDescription[];
|
|
202
196
|
|
|
@@ -204,17 +198,17 @@ interface ClientBaseEx extends OPCUAClientBase {
|
|
|
204
198
|
performMessageTransaction(request: RegisterServerRequest, callback: ResponseCallback<RegisterServerResponse>): void;
|
|
205
199
|
}
|
|
206
200
|
|
|
207
|
-
function sendRegisterServerRequest(server: IPartialServer, client: ClientBaseEx, isOnline: boolean
|
|
201
|
+
async function sendRegisterServerRequest(server: IPartialServer, client: ClientBaseEx, isOnline: boolean) {
|
|
208
202
|
// try to send a RegisterServer2Request
|
|
209
203
|
const request = constructRegisterServer2Request(server, isOnline);
|
|
210
204
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
205
|
+
await new Promise<void>((resolve, reject) => {
|
|
206
|
+
client.performMessageTransaction(request, (err: Error | null, response?: RegisterServer2Response) => {
|
|
207
|
+
if (!err) {
|
|
208
|
+
// RegisterServerResponse
|
|
209
|
+
debugLog("RegisterServerManager#_registerServer sendRegisterServer2Request has succeeded (isOnline", isOnline, ")");
|
|
210
|
+
return resolve();
|
|
211
|
+
}
|
|
218
212
|
debugLog("RegisterServerManager#_registerServer sendRegisterServer2Request has failed " + "(isOnline", isOnline, ")");
|
|
219
213
|
debugLog("RegisterServerManager#_registerServer" + " falling back to using sendRegisterServerRequest instead");
|
|
220
214
|
// fall back to
|
|
@@ -226,17 +220,17 @@ function sendRegisterServerRequest(server: IPartialServer, client: ClientBaseEx,
|
|
|
226
220
|
isOnline,
|
|
227
221
|
")"
|
|
228
222
|
);
|
|
229
|
-
|
|
230
|
-
} else {
|
|
231
|
-
debugLog(
|
|
232
|
-
"RegisterServerManager#_registerServer sendRegisterServerRequest " + "has failed (isOnline",
|
|
233
|
-
isOnline,
|
|
234
|
-
")"
|
|
235
|
-
);
|
|
223
|
+
return resolve();
|
|
236
224
|
}
|
|
237
|
-
|
|
225
|
+
debugLog(
|
|
226
|
+
"RegisterServerManager#_registerServer sendRegisterServerRequest " + "has failed (isOnline",
|
|
227
|
+
isOnline,
|
|
228
|
+
")"
|
|
229
|
+
);
|
|
230
|
+
reject(err1!)
|
|
238
231
|
});
|
|
239
|
-
}
|
|
232
|
+
});
|
|
233
|
+
|
|
240
234
|
});
|
|
241
235
|
}
|
|
242
236
|
|
|
@@ -262,13 +256,13 @@ export interface RegisterServerManagerOptions {
|
|
|
262
256
|
let g_registeringClientCounter = 0;
|
|
263
257
|
/**
|
|
264
258
|
* RegisterServerManager is responsible to Register an opcua server on a LDS or LDS-ME server
|
|
265
|
-
* This class takes in charge :
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
259
|
+
* This class takes in charge :
|
|
260
|
+
* - the initial registration of a server
|
|
261
|
+
* - the regular registration renewal (every 8 minutes or so ...)
|
|
262
|
+
* - dealing with cases where LDS is not up and running when server starts.
|
|
263
|
+
* ( in this case the connection will be continuously attempted using the infinite
|
|
264
|
+
* back-off strategy
|
|
265
|
+
* - the un-registration of the server ( during shutdown for instance)
|
|
272
266
|
*
|
|
273
267
|
* Events:
|
|
274
268
|
*
|
|
@@ -302,11 +296,14 @@ export class RegisterServerManager extends EventEmitter implements IRegisterServ
|
|
|
302
296
|
private selectedEndpoint?: EndpointDescription;
|
|
303
297
|
private _serverEndpoints: EndpointDescription[] = [];
|
|
304
298
|
|
|
299
|
+
getState(): RegisterServerManagerStatus {
|
|
300
|
+
return this.state;
|
|
301
|
+
}
|
|
305
302
|
constructor(options: RegisterServerManagerOptions) {
|
|
306
303
|
super();
|
|
307
304
|
|
|
308
305
|
this.server = options.server;
|
|
309
|
-
this
|
|
306
|
+
this.#_setState(RegisterServerManagerStatus.INACTIVE);
|
|
310
307
|
this.timeout = g_DefaultRegistrationServerTimeout;
|
|
311
308
|
this.discoveryServerEndpointUrl = options.discoveryServerEndpointUrl || "opc.tcp://localhost:4840";
|
|
312
309
|
|
|
@@ -317,7 +314,6 @@ export class RegisterServerManager extends EventEmitter implements IRegisterServ
|
|
|
317
314
|
public dispose(): void {
|
|
318
315
|
this.server = null;
|
|
319
316
|
debugLog("RegisterServerManager#dispose", this.state.toString());
|
|
320
|
-
assert(this.state === RegisterServerManagerStatus.INACTIVE);
|
|
321
317
|
|
|
322
318
|
if (this._registrationTimerId) {
|
|
323
319
|
clearTimeout(this._registrationTimerId);
|
|
@@ -328,13 +324,14 @@ export class RegisterServerManager extends EventEmitter implements IRegisterServ
|
|
|
328
324
|
this.removeAllListeners();
|
|
329
325
|
}
|
|
330
326
|
|
|
331
|
-
|
|
327
|
+
#_emitEvent(eventName: string): void {
|
|
332
328
|
setImmediate(() => {
|
|
329
|
+
debugLog("emiting event", eventName);
|
|
333
330
|
this.emit(eventName);
|
|
334
331
|
});
|
|
335
332
|
}
|
|
336
333
|
|
|
337
|
-
|
|
334
|
+
#_setState(status: RegisterServerManagerStatus): void {
|
|
338
335
|
const previousState = this.state || RegisterServerManagerStatus.INACTIVE;
|
|
339
336
|
debugLog(
|
|
340
337
|
"RegisterServerManager#setState : ",
|
|
@@ -345,88 +342,123 @@ export class RegisterServerManager extends EventEmitter implements IRegisterServ
|
|
|
345
342
|
this.state = status;
|
|
346
343
|
}
|
|
347
344
|
|
|
348
|
-
|
|
345
|
+
/**
|
|
346
|
+
* The start method initiates the registration process in a non-blocking way.
|
|
347
|
+
* It immediately returns while the actual work is performed in a background task.
|
|
348
|
+
*/
|
|
349
|
+
public async start(): Promise<void> {
|
|
349
350
|
debugLog("RegisterServerManager#start");
|
|
350
351
|
if (this.state !== RegisterServerManagerStatus.INACTIVE) {
|
|
351
|
-
|
|
352
|
+
throw new Error("RegisterServer process already started: " + RegisterServerManagerStatus[this.state]);
|
|
352
353
|
}
|
|
353
|
-
|
|
354
354
|
this.discoveryServerEndpointUrl = resolveFullyQualifiedDomainName(this.discoveryServerEndpointUrl);
|
|
355
355
|
|
|
356
|
-
//
|
|
357
|
-
this.
|
|
358
|
-
if (err) {
|
|
359
|
-
debugLog("RegisterServerManager#start => _establish_initial_connection has failed");
|
|
360
|
-
return callback(err);
|
|
361
|
-
}
|
|
362
|
-
if (this.state !== RegisterServerManagerStatus.INITIALIZING) {
|
|
363
|
-
debugLog("RegisterServerManager#start => _establish_initial_connection has failed");
|
|
364
|
-
return callback();
|
|
365
|
-
}
|
|
356
|
+
// Immediately set the state to INITIALIZING and run the process in the background.
|
|
357
|
+
this.#_setState(RegisterServerManagerStatus.INITIALIZING);
|
|
366
358
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
359
|
+
// This method is called without await to ensure it is non-blocking.
|
|
360
|
+
// The catch block handles any synchronous errors.
|
|
361
|
+
this.#_runRegistrationProcess().catch((err) => {
|
|
362
|
+
warningLog("Synchronous error in #_runRegistrationProcess: ", (err?.message));
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Private method to run the entire registration process in the background.
|
|
368
|
+
* It handles the state machine transitions and re-connection logic.
|
|
369
|
+
* @private
|
|
370
|
+
*/
|
|
371
|
+
async #_runRegistrationProcess(): Promise<void> {
|
|
372
|
+
while (this.getState() !== RegisterServerManagerStatus.WAITING && !this.#_isTerminating()) {
|
|
373
|
+
debugLog("RegisterServerManager#_runRegistrationProcess - state =", RegisterServerManagerStatus[this.state], "isTerminating =", this.#_isTerminating());
|
|
374
|
+
try {
|
|
375
|
+
if (this.getState() === RegisterServerManagerStatus.INACTIVE) {
|
|
376
|
+
this.#_setState(RegisterServerManagerStatus.INITIALIZING);
|
|
371
377
|
}
|
|
378
|
+
await this.#_establish_initial_connection();
|
|
372
379
|
|
|
373
|
-
if (
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
"please check that your server certificate is accepted by the LDS"
|
|
377
|
-
);
|
|
378
|
-
this._setState(RegisterServerManagerStatus.INACTIVE);
|
|
379
|
-
this._emitEvent("serverRegistrationFailure");
|
|
380
|
-
} else {
|
|
381
|
-
this._emitEvent("serverRegistered");
|
|
382
|
-
this._setState(RegisterServerManagerStatus.WAITING);
|
|
383
|
-
this._trigger_next();
|
|
380
|
+
if (this.getState() !== RegisterServerManagerStatus.INITIALIZING) {
|
|
381
|
+
debugLog("RegisterServerManager#_runRegistrationProcess: aborted during initialization");
|
|
382
|
+
return;
|
|
384
383
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
384
|
+
|
|
385
|
+
this.#_setState(RegisterServerManagerStatus.INITIALIZED);
|
|
386
|
+
this.#_setState(RegisterServerManagerStatus.REGISTERING);
|
|
387
|
+
this.#_emitEvent("serverRegistrationPending");
|
|
388
|
+
|
|
389
|
+
await this.#_registerOrUnregisterServer(true);
|
|
390
|
+
|
|
391
|
+
if (this.getState() !== RegisterServerManagerStatus.REGISTERING) {
|
|
392
|
+
debugLog("RegisterServerManager#_runRegistrationProcess: aborted during registration");
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
this.#_setState(RegisterServerManagerStatus.REGISTERED);
|
|
397
|
+
this.#_emitEvent("serverRegistered");
|
|
398
|
+
this.#_setState(RegisterServerManagerStatus.WAITING);
|
|
399
|
+
this.#_trigger_next();
|
|
400
|
+
return;
|
|
401
|
+
} catch (err) {
|
|
402
|
+
debugLog("RegisterServerManager#_runRegistrationProcess - operation failed!", ((err as Error).message));
|
|
403
|
+
if (!this.#_isTerminating()) {
|
|
404
|
+
this.#_setState(RegisterServerManagerStatus.INACTIVE);
|
|
405
|
+
this.#_emitEvent("serverRegistrationFailure");
|
|
406
|
+
await pause(Math.min(5000, this.timeout));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
#_isTerminating(): boolean {
|
|
412
|
+
return this.getState() === RegisterServerManagerStatus.UNREGISTERING || this.getState() === RegisterServerManagerStatus.DISPOSING
|
|
388
413
|
}
|
|
389
414
|
|
|
390
|
-
|
|
391
|
-
|
|
415
|
+
/**
|
|
416
|
+
* Establish the initial connection with the Discovery Server to extract best endpoint to use.
|
|
417
|
+
* @private
|
|
418
|
+
*/
|
|
419
|
+
async #_establish_initial_connection(): Promise<void> {
|
|
392
420
|
if (!this.server) {
|
|
393
|
-
|
|
421
|
+
this.#_setState(RegisterServerManagerStatus.DISPOSING);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (this.state !== RegisterServerManagerStatus.INITIALIZING) {
|
|
425
|
+
debugLog("RegisterServerManager#_establish_initial_connection: aborting due to state change");
|
|
426
|
+
return;
|
|
394
427
|
}
|
|
395
428
|
debugLog("RegisterServerManager#_establish_initial_connection");
|
|
396
429
|
|
|
397
430
|
assert(!this._registration_client);
|
|
398
431
|
assert(typeof this.discoveryServerEndpointUrl === "string");
|
|
399
|
-
assert(this.state === RegisterServerManagerStatus.
|
|
400
|
-
|
|
432
|
+
assert(this.state === RegisterServerManagerStatus.INITIALIZING);
|
|
433
|
+
|
|
434
|
+
|
|
401
435
|
this.selectedEndpoint = undefined;
|
|
402
436
|
|
|
403
437
|
const applicationName = coerceLocalizedText(this.server.serverInfo.applicationName!)?.text || undefined;
|
|
404
|
-
|
|
405
|
-
// Retry Strategy must be set
|
|
406
438
|
this.server.serverCertificateManager.referenceCounter++;
|
|
407
|
-
|
|
408
|
-
const prefix = "Client-" + g_registeringClientCounter++ + " - ";
|
|
409
439
|
|
|
410
|
-
const
|
|
411
|
-
|
|
440
|
+
const server = this.server;
|
|
441
|
+
const prefix = `Client-${g_registeringClientCounter++}`;
|
|
442
|
+
const action = "initializing";
|
|
443
|
+
const ldsInfo = this.discoveryServerEndpointUrl;
|
|
444
|
+
const serverInfo = this.server?.serverInfo.applicationUri!;
|
|
445
|
+
const clientName = `${prefix} for server ${serverInfo} to LDS ${ldsInfo} for ${action}`;
|
|
412
446
|
|
|
447
|
+
const registrationClient = OPCUAClientBase.create({
|
|
448
|
+
clientName,
|
|
413
449
|
applicationName,
|
|
414
|
-
|
|
415
|
-
applicationUri: this.server.serverInfo.applicationUri!,
|
|
416
|
-
|
|
450
|
+
applicationUri: server.serverInfo.applicationUri!,
|
|
417
451
|
connectionStrategy: infinite_connectivity_strategy,
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
certificateFile: this.server.certificateFile,
|
|
422
|
-
privateKeyFile: this.server.privateKeyFile
|
|
452
|
+
clientCertificateManager: server.serverCertificateManager,
|
|
453
|
+
certificateFile: server.certificateFile,
|
|
454
|
+
privateKeyFile: server.privateKeyFile
|
|
423
455
|
}) as ClientBaseEx;
|
|
424
456
|
|
|
425
|
-
this._registration_client = registrationClient;
|
|
426
457
|
|
|
427
458
|
registrationClient.on("backoff", (nbRetry: number, delay: number) => {
|
|
459
|
+
if (this.state !== RegisterServerManagerStatus.INITIALIZING) return; // Ignore event if state has changed
|
|
428
460
|
debugLog("RegisterServerManager - received backoff");
|
|
429
|
-
|
|
461
|
+
debugLog(
|
|
430
462
|
registrationClient.clientName,
|
|
431
463
|
chalk.bgWhite.cyan("contacting discovery server backoff "),
|
|
432
464
|
this.discoveryServerEndpointUrl,
|
|
@@ -436,123 +468,64 @@ export class RegisterServerManager extends EventEmitter implements IRegisterServ
|
|
|
436
468
|
delay / 1000.0,
|
|
437
469
|
" seconds"
|
|
438
470
|
);
|
|
439
|
-
this
|
|
471
|
+
this.#_emitEvent("serverRegistrationPending");
|
|
440
472
|
});
|
|
441
473
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
// do_initial_connection_with_discovery_server
|
|
445
|
-
(callback: ErrorCallback) => {
|
|
446
|
-
registrationClient.connect(this.discoveryServerEndpointUrl, (err?: Error) => {
|
|
447
|
-
if (err) {
|
|
448
|
-
debugLog(
|
|
449
|
-
"RegisterServerManager#_establish_initial_connection " + ": initial connection to server has failed"
|
|
450
|
-
);
|
|
451
|
-
// xx debugLog(err);
|
|
452
|
-
}
|
|
453
|
-
return callback(err);
|
|
454
|
-
});
|
|
455
|
-
},
|
|
456
|
-
|
|
457
|
-
// getEndpoints_on_discovery_server
|
|
458
|
-
(callback: ErrorCallback) => {
|
|
459
|
-
registrationClient.getEndpoints((err: Error | null, endpoints?: EndpointDescription[]) => {
|
|
460
|
-
if (!err) {
|
|
461
|
-
const endpoint = findSecureEndpoint(endpoints!);
|
|
462
|
-
|
|
463
|
-
if (!endpoint) {
|
|
464
|
-
throw new Error("Cannot find Secure endpoint");
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
if (endpoint.serverCertificate) {
|
|
468
|
-
assert(endpoint.serverCertificate);
|
|
469
|
-
this.selectedEndpoint = endpoint;
|
|
470
|
-
} else {
|
|
471
|
-
this.selectedEndpoint = undefined;
|
|
472
|
-
}
|
|
473
|
-
} else {
|
|
474
|
-
debugLog("RegisterServerManager#_establish_initial_connection " + ": getEndpointsRequest has failed");
|
|
475
|
-
debugLog(err);
|
|
476
|
-
}
|
|
477
|
-
callback(err!);
|
|
478
|
-
});
|
|
479
|
-
},
|
|
480
|
-
// function closing_discovery_server_connection
|
|
481
|
-
(callback: ErrorCallback) => {
|
|
482
|
-
this._serverEndpoints = registrationClient._serverEndpoints;
|
|
483
|
-
|
|
484
|
-
registrationClient.disconnect((err?: Error) => {
|
|
485
|
-
this._registration_client = null;
|
|
486
|
-
callback(err);
|
|
487
|
-
});
|
|
488
|
-
},
|
|
489
|
-
// function wait_a_little_bit
|
|
490
|
-
(callback: ErrorCallback) => {
|
|
491
|
-
setTimeout(callback, 10);
|
|
492
|
-
}
|
|
493
|
-
],
|
|
494
|
-
(err?: Error | null) => {
|
|
495
|
-
debugLog("-------------------------------", !!err);
|
|
474
|
+
// Keep track of the client to allow cancellation during connect()
|
|
475
|
+
this._registration_client = registrationClient;
|
|
496
476
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
477
|
+
try {
|
|
478
|
+
await registrationClient.connect(this.discoveryServerEndpointUrl);
|
|
479
|
+
|
|
480
|
+
if (!this._registration_client) return;
|
|
481
|
+
|
|
482
|
+
// Re-check state after the long-running connect operation
|
|
483
|
+
if (this.state !== RegisterServerManagerStatus.INITIALIZING) {
|
|
484
|
+
debugLog("RegisterServerManager#_establish_initial_connection: aborted after connection");
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
const endpoints: EndpointDescription[] | undefined = await registrationClient.getEndpoints();
|
|
490
|
+
const endpoint = findSecureEndpoint(endpoints!);
|
|
491
|
+
if (!endpoint) {
|
|
492
|
+
throw new Error("Cannot find Secure endpoint");
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (endpoint.serverCertificate) {
|
|
496
|
+
assert(endpoint.serverCertificate);
|
|
497
|
+
this.selectedEndpoint = endpoint;
|
|
498
|
+
} else {
|
|
499
|
+
this.selectedEndpoint = undefined;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
this._serverEndpoints = registrationClient._serverEndpoints;
|
|
503
|
+
} catch (err) {
|
|
504
|
+
// Do not set state to INACTIVE or rethrow here, let the caller handle it.
|
|
505
|
+
throw err;
|
|
506
|
+
} finally {
|
|
507
|
+
if (this._registration_client) {
|
|
508
|
+
const tmp = this._registration_client;
|
|
509
|
+
this._registration_client = null;
|
|
510
|
+
try {
|
|
511
|
+
await tmp.disconnect();
|
|
512
|
+
} catch (err) {
|
|
513
|
+
warningLog("RegisterServerManager#_establish_initial_connection: error disconnecting client", err);
|
|
523
514
|
}
|
|
524
|
-
outer_callback();
|
|
525
515
|
}
|
|
526
|
-
|
|
516
|
+
}
|
|
527
517
|
}
|
|
528
518
|
|
|
529
|
-
|
|
519
|
+
#_trigger_next(): void {
|
|
530
520
|
assert(!this._registrationTimerId);
|
|
531
521
|
assert(this.state === RegisterServerManagerStatus.WAITING);
|
|
532
|
-
|
|
533
|
-
// The registration process is designed to be platform independent, robust and able to minimize
|
|
534
|
-
// problems created by configuration errors. For that reason, Servers shall register themselves more
|
|
535
|
-
// than once.
|
|
536
|
-
// Under normal conditions, manually launched Servers shall periodically register with the Discovery
|
|
537
|
-
// Server as long as they are able to receive connections from Clients. If a Server goes offline then it
|
|
538
|
-
// shall register itself once more and indicate that it is going offline. The registration frequency
|
|
539
|
-
// should be configurable; however, the maximum is 10 minutes. If an error occurs during registration
|
|
540
|
-
// (e.g. the Discovery Server is not running) then the Server shall periodically re-attempt registration.
|
|
541
|
-
// The frequency of these attempts should start at 1 second but gradually increase until the
|
|
542
|
-
// registration frequency is the same as what it would be if no errors occurred. The recommended
|
|
543
|
-
// approach would be to double the period of each attempt until reaching the maximum.
|
|
544
|
-
// When an automatically launched Server (or its install program) registers with the Discovery Server
|
|
545
|
-
// it shall provide a path to a semaphore file which the Discovery Server can use to determine if the
|
|
546
|
-
// Server has been uninstalled from the machine. The Discovery Server shall have read access to
|
|
547
|
-
// the file system that contains the file
|
|
548
|
-
|
|
549
|
-
// install a registration
|
|
522
|
+
|
|
550
523
|
debugLog(
|
|
551
524
|
"RegisterServerManager#_trigger_next " + ": installing timeout to perform registerServer renewal (timeout =",
|
|
552
525
|
this.timeout,
|
|
553
526
|
")"
|
|
554
527
|
);
|
|
555
|
-
|
|
528
|
+
if (this._registrationTimerId) clearTimeout(this._registrationTimerId);
|
|
556
529
|
this._registrationTimerId = setTimeout(() => {
|
|
557
530
|
if (!this._registrationTimerId) {
|
|
558
531
|
debugLog("RegisterServerManager => cancelling re registration");
|
|
@@ -560,210 +533,177 @@ export class RegisterServerManager extends EventEmitter implements IRegisterServ
|
|
|
560
533
|
}
|
|
561
534
|
this._registrationTimerId = null;
|
|
562
535
|
|
|
536
|
+
if (this.#_isTerminating()) {
|
|
537
|
+
debugLog("RegisterServerManager#_trigger_next : cancelling re registration");
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
563
540
|
debugLog("RegisterServerManager#_trigger_next : renewing RegisterServer");
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
this
|
|
571
|
-
this._emitEvent("serverRegistrationRenewed");
|
|
572
|
-
this._trigger_next();
|
|
541
|
+
|
|
542
|
+
const after_register = (err?: Error) => {
|
|
543
|
+
if (!this.#_isTerminating()) {
|
|
544
|
+
debugLog("RegisterServerManager#_trigger_next : renewed ! err:", err?.message);
|
|
545
|
+
this.#_setState(RegisterServerManagerStatus.WAITING);
|
|
546
|
+
this.#_emitEvent("serverRegistrationRenewed");
|
|
547
|
+
this.#_trigger_next();
|
|
573
548
|
}
|
|
574
|
-
}
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
// State transition before the call
|
|
552
|
+
this.#_setState(RegisterServerManagerStatus.REGISTERING);
|
|
553
|
+
this.#_emitEvent("serverRegistrationPending");
|
|
554
|
+
|
|
555
|
+
this.#_registerOrUnregisterServer(/*isOnline=*/true)
|
|
556
|
+
.then(() => after_register())
|
|
557
|
+
.catch((err) => after_register(err));
|
|
575
558
|
}, this.timeout);
|
|
576
559
|
}
|
|
577
560
|
|
|
578
|
-
public stop(
|
|
561
|
+
public async stop(): Promise<void> {
|
|
579
562
|
debugLog("RegisterServerManager#stop");
|
|
563
|
+
if (this.#_isTerminating()) {
|
|
564
|
+
debugLog("Already stopping or stopped...");
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
580
567
|
|
|
568
|
+
// make sure we don't have any timer running
|
|
569
|
+
// so a registration renewal won't happen while we are stopping
|
|
581
570
|
if (this._registrationTimerId) {
|
|
582
|
-
debugLog("RegisterServerManager#stop :clearing timeout");
|
|
583
571
|
clearTimeout(this._registrationTimerId);
|
|
584
572
|
this._registrationTimerId = null;
|
|
585
573
|
}
|
|
586
574
|
|
|
587
|
-
this._cancel_pending_client_if_any(() => {
|
|
588
|
-
debugLog("RegisterServerManager#stop _cancel_pending_client_if_any done ", this.state);
|
|
589
575
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
576
|
+
// Immediately set state to signal a stop
|
|
577
|
+
this.#_setState(RegisterServerManagerStatus.UNREGISTERING);
|
|
578
|
+
|
|
579
|
+
// Cancel any pending client connections
|
|
580
|
+
await this.#_cancel_pending_client_if_any();
|
|
581
|
+
|
|
582
|
+
if (this.selectedEndpoint) {
|
|
583
|
+
try {
|
|
584
|
+
await this.#_registerOrUnregisterServer(/* isOnline= */false);
|
|
585
|
+
this.#_setState(RegisterServerManagerStatus.UNREGISTERED);
|
|
586
|
+
this.#_emitEvent("serverUnregistered");
|
|
587
|
+
} catch (err) {
|
|
588
|
+
warningLog(err);
|
|
589
|
+
warningLog("RegisterServerManager#stop: Unregistration failed.", (err as Error).message);
|
|
594
590
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
});
|
|
600
|
-
});
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Final state transition to INACTIVE
|
|
594
|
+
this.#_setState(RegisterServerManagerStatus.DISPOSING);
|
|
601
595
|
}
|
|
602
596
|
|
|
603
597
|
/**
|
|
604
|
-
*
|
|
605
|
-
*
|
|
598
|
+
* Handles the actual registration/unregistration request.
|
|
599
|
+
* It is designed to be interruptible by checking the state.
|
|
600
|
+
* @param isOnline - true for registration, false for unregistration
|
|
606
601
|
* @private
|
|
607
602
|
*/
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
"selectedEndpoint: ",
|
|
615
|
-
this.selectedEndpoint?.endpointUrl
|
|
616
|
-
);
|
|
603
|
+
async #_registerOrUnregisterServer(isOnline: boolean): Promise<void> {
|
|
604
|
+
const expectedState = isOnline ? RegisterServerManagerStatus.REGISTERING : RegisterServerManagerStatus.UNREGISTERING;
|
|
605
|
+
if (this.getState() !== expectedState) {
|
|
606
|
+
debugLog("RegisterServerManager#_registerServer: aborting due to state change");
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
617
609
|
|
|
618
|
-
|
|
610
|
+
debugLog("RegisterServerManager#_registerServer isOnline:", isOnline);
|
|
619
611
|
|
|
612
|
+
assert(this.selectedEndpoint, "must have a selected endpoint");
|
|
620
613
|
assert(this.server!.serverType !== undefined, " must have a valid server Type");
|
|
621
614
|
|
|
622
|
-
// construct connection
|
|
623
615
|
const server = this.server!;
|
|
624
616
|
const selectedEndpoint = this.selectedEndpoint;
|
|
625
|
-
|
|
626
617
|
if (!selectedEndpoint) {
|
|
627
|
-
warningLog("Warning
|
|
628
|
-
|
|
618
|
+
warningLog("Warning: cannot register server - no endpoint available");
|
|
619
|
+
// Do not rethrow here, let the caller handle it.
|
|
620
|
+
return;
|
|
629
621
|
}
|
|
630
622
|
|
|
631
623
|
server.serverCertificateManager.referenceCounter++;
|
|
632
|
-
|
|
633
624
|
const applicationName: string | undefined = coerceLocalizedText(server.serverInfo.applicationName!)?.text || undefined;
|
|
634
625
|
|
|
635
|
-
const
|
|
626
|
+
const prefix = `Client-${g_registeringClientCounter++}`;
|
|
627
|
+
const action = isOnline ? "registering" : "unregistering";
|
|
628
|
+
const ldsInfo = this.discoveryServerEndpointUrl;
|
|
629
|
+
const serverInfo = server.serverInfo.applicationUri!;
|
|
630
|
+
const clientName = `${prefix} for server ${serverInfo} to LDS ${ldsInfo} for ${action}`;
|
|
636
631
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
return outer_callback();
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
this._setState(theStatus);
|
|
647
|
-
|
|
648
|
-
if (this._registration_client) {
|
|
649
|
-
warningLog(
|
|
650
|
-
`Warning there is already a registering/un-registering task taking place: ${
|
|
651
|
-
RegisterServerManagerStatus[this.state]
|
|
652
|
-
} state`
|
|
653
|
-
);
|
|
654
|
-
}
|
|
632
|
+
const client = OPCUAClientBase.create({
|
|
633
|
+
clientName,
|
|
634
|
+
applicationName,
|
|
635
|
+
applicationUri: server.serverInfo.applicationUri!,
|
|
636
|
+
connectionStrategy: no_retry_connectivity_strategy,
|
|
637
|
+
clientCertificateManager: server.serverCertificateManager,
|
|
655
638
|
|
|
656
|
-
const options: OPCUAClientBaseOptions = {
|
|
657
639
|
securityMode: selectedEndpoint.securityMode,
|
|
658
640
|
securityPolicy: coerceSecurityPolicy(selectedEndpoint.securityPolicyUri),
|
|
659
641
|
serverCertificate: selectedEndpoint.serverCertificate,
|
|
660
|
-
|
|
661
|
-
clientCertificateManager: server.serverCertificateManager,
|
|
662
|
-
|
|
663
642
|
certificateFile: server.certificateFile,
|
|
664
643
|
privateKeyFile: server.privateKeyFile,
|
|
665
644
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
applicationUri: server.serverInfo.applicationUri!,
|
|
671
|
-
|
|
672
|
-
connectionStrategy: no_reconnect_connectivity_strategy,
|
|
645
|
+
}) as ClientBaseEx;
|
|
646
|
+
client.on("backoff", (nbRetry, delay) => {
|
|
647
|
+
debugLog(client.clientCertificateManager, "backoff trying to connect to the LDS has failed", nbRetry, delay);
|
|
648
|
+
});
|
|
673
649
|
|
|
674
|
-
|
|
675
|
-
};
|
|
650
|
+
this._registration_client = client;
|
|
676
651
|
|
|
677
|
-
|
|
652
|
+
debugLog("lds endpoint uri : ", selectedEndpoint.endpointUrl);
|
|
678
653
|
|
|
679
|
-
const
|
|
680
|
-
|
|
681
|
-
|
|
654
|
+
const state = isOnline ? "UnRegisterServer" : "RegisterServer";
|
|
655
|
+
try {
|
|
656
|
+
await client.connect(selectedEndpoint!.endpointUrl!);
|
|
682
657
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
client.connect(selectedEndpoint!.endpointUrl!, (err?: Error) => {
|
|
692
|
-
debugLog("establish_connection_with_lds => err = ", err);
|
|
693
|
-
if (err) {
|
|
694
|
-
debugLog("RegisterServerManager#_registerServer connection to client has failed");
|
|
695
|
-
debugLog(
|
|
696
|
-
"RegisterServerManager#_registerServer " +
|
|
697
|
-
"=> please check that you server certificate is trusted by the LDS"
|
|
698
|
-
);
|
|
699
|
-
warningLog(
|
|
700
|
-
"RegisterServer to the LDS has failed during secure connection " +
|
|
701
|
-
"=> please check that you server certificate is trusted by the LDS.",
|
|
702
|
-
"\nerr: " + err.message,
|
|
703
|
-
"\nLDS endpoint :",
|
|
704
|
-
selectedEndpoint!.endpointUrl!,
|
|
705
|
-
"\nsecurity mode :",
|
|
706
|
-
MessageSecurityMode[selectedEndpoint.securityMode],
|
|
707
|
-
"\nsecurity policy :",
|
|
708
|
-
coerceSecurityPolicy(selectedEndpoint.securityPolicyUri)
|
|
709
|
-
);
|
|
710
|
-
// xx debugLog(options);
|
|
711
|
-
client.disconnect(() => {
|
|
712
|
-
this._registration_client = null;
|
|
713
|
-
debugLog("RegisterServerManager#_registerServer client disconnected");
|
|
714
|
-
callback(/* intentionally no error propagation*/);
|
|
715
|
-
});
|
|
716
|
-
} else {
|
|
717
|
-
callback();
|
|
718
|
-
}
|
|
719
|
-
});
|
|
720
|
-
},
|
|
721
|
-
(callback: ErrorCallback) => {
|
|
722
|
-
if (!this._registration_client) {
|
|
723
|
-
callback();
|
|
724
|
-
return;
|
|
725
|
-
}
|
|
726
|
-
sendRegisterServerRequest(this.server!, client as ClientBaseEx, isOnline, (err?: Error | null) => {
|
|
727
|
-
callback(/* intentionally no error propagation*/);
|
|
728
|
-
});
|
|
729
|
-
},
|
|
730
|
-
// close_connection_with_lds
|
|
731
|
-
(callback: ErrorCallback) => {
|
|
732
|
-
if (!this._registration_client) {
|
|
733
|
-
callback();
|
|
734
|
-
return;
|
|
658
|
+
// Check state again after connection is established
|
|
659
|
+
if (this.getState() === expectedState) {
|
|
660
|
+
try {
|
|
661
|
+
await sendRegisterServerRequest(server, client as ClientBaseEx, isOnline)
|
|
662
|
+
} catch (err) {
|
|
663
|
+
if (this.getState() !== expectedState) {
|
|
664
|
+
warningLog(`${state} '${this.server!.serverInfo.applicationUri}' to the LDS has failed during secure connection to the LDS server`);
|
|
665
|
+
warningLog(chalk.red(" Error message:"), (err as Error).message); // Do not rethrow here, let the caller
|
|
735
666
|
}
|
|
736
|
-
client.disconnect(callback);
|
|
737
667
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
if (!this._registration_client) {
|
|
741
|
-
debugLog("RegisterServerManager#_registerServer end (isOnline", isOnline, ") has been interrupted");
|
|
742
|
-
outer_callback();
|
|
743
|
-
return;
|
|
744
|
-
}
|
|
745
|
-
this._registration_client.disconnect(() => {
|
|
746
|
-
debugLog("RegisterServerManager#_registerServer end (isOnline", isOnline, ")");
|
|
747
|
-
this._registration_client = null;
|
|
748
|
-
outer_callback(err!);
|
|
749
|
-
});
|
|
668
|
+
} else {
|
|
669
|
+
debugLog("RegisterServerManager#_registerServer: aborted ");
|
|
750
670
|
}
|
|
751
|
-
|
|
671
|
+
|
|
672
|
+
} catch (err) {
|
|
673
|
+
if (this.getState() !== expectedState) {
|
|
674
|
+
|
|
675
|
+
warningLog(`${state} '${this.server!.serverInfo.applicationUri}' cannot connect to LDS at endpoint ${client.clientName}, ${selectedEndpoint.endpointUrl} :`);
|
|
676
|
+
warningLog(chalk.red(" Error message:"), (err as Error).message);
|
|
677
|
+
}
|
|
678
|
+
} finally {
|
|
679
|
+
if (this._registration_client) {
|
|
680
|
+
const tmp = this._registration_client;
|
|
681
|
+
this._registration_client = null;
|
|
682
|
+
await tmp.disconnect();
|
|
683
|
+
}
|
|
684
|
+
}
|
|
752
685
|
}
|
|
753
686
|
|
|
754
|
-
|
|
687
|
+
/**
|
|
688
|
+
* Cancels any pending client connections.
|
|
689
|
+
* This is crucial for a clean shutdown.
|
|
690
|
+
* @private
|
|
691
|
+
*/
|
|
692
|
+
async #_cancel_pending_client_if_any(): Promise<void> {
|
|
755
693
|
debugLog("RegisterServerManager#_cancel_pending_client_if_any");
|
|
756
|
-
|
|
757
694
|
if (this._registration_client) {
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
695
|
+
const client = this._registration_client;
|
|
696
|
+
this._registration_client = null;
|
|
697
|
+
debugLog("RegisterServerManager#_cancel_pending_client_if_any "
|
|
698
|
+
+ "=> wee need to disconnect_registration_client");
|
|
699
|
+
try {
|
|
700
|
+
await client.disconnect();
|
|
701
|
+
} catch (err) {
|
|
702
|
+
warningLog("Error disconnecting registration client:", (err as Error).message);
|
|
703
|
+
}
|
|
704
|
+
await this.#_cancel_pending_client_if_any(); // Recursive call to ensure all are handled
|
|
764
705
|
} else {
|
|
765
706
|
debugLog("RegisterServerManager#_cancel_pending_client_if_any : done (nothing to do)");
|
|
766
|
-
callback();
|
|
767
707
|
}
|
|
768
708
|
}
|
|
769
709
|
}
|