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.
Files changed (54) hide show
  1. package/dist/addressSpace_accessor.js +1 -0
  2. package/dist/addressSpace_accessor.js.map +1 -1
  3. package/dist/base_server.js +7 -1
  4. package/dist/base_server.js.map +1 -1
  5. package/dist/factory.js +1 -0
  6. package/dist/factory.js.map +1 -1
  7. package/dist/history_server_capabilities.js +14 -0
  8. package/dist/history_server_capabilities.js.map +1 -1
  9. package/dist/i_register_server_manager.d.ts +63 -3
  10. package/dist/i_register_server_manager.js +47 -0
  11. package/dist/i_register_server_manager.js.map +1 -1
  12. package/dist/i_server_side_publish_engine.js +4 -0
  13. package/dist/i_server_side_publish_engine.js.map +1 -1
  14. package/dist/index.d.ts +22 -0
  15. package/dist/index.js +22 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/monitored_item.d.ts +3 -6
  18. package/dist/monitored_item.js +44 -17
  19. package/dist/monitored_item.js.map +1 -1
  20. package/dist/opcua_server.js +44 -26
  21. package/dist/opcua_server.js.map +1 -1
  22. package/dist/queue.js +2 -1
  23. package/dist/queue.js.map +1 -1
  24. package/dist/register_server_manager.d.ts +14 -26
  25. package/dist/register_server_manager.js +282 -293
  26. package/dist/register_server_manager.js.map +1 -1
  27. package/dist/register_server_manager_hidden.d.ts +4 -3
  28. package/dist/register_server_manager_hidden.js +7 -5
  29. package/dist/register_server_manager_hidden.js.map +1 -1
  30. package/dist/register_server_manager_mdns_only.d.ts +5 -3
  31. package/dist/register_server_manager_mdns_only.js +25 -14
  32. package/dist/register_server_manager_mdns_only.js.map +1 -1
  33. package/dist/server_capabilities.js +33 -0
  34. package/dist/server_capabilities.js.map +1 -1
  35. package/dist/server_end_point.js +29 -3
  36. package/dist/server_end_point.js.map +1 -1
  37. package/dist/server_engine.js +20 -3
  38. package/dist/server_engine.js.map +1 -1
  39. package/dist/server_publish_engine.js +95 -98
  40. package/dist/server_publish_engine.js.map +1 -1
  41. package/dist/server_session.js +33 -5
  42. package/dist/server_session.js.map +1 -1
  43. package/dist/server_subscription.js +70 -15
  44. package/dist/server_subscription.js.map +1 -1
  45. package/dist/user_manager.js +1 -0
  46. package/dist/user_manager.js.map +1 -1
  47. package/package.json +49 -47
  48. package/source/i_register_server_manager.ts +66 -5
  49. package/source/index.ts +22 -0
  50. package/source/monitored_item.ts +28 -19
  51. package/source/opcua_server.ts +33 -23
  52. package/source/register_server_manager.ts +294 -354
  53. package/source/register_server_manager_hidden.ts +6 -5
  54. 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 { ErrorCallback, UAString } from "node-opcua-basic-types";
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(__filename);
33
- const debugLog = make_debugLog(__filename);
34
- const warningLog = make_warningLog(__filename);
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 no_reconnect_connectivity_strategy = {
188
- initialDelay: 2000,
189
- maxDelay: 50000,
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, callback: ErrorCallback) {
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
- client.performMessageTransaction(request, (err: Error | null, response?: RegisterServer2Response) => {
212
- if (!err) {
213
- // RegisterServerResponse
214
- debugLog("RegisterServerManager#_registerServer sendRegisterServer2Request has succeeded (isOnline", isOnline, ")");
215
- assert(response instanceof RegisterServer2Response);
216
- callback(err!);
217
- } else {
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
- assert(response1 instanceof RegisterServerResponse);
230
- } else {
231
- debugLog(
232
- "RegisterServerManager#_registerServer sendRegisterServerRequest " + "has failed (isOnline",
233
- isOnline,
234
- ")"
235
- );
223
+ return resolve();
236
224
  }
237
- callback(err1!);
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
- * - the initial registration of a server
267
- * - the regular registration renewal (every 8 minutes or so ...)
268
- * - dealing with cases where LDS is not up and running when server starts.
269
- * ( in this case the connection will be continuously attempted using the infinite
270
- * back-off strategy
271
- * - the un-registration of the server ( during shutdown for instance)
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._setState(RegisterServerManagerStatus.INACTIVE);
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
- public _emitEvent(eventName: string): void {
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
- public _setState(status: RegisterServerManagerStatus): void {
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
- public start(callback: ErrorCallback): void {
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
- return callback(new Error("RegisterServer process already started")); // already started
352
+ throw new Error("RegisterServer process already started: " + RegisterServerManagerStatus[this.state]);
352
353
  }
353
-
354
354
  this.discoveryServerEndpointUrl = resolveFullyQualifiedDomainName(this.discoveryServerEndpointUrl);
355
355
 
356
- // perform initial registration + automatic renewal
357
- this._establish_initial_connection((err?: Error | null) => {
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
- this._registerServer(true, (err1?: Error | null) => {
368
- if (this.state !== RegisterServerManagerStatus.REGISTERING) {
369
- debugLog("RegisterServerManager#start )=> Registration has been cancelled");
370
- return callback(new Error("Registration has been cancelled"));
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 (err1) {
374
- warningLog(
375
- "RegisterServerManager#start - registering server has failed ! \n" +
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
- callback();
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
- private _establish_initial_connection(outer_callback: ErrorCallback) {
391
- /* istanbul ignore next */
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
- throw new Error("Internal Error");
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.INACTIVE);
400
- this._setState(RegisterServerManagerStatus.INITIALIZING);
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 registrationClient = OPCUAClientBase.create({
411
- clientName: prefix + " Registering Client for Server " + this.server.serverInfo.applicationUri!,
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
- clientCertificateManager: this.server.serverCertificateManager,
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
- warningLog(
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._emitEvent("serverRegistrationPending");
471
+ this.#_emitEvent("serverRegistrationPending");
440
472
  });
441
473
 
442
- async.series(
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
- if (this.state !== RegisterServerManagerStatus.INITIALIZING) {
498
- debugLog(
499
- "RegisterServerManager#_establish_initial_connection has been interrupted ",
500
- RegisterServerManagerStatus[this.state]
501
- );
502
- this._setState(RegisterServerManagerStatus.INACTIVE);
503
- if (this._registration_client) {
504
- this._registration_client.disconnect((err2?: Error) => {
505
- this._registration_client = null;
506
- outer_callback(new Error("Initialization has been canceled"));
507
- });
508
- } else {
509
- outer_callback(new Error("Initialization has been canceled"));
510
- }
511
- return;
512
- }
513
- if (err) {
514
- this._setState(RegisterServerManagerStatus.INACTIVE);
515
- if (this._registration_client) {
516
- this._registration_client.disconnect((err1?: Error) => {
517
- this._registration_client = null;
518
- debugLog("#######", !!err1);
519
- outer_callback(err);
520
- });
521
- return;
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
- public _trigger_next(): void {
519
+ #_trigger_next(): void {
530
520
  assert(!this._registrationTimerId);
531
521
  assert(this.state === RegisterServerManagerStatus.WAITING);
532
- // from spec 1.04 part 4:
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
- this._registerServer(true, (err?: Error | null) => {
565
- if (
566
- this.state !== RegisterServerManagerStatus.INACTIVE &&
567
- this.state !== RegisterServerManagerStatus.UNREGISTERING
568
- ) {
569
- debugLog("RegisterServerManager#_trigger_next : renewed !", err);
570
- this._setState(RegisterServerManagerStatus.WAITING);
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(callback: ErrorCallback): void {
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
- if (!this.selectedEndpoint || this.state === RegisterServerManagerStatus.INACTIVE) {
591
- this.state = RegisterServerManagerStatus.INACTIVE;
592
- assert(this._registrationTimerId === null);
593
- return callback();
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
- this._registerServer(false, () => {
596
- this._setState(RegisterServerManagerStatus.INACTIVE);
597
- this._emitEvent("serverUnregistered");
598
- callback();
599
- });
600
- });
591
+ }
592
+
593
+ // Final state transition to INACTIVE
594
+ this.#_setState(RegisterServerManagerStatus.DISPOSING);
601
595
  }
602
596
 
603
597
  /**
604
- * @param isOnline
605
- * @param outer_callback
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
- public _registerServer(isOnline: boolean, outer_callback: ErrorCallback): void {
609
- assert(typeof outer_callback === "function");
610
-
611
- debugLog(
612
- "RegisterServerManager#_registerServer isOnline:",
613
- isOnline,
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
- assert(this.selectedEndpoint, "must have a selected endpoint => please call _establish_initial_connection");
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 : cannot register server - no endpoint available");
628
- return outer_callback(new Error("Cannot registerServer"));
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 theStatus = isOnline ? RegisterServerManagerStatus.REGISTERING : RegisterServerManagerStatus.UNREGISTERING;
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
- if (theStatus === this.state) {
638
- warningLog(`Warning the server is already in the ${RegisterServerManagerStatus[theStatus]} state`);
639
- return outer_callback();
640
- }
641
- if (!(this.state === RegisterServerManagerStatus.INITIALIZING || this.state === RegisterServerManagerStatus.WAITING)) {
642
- warningLog("Warning : cannot register server - wrong state ", RegisterServerManagerStatus[this.state]);
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
- // xx clientName: server.serverInfo.applicationUri!,
667
-
668
- applicationName,
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
- clientName: "server client to LDS " + RegisterServerManagerStatus[theStatus]
675
- };
650
+ this._registration_client = client;
676
651
 
677
- const client = OPCUAClientBase.create(options) as ClientBaseEx;
652
+ debugLog("lds endpoint uri : ", selectedEndpoint.endpointUrl);
678
653
 
679
- const tmp = this._serverEndpoints;
680
- client._serverEndpoints = tmp;
681
- this._registration_client = client;
654
+ const state = isOnline ? "UnRegisterServer" : "RegisterServer";
655
+ try {
656
+ await client.connect(selectedEndpoint!.endpointUrl!);
682
657
 
683
- debugLog(" lds endpoint uri : ", selectedEndpoint.endpointUrl);
684
- debugLog(" securityMode : ", MessageSecurityMode[selectedEndpoint.securityMode]);
685
- debugLog(" securityPolicy : ", selectedEndpoint.securityPolicyUri);
686
-
687
- async.series(
688
- [
689
- // establish_connection_with_lds
690
- (callback: ErrorCallback) => {
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
- (err?: Error | null) => {
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
- private _cancel_pending_client_if_any(callback: () => void) {
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
- debugLog("RegisterServerManager#_cancel_pending_client_if_any " + "=> wee need to disconnect _registration_client");
759
-
760
- this._registration_client.disconnect(() => {
761
- this._registration_client = null;
762
- this._cancel_pending_client_if_any(callback);
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
  }