node-opcua-server 2.164.2 → 2.165.1

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.
@@ -3,42 +3,39 @@
3
3
  * @module node-opcua-server
4
4
  */
5
5
  // tslint:disable:no-console
6
- import { EventEmitter } from "events";
7
- import net from "net";
8
- import { Server, Socket } from "net";
6
+
7
+ import { EventEmitter } from "node:events";
8
+ import net, { type Server, type Socket } from "node:net";
9
9
  import chalk from "chalk";
10
- import async from "async";
11
10
 
12
11
  import { assert } from "node-opcua-assert";
13
- import { OPCUACertificateManager } from "node-opcua-certificate-manager";
14
- import { Certificate, PrivateKey, makeSHA1Thumbprint, split_der } from "node-opcua-crypto/web";
12
+ import type { OPCUACertificateManager } from "node-opcua-certificate-manager";
13
+ import { type Certificate, makeSHA1Thumbprint, type PrivateKey, split_der } from "node-opcua-crypto/web";
15
14
  import { checkDebugFlag, make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
16
15
  import { getFullyQualifiedDomainName, resolveFullyQualifiedDomainName } from "node-opcua-hostname";
17
16
  import {
18
17
  fromURI,
18
+ type IServerSessionBase,
19
+ type Message,
19
20
  MessageSecurityMode,
20
21
  SecurityPolicy,
21
22
  ServerSecureChannelLayer,
22
- ServerSecureChannelParent,
23
- toURI,
24
- IServerSessionBase,
25
- Message
23
+ type ServerSecureChannelParent,
24
+ toURI
26
25
  } from "node-opcua-secure-channel";
27
- import { UserTokenType } from "node-opcua-service-endpoints";
28
- import { EndpointDescription } from "node-opcua-service-endpoints";
29
- import { ApplicationDescription } from "node-opcua-service-endpoints";
30
- import { UserTokenPolicyOptions } from "node-opcua-types";
31
- import { IHelloAckLimits } from "node-opcua-transport";
26
+ import { ApplicationDescription, EndpointDescription, UserTokenType } from "node-opcua-service-endpoints";
27
+ import type { IHelloAckLimits } from "node-opcua-transport";
28
+ import type { UserTokenPolicyOptions } from "node-opcua-types";
32
29
 
33
- import { IChannelData } from "./i_channel_data";
34
- import { ISocketData } from "./i_socket_data";
30
+ import type { IChannelData } from "./i_channel_data";
31
+ import type { ISocketData } from "./i_socket_data";
35
32
 
36
33
  const debugLog = make_debugLog(__filename);
37
34
  const errorLog = make_errorLog(__filename);
38
35
  const warningLog = make_warningLog(__filename);
39
36
  const doDebug = checkDebugFlag(__filename);
40
37
 
41
- const default_transportProfileUri = "http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary";
38
+ const UATCP_UASC_UABINARY = "http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary";
42
39
 
43
40
  function extractSocketData(socket: net.Socket, reason: string): ISocketData {
44
41
  const { bytesRead, bytesWritten, remoteAddress, remoteFamily, remotePort, localAddress, localPort } = socket;
@@ -57,14 +54,7 @@ function extractSocketData(socket: net.Socket, reason: string): ISocketData {
57
54
  }
58
55
 
59
56
  function extractChannelData(channel: ServerSecureChannelLayer): IChannelData {
60
- const {
61
- channelId,
62
- clientCertificate,
63
- securityMode,
64
- securityPolicy,
65
- timeout,
66
- transactionsCount
67
- } = channel;
57
+ const { channelId, clientCertificate, securityMode, securityPolicy, timeout, transactionsCount } = channel;
68
58
 
69
59
  const channelData: IChannelData = {
70
60
  channelId,
@@ -95,6 +85,7 @@ function dumpChannelInfo(channels: ServerSecureChannelLayer[]): void {
95
85
  console.log(" sessions = ", Object.keys(channel.sessionTokens).length);
96
86
  console.log(Object.values(channel.sessionTokens).map(d).join("\n"));
97
87
 
88
+ // biome-ignore lint/suspicious/noExplicitAny: accessing internal transport for debug dump
98
89
  const socket = (channel as any).transport?._socket;
99
90
  if (!socket) {
100
91
  console.log(" SOCKET IS CLOSED");
@@ -108,6 +99,7 @@ function dumpChannelInfo(channels: ServerSecureChannelLayer[]): void {
108
99
  }
109
100
 
110
101
  const emptyCertificate = Buffer.alloc(0);
102
+ // biome-ignore lint/suspicious/noExplicitAny: deliberate null→PrivateKey sentinel
111
103
  const emptyPrivateKey = null as any as PrivateKey;
112
104
 
113
105
  let OPCUAServerEndPointCounter = 0;
@@ -152,7 +144,7 @@ export interface OPCUAServerEndPointOptions {
152
144
 
153
145
  serverInfo: ApplicationDescription;
154
146
 
155
- objectFactory?: any;
147
+ objectFactory?: unknown;
156
148
 
157
149
  transportSettings?: IServerTransportSettings;
158
150
  }
@@ -167,10 +159,74 @@ export interface EndpointDescriptionParams {
167
159
  resourcePath?: string;
168
160
  alternateHostname?: string[];
169
161
  hostname: string;
162
+ /**
163
+ * Override the port used in the endpoint URL.
164
+ * When set, the endpoint URL uses this port instead of the
165
+ * server's listen port. The server does NOT listen on this port.
166
+ * Useful for Docker port-mapping, reverse proxies, and NAT.
167
+ */
168
+ advertisedPort?: number;
170
169
  securityPolicies: SecurityPolicy[];
171
170
  userTokenTypes: UserTokenType[];
172
171
  }
173
172
 
173
+ /**
174
+ * Per-URL security overrides for advertised endpoints.
175
+ *
176
+ * When `advertisedEndpoints` contains a config object, the endpoint
177
+ * descriptions generated for that URL use the overridden security
178
+ * settings instead of inheriting from the main endpoint.
179
+ *
180
+ * Any field that is omitted falls back to the main endpoint's value.
181
+ *
182
+ * @example
183
+ * ```ts
184
+ * advertisedEndpoints: [
185
+ * // Public: SignAndEncrypt only, no anonymous
186
+ * {
187
+ * url: "opc.tcp://public.example.com:4840",
188
+ * securityModes: [MessageSecurityMode.SignAndEncrypt],
189
+ * allowAnonymous: false
190
+ * },
191
+ * // Internal: inherits everything from main endpoint
192
+ * "opc.tcp://internal:48480"
193
+ * ]
194
+ * ```
195
+ */
196
+ export interface AdvertisedEndpointConfig {
197
+ /** The full endpoint URL, e.g. `"opc.tcp://public.example.com:4840"` */
198
+ url: string;
199
+ /** Override security modes (default: inherit from main endpoint) */
200
+ securityModes?: MessageSecurityMode[];
201
+ /** Override security policies (default: inherit from main endpoint) */
202
+ securityPolicies?: SecurityPolicy[];
203
+ /** Override anonymous access (default: inherit from main endpoint) */
204
+ allowAnonymous?: boolean;
205
+ /** Override user token types (default: inherit from main endpoint) */
206
+ userTokenTypes?: UserTokenType[];
207
+ }
208
+
209
+ /**
210
+ * An advertised endpoint entry — either a plain URL string (inherits
211
+ * all settings from the main endpoint) or a config object with
212
+ * per-URL security overrides.
213
+ */
214
+ export type AdvertisedEndpoint = string | AdvertisedEndpointConfig;
215
+
216
+ /**
217
+ * Normalize any `advertisedEndpoints` input into a uniform
218
+ * `AdvertisedEndpointConfig[]`.
219
+ *
220
+ * This coercion is done early so that all downstream code
221
+ * (endpoint generation, IP/hostname extraction) only deals
222
+ * with one type.
223
+ */
224
+ export function normalizeAdvertisedEndpoints(raw?: AdvertisedEndpoint | AdvertisedEndpoint[]): AdvertisedEndpointConfig[] {
225
+ if (!raw) return [];
226
+ const arr = Array.isArray(raw) ? raw : [raw];
227
+ return arr.map((entry) => (typeof entry === "string" ? { url: entry } : entry));
228
+ }
229
+
174
230
  export interface AddStandardEndpointDescriptionsParam {
175
231
  allowAnonymous?: boolean;
176
232
  disableDiscovery?: boolean;
@@ -183,15 +239,61 @@ export interface AddStandardEndpointDescriptionsParam {
183
239
  hostname?: string;
184
240
  securityPolicies?: SecurityPolicy[];
185
241
  userTokenTypes?: UserTokenType[];
242
+
243
+ /**
244
+ * Additional endpoint URL(s) to advertise.
245
+ *
246
+ * Use when the server is behind Docker port-mapping,
247
+ * a reverse proxy, or a NAT gateway.
248
+ *
249
+ * Each entry can be a plain URL string (inherits all security
250
+ * settings from the main endpoint) or an
251
+ * `AdvertisedEndpointConfig` object with per-URL overrides.
252
+ *
253
+ * The server still listens on `port` — these are purely
254
+ * advertised aliases.
255
+ *
256
+ * @example Simple string (inherits main settings)
257
+ * ```ts
258
+ * advertisedEndpoints: "opc.tcp://localhost:48481"
259
+ * ```
260
+ *
261
+ * @example Mixed array with per-URL security overrides
262
+ * ```ts
263
+ * advertisedEndpoints: [
264
+ * "opc.tcp://internal:48480",
265
+ * {
266
+ * url: "opc.tcp://public.example.com:4840",
267
+ * securityModes: [MessageSecurityMode.SignAndEncrypt],
268
+ * allowAnonymous: false
269
+ * }
270
+ * ]
271
+ * ```
272
+ */
273
+ advertisedEndpoints?: AdvertisedEndpoint | AdvertisedEndpoint[];
274
+ }
275
+
276
+ /**
277
+ * Parse an `opc.tcp://hostname:port` URL and extract hostname and port.
278
+ * @internal
279
+ */
280
+ export function parseOpcTcpUrl(url: string): { hostname: string; port: number } {
281
+ // URL class doesn't understand opc.tcp://, so swap to http://
282
+ const httpUrl = url.replace(/^opc\.tcp:\/\//i, "http://");
283
+ const parsed = new URL(httpUrl);
284
+ return {
285
+ hostname: parsed.hostname,
286
+ port: parsed.port ? Number.parseInt(parsed.port, 10) : 4840
287
+ };
186
288
  }
187
289
 
188
290
  function getUniqueName(name: string, collection: { [key: string]: number }) {
189
291
  if (collection[name]) {
190
292
  let counter = 0;
191
- while (collection[name + "_" + counter.toString()]) {
293
+ while (collection[`${name}_${counter.toString()}`]) {
192
294
  counter++;
193
295
  }
194
- name = name + "_" + counter.toString();
296
+ name = `${name}_${counter.toString()}`;
195
297
  collection[name] = 1;
196
298
  return name;
197
299
  } else {
@@ -224,12 +326,12 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
224
326
  public transactionsCountOldChannels: number;
225
327
  public securityTokenCountOldChannels: number;
226
328
  public serverInfo: ApplicationDescription;
227
- public objectFactory: any;
329
+ public objectFactory: unknown;
228
330
 
229
331
  public _on_new_channel?: (channel: ServerSecureChannelLayer) => void;
230
332
  public _on_close_channel?: (channel: ServerSecureChannelLayer) => void;
231
- public _on_connectionRefused?: (socketData: any) => void;
232
- public _on_openSecureChannelFailure?: (socketData: any, channelData: any) => void;
333
+ public _on_connectionRefused?: (socketData: ISocketData) => void;
334
+ public _on_openSecureChannelFailure?: (socketData: ISocketData, channelData: IChannelData) => void;
233
335
 
234
336
  private _certificateChain: Certificate;
235
337
  private _privateKey: PrivateKey;
@@ -307,7 +409,6 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
307
409
  }
308
410
 
309
411
  public toString(): string {
310
-
311
412
  const txt =
312
413
  " end point" +
313
414
  this._counter +
@@ -316,7 +417,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
316
417
  " l = " +
317
418
  this._endpoints.length +
318
419
  " " +
319
- makeSHA1Thumbprint(this.getCertificateChain()).toString("hex")
420
+ makeSHA1Thumbprint(this.getCertificateChain()).toString("hex");
320
421
  return txt;
321
422
  }
322
423
 
@@ -392,7 +493,8 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
392
493
  assert(resourcePath.length === 0 || resourcePath.charAt(0) === "/", "resourcePath should start with /");
393
494
 
394
495
  const hostname = options.hostname || getFullyQualifiedDomainName();
395
- const endpointUrl = `opc.tcp://${hostname}:${this.port}${resourcePath}`;
496
+ const effectivePort = options.advertisedPort ?? this.port;
497
+ const endpointUrl = `opc.tcp://${hostname}:${effectivePort}${resourcePath}`;
396
498
 
397
499
  const endpoint_desc = this.getEndpointDescription(securityMode, securityPolicy, endpointUrl);
398
500
 
@@ -421,6 +523,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
421
523
  restricted: !!options.restricted,
422
524
  securityPolicies: options.securityPolicies || [],
423
525
 
526
+ advertisedPort: options.advertisedPort,
424
527
  userTokenTypes
425
528
  },
426
529
  this
@@ -431,7 +534,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
431
534
  public addRestrictedEndpointDescription(options: EndpointDescriptionParams): void {
432
535
  options = { ...options };
433
536
  options.restricted = true;
434
- return this.addEndpointDescription(MessageSecurityMode.None, SecurityPolicy.None, options);
537
+ this.addEndpointDescription(MessageSecurityMode.None, SecurityPolicy.None, options);
435
538
  }
436
539
 
437
540
  public addStandardEndpointDescriptions(options?: AddStandardEndpointDescriptionsParam): void {
@@ -487,6 +590,65 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
487
590
  }
488
591
  }
489
592
  }
593
+
594
+ // ── Advertised endpoints (virtual — no TCP listener) ──────
595
+ // Normalize to AdvertisedEndpointConfig[] so downstream code
596
+ // only deals with one type.
597
+ const advertisedList = normalizeAdvertisedEndpoints(options.advertisedEndpoints);
598
+
599
+ // Main endpoint defaults (guaranteed non-null — assigned above)
600
+ const mainSecurityModes = options.securityModes || defaultSecurityModes;
601
+ const mainSecurityPolicies = options.securityPolicies || defaultSecurityPolicies;
602
+ const mainUserTokenTypes = options.userTokenTypes || defaultUserTokenTypes;
603
+
604
+ for (const config of advertisedList) {
605
+ const { hostname: advHostname, port: advPort } = parseOpcTcpUrl(config.url);
606
+ // Skip if this hostname+port combo was already covered
607
+ // by the regular hostname loop (same hostname, same port)
608
+ if (hostnames.some((h) => h.toLowerCase() === advHostname.toLowerCase()) && advPort === this.port) {
609
+ continue;
610
+ }
611
+
612
+ // Per-URL security overrides — fall back to main settings
613
+ const entrySecurityModes = config.securityModes ?? mainSecurityModes;
614
+ const entrySecurityPolicies = config.securityPolicies ?? mainSecurityPolicies;
615
+ let entryUserTokenTypes = config.userTokenTypes ?? mainUserTokenTypes;
616
+
617
+ // Handle allowAnonymous override: if explicitly false,
618
+ // filter out Anonymous even if the main config allows it
619
+ if (config.allowAnonymous === false) {
620
+ entryUserTokenTypes = entryUserTokenTypes.filter((t) => t !== UserTokenType.Anonymous);
621
+ }
622
+
623
+ const optionsE: EndpointDescriptionParams = {
624
+ hostname: advHostname,
625
+ advertisedPort: advPort,
626
+ securityPolicies: entrySecurityPolicies,
627
+ userTokenTypes: entryUserTokenTypes,
628
+ allowUnsecurePassword: options.allowUnsecurePassword,
629
+ alternateHostname: options.alternateHostname,
630
+ resourcePath: options.resourcePath
631
+ };
632
+
633
+ if (entrySecurityModes.indexOf(MessageSecurityMode.None) >= 0) {
634
+ this.addEndpointDescription(MessageSecurityMode.None, SecurityPolicy.None, optionsE);
635
+ } else {
636
+ if (!options.disableDiscovery) {
637
+ this.addRestrictedEndpointDescription(optionsE);
638
+ }
639
+ }
640
+ for (const securityMode of entrySecurityModes) {
641
+ if (securityMode === MessageSecurityMode.None) {
642
+ continue;
643
+ }
644
+ for (const securityPolicy of entrySecurityPolicies) {
645
+ if (securityPolicy === SecurityPolicy.None) {
646
+ continue;
647
+ }
648
+ this.addEndpointDescription(securityMode, securityPolicy, optionsE);
649
+ }
650
+ }
651
+ }
490
652
  }
491
653
 
492
654
  /**
@@ -502,14 +664,19 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
502
664
  assert(typeof callback === "function");
503
665
  assert(!this._started, "OPCUAServerEndPoint is already listening");
504
666
 
667
+ if (!this._server) {
668
+ callback(new Error("Server is not initialized"));
669
+ return;
670
+ }
671
+
505
672
  this._listen_callback = callback;
506
673
 
507
- this._server!.on("error", (err: Error) => {
508
- debugLog(chalk.red.bold(" error") + " port = " + this.port, err);
674
+ this._server.on("error", (err: Error) => {
675
+ debugLog(`${chalk.red.bold(" error")} port = ${this.port}`, err);
509
676
  this._started = false;
510
677
  this._end_listen(err);
511
678
  });
512
- this._server!.on("listening", () => {
679
+ this._server.on("listening", () => {
513
680
  debugLog("server is listening");
514
681
  });
515
682
 
@@ -518,7 +685,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
518
685
  host: this.host
519
686
  };
520
687
 
521
- this._server!.listen(
688
+ this._server.listen(
522
689
  listenOptions,
523
690
  /*"::",*/ (err?: Error) => {
524
691
  // 'listening' listener
@@ -526,8 +693,8 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
526
693
  assert(!err, " cannot listen to port ");
527
694
  this._started = true;
528
695
  if (!this.port) {
529
- const add = this._server!.address()!;
530
- this.port = typeof add !== "string" ? add.port : this.port;
696
+ const add = this._server?.address();
697
+ this.port = typeof add !== "string" ? add?.port || 0 : this.port;
531
698
  }
532
699
  this._end_listen();
533
700
  }
@@ -536,8 +703,10 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
536
703
 
537
704
  public killClientSockets(callback: (err?: Error) => void): void {
538
705
  for (const channel of this.getChannels()) {
539
- const hacked_channel = channel as any;
540
- if (hacked_channel.transport && hacked_channel.transport._socket) {
706
+ const hacked_channel = channel as unknown as {
707
+ transport: { _socket: { destroy: () => void; emit: (event: string, err: Error) => void } };
708
+ };
709
+ if (hacked_channel.transport?._socket) {
541
710
  // hacked_channel.transport._socket.close();
542
711
  hacked_channel.transport._socket.destroy();
543
712
  hacked_channel.transport._socket.emit("error", new Error("EPIPE"));
@@ -547,8 +716,9 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
547
716
  }
548
717
 
549
718
  public suspendConnection(callback: (err?: Error) => void): void {
550
- if (!this._started) {
551
- return callback(new Error("Connection already suspended !!"));
719
+ if (!this._started || !this._server) {
720
+ callback(new Error("Connection already suspended !!"));
721
+ return;
552
722
  }
553
723
 
554
724
  // Stops the server from accepting new connections and keeps existing connections.
@@ -557,9 +727,9 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
557
727
  // The optional callback will be called once the 'close' event occurs.
558
728
  // Unlike that event, it will be called with an Error as its only argument
559
729
  // if the server was not open when it was closed.
560
- this._server!.close(() => {
730
+ this._server.close(() => {
561
731
  this._started = false;
562
- debugLog("Connection has been closed !" + this.port);
732
+ debugLog(`Connection has been closed !${this.port}`);
563
733
  });
564
734
  this._started = false;
565
735
  callback();
@@ -585,20 +755,30 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
585
755
  this.suspendConnection(() => {
586
756
  // shutdown all opened channels ...
587
757
  const _channels = Object.values(this._channels);
588
- async.each(
589
- _channels,
590
- (channel: ServerSecureChannelLayer, callback1: (err?: Error) => void) => {
591
- this.shutdown_channel(channel, callback1);
592
- },
593
- (err?: Error | null) => {
758
+ const promises = _channels.map(
759
+ (channel) =>
760
+ new Promise<void>((resolve, reject) => {
761
+ this.shutdown_channel(channel, (err?: Error) => {
762
+ if (err) {
763
+ reject(err);
764
+ } else {
765
+ resolve();
766
+ }
767
+ });
768
+ })
769
+ );
770
+ Promise.all(promises)
771
+ .then(() => {
594
772
  /* c8 ignore next */
595
773
  if (!(Object.keys(this._channels).length === 0)) {
596
774
  errorLog(" Bad !");
597
775
  }
598
776
  assert(Object.keys(this._channels).length === 0, "channel must have unregistered themselves");
599
- callback(err || undefined);
600
- }
601
- );
777
+ callback();
778
+ })
779
+ .catch((err) => {
780
+ callback(err);
781
+ });
602
782
  });
603
783
  } else {
604
784
  callback();
@@ -657,10 +837,10 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
657
837
  }
658
838
 
659
839
  private _dump_statistics() {
660
- this._server!.getConnections((err: Error | null, count: number) => {
840
+ this._server?.getConnections((_err: Error | null, count: number) => {
661
841
  debugLog(chalk.cyan("CONCURRENT CONNECTION = "), count);
662
842
  });
663
- debugLog(chalk.cyan("MAX CONNECTIONS = "), this._server!.maxConnections);
843
+ debugLog(chalk.cyan("MAX CONNECTIONS = "), this._server?.maxConnections);
664
844
  }
665
845
 
666
846
  private _setup_server() {
@@ -672,11 +852,11 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
672
852
 
673
853
  this._listen_callback = undefined;
674
854
  this._server
675
- .on("connection", (socket: NodeJS.Socket) => {
855
+ .on("connection", (socket: Socket) => {
676
856
  // c8 ignore next
677
857
  if (doDebug) {
678
858
  this._dump_statistics();
679
- debugLog("server connected with : " + (socket as any).remoteAddress + ":" + (socket as any).remotePort);
859
+ debugLog(`server connected with : ${socket.remoteAddress}:${socket.remotePort}`);
680
860
  }
681
861
  })
682
862
  .on("close", () => {
@@ -710,7 +890,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
710
890
  "The maximum number of connection has been reached - Connection is refused"
711
891
  )
712
892
  );
713
- const reason = "maxConnections reached (" + this.maxConnections + ")";
893
+ const reason = `maxConnections reached (${this.maxConnections})`;
714
894
  const socketData = extractSocketData(socket, reason);
715
895
  this.emit("connectionRefused", socketData);
716
896
 
@@ -725,7 +905,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
725
905
  " nbConnections ",
726
906
  nbConnections,
727
907
  " self._server.maxConnections",
728
- this._server!.maxConnections,
908
+ this._server?.maxConnections,
729
909
  this.maxConnections
730
910
  );
731
911
  deny_connection();
@@ -741,7 +921,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
741
921
  timeout: this.timeout,
742
922
  adjustTransportLimits: this.transportSettings?.adjustTransportLimits
743
923
  });
744
-
924
+
745
925
  debugLog("channel Timeout = >", channel.timeout);
746
926
 
747
927
  socket.resume();
@@ -752,7 +932,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
752
932
  this._un_pre_registerChannel(channel);
753
933
  debugLog(chalk.yellow.bold("Channel#init done"), err);
754
934
  if (err) {
755
- const reason = "openSecureChannel has Failed " + err.message;
935
+ const reason = `openSecureChannel has Failed ${err.message}`;
756
936
  const socketData = extractSocketData(socket, reason);
757
937
  const channelData = extractChannelData(channel);
758
938
  this.emit("openSecureChannelFailure", socketData, channelData);
@@ -804,7 +984,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
804
984
  delete this._channels[channel.hashKey];
805
985
  const channelPriv = <ServerSecureChannelLayerPriv>channel;
806
986
  if (typeof channelPriv._unpreregisterChannelEvent === "function") {
807
- channel.removeListener("abort", channelPriv._unpreregisterChannelEvent!);
987
+ channel.removeListener("abort", channelPriv._unpreregisterChannelEvent);
808
988
  channelPriv._unpreregisterChannelEvent = undefined;
809
989
  }
810
990
  }
@@ -870,8 +1050,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
870
1050
 
871
1051
  private _end_listen(err?: Error) {
872
1052
  if (!this._listen_callback) return;
873
- assert(typeof this._listen_callback === "function");
874
- this._listen_callback!(err);
1053
+ this._listen_callback(err);
875
1054
  this._listen_callback = undefined;
876
1055
  }
877
1056
 
@@ -900,17 +1079,18 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
900
1079
 
901
1080
  if (nbConnections >= this.maxConnections) {
902
1081
  // c8 ignore next
903
- errorLog(chalk.bgRed.white("PREVENTING DDOS ATTACK => maxConnection =" + this.maxConnections));
1082
+ errorLog(chalk.bgRed.white(`PREVENTING DDOS ATTACK => maxConnection =${this.maxConnections}`));
904
1083
 
905
1084
  const unused_channels: ServerSecureChannelLayer[] = this.getChannels().filter((channel1: ServerSecureChannelLayer) => {
906
1085
  return !channel1.hasSession;
907
1086
  });
908
1087
  if (unused_channels.length === 0) {
909
- doDebug && console.log(
910
- this.getChannels()
911
- .map(({ status, isOpened, hasSession }) => `${status} ${isOpened} ${hasSession}\n`)
912
- .join(" ")
913
- );
1088
+ doDebug &&
1089
+ console.log(
1090
+ this.getChannels()
1091
+ .map(({ status, isOpened, hasSession }) => `${status} ${isOpened} ${hasSession}\n`)
1092
+ .join(" ")
1093
+ );
914
1094
  // all channels are in used , we cannot get any
915
1095
  errorLog(`All channels are in used ! we cannot cancel any ${this.getChannels().length}`);
916
1096
  // c8 ignore next
@@ -990,6 +1170,12 @@ interface MakeEndpointDescriptionOptions {
990
1170
  securityPolicies: SecurityPolicy[];
991
1171
 
992
1172
  userTokenTypes: UserTokenType[];
1173
+ /**
1174
+ * Override the port used in the dynamic endpointUrl getter.
1175
+ * When set, the endpoint URL advertises this port instead of
1176
+ * the parent's listen port.
1177
+ */
1178
+ advertisedPort?: number;
993
1179
  /**
994
1180
  *
995
1181
  * default value: false;
@@ -1031,7 +1217,6 @@ function estimateSecurityLevel(securityMode: MessageSecurityMode, securityPolicy
1031
1217
  return 7 + offset;
1032
1218
 
1033
1219
  default:
1034
- case SecurityPolicy.None:
1035
1220
  return 1;
1036
1221
  }
1037
1222
  }
@@ -1052,7 +1237,7 @@ function _makeEndpointDescription(options: MakeEndpointDescriptionOptions, paren
1052
1237
  options.securityLevel === undefined
1053
1238
  ? estimateSecurityLevel(options.securityMode, options.securityPolicy)
1054
1239
  : options.securityLevel;
1055
- assert(isFinite(options.securityLevel), "expecting a valid securityLevel");
1240
+ assert(Number.isFinite(options.securityLevel), "expecting a valid securityLevel");
1056
1241
 
1057
1242
  const securityPolicyUri = toURI(options.securityPolicy);
1058
1243
 
@@ -1175,14 +1360,15 @@ function _makeEndpointDescription(options: MakeEndpointDescriptionOptions, paren
1175
1360
  userIdentityTokens,
1176
1361
 
1177
1362
  securityLevel: options.securityLevel,
1178
- transportProfileUri: default_transportProfileUri
1363
+ transportProfileUri: UATCP_UASC_UABINARY
1179
1364
  }) as EndpointDescriptionEx;
1180
1365
  endpoint._parent = parent;
1181
1366
 
1182
1367
  // endpointUrl is dynamic as port number may be adjusted
1183
1368
  // when the tcp socket start listening
1369
+ // biome-ignore lint/suspicious/noExplicitAny: __defineGetter__ not in standard typings
1184
1370
  (endpoint as any).__defineGetter__("endpointUrl", () => {
1185
- const port = endpoint._parent.port;
1371
+ const port = options.advertisedPort ?? endpoint._parent.port;
1186
1372
  const resourcePath = options.resourcePath || "";
1187
1373
  const hostname = options.hostname;
1188
1374
  const endpointUrl = `opc.tcp://${hostname}:${port}${resourcePath}`;
@@ -1212,7 +1398,7 @@ function matching_endpoint(
1212
1398
  ): boolean {
1213
1399
  assert(endpoint instanceof EndpointDescription);
1214
1400
  const endpoint_securityPolicy = fromURI(endpoint.securityPolicyUri);
1215
- if (endpointUrl && endpoint.endpointUrl! !== endpointUrl) {
1401
+ if (endpointUrl && endpoint.endpointUrl !== endpointUrl) {
1216
1402
  return false;
1217
1403
  }
1218
1404
  return endpoint.securityMode === securityMode && endpoint_securityPolicy === securityPolicy;
@@ -1220,9 +1406,7 @@ function matching_endpoint(
1220
1406
 
1221
1407
  const defaultSecurityModes = [MessageSecurityMode.None, MessageSecurityMode.Sign, MessageSecurityMode.SignAndEncrypt];
1222
1408
 
1223
-
1224
1409
  const defaultSecurityPolicies = [
1225
-
1226
1410
  // now deprecated Basic128Rs15 shall be disabled by default
1227
1411
  // see https://profiles.opcfoundation.org/profile/1532
1228
1412
  // SecurityPolicy.Basic128Rsa15,
@@ -1230,10 +1414,10 @@ const defaultSecurityPolicies = [
1230
1414
  // now deprecated Basic256 shall be disabled by default
1231
1415
  // see https://profiles.opcfoundation.org/profile/2062
1232
1416
  // SecurityPolicy.Basic256,
1233
-
1417
+
1234
1418
  // xx UNUSED!! SecurityPolicy.Basic192Rsa15,
1235
1419
  // xx UNUSED!! SecurityPolicy.Basic256Rsa15,
1236
-
1420
+
1237
1421
  SecurityPolicy.Basic256Sha256,
1238
1422
  SecurityPolicy.Aes128_Sha256_RsaOaep,
1239
1423
  SecurityPolicy.Aes256_Sha256_RsaPss