node-opcua-server 2.164.2 → 2.165.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.
@@ -2,51 +2,45 @@
2
2
  * @module node-opcua-server
3
3
  */
4
4
  // tslint:disable:no-console
5
- import fs from "fs";
6
- import path from "path";
7
- import os from "os";
8
- import { types } from "util";
5
+
6
+ import fs from "node:fs";
7
+ import os from "node:os";
8
+ import path from "node:path";
9
+ import { isIP } from "node:net";
10
+ import { withLock } from "@ster5/global-mutex";
9
11
  import async from "async";
10
12
  import chalk from "chalk";
11
13
  import { assert } from "node-opcua-assert";
12
- import { withLock } from "@ster5/global-mutex";
13
-
14
- import {
15
- getDefaultCertificateManager,
16
- ICertificateManager,
17
- makeSubject,
18
- OPCUACertificateManager
19
- } from "node-opcua-certificate-manager";
20
- import { IOPCUASecureObjectOptions, makeApplicationUrn, OPCUASecureObject } from "node-opcua-common";
21
- import { coerceLocalizedText, LocalizedText } from "node-opcua-data-model";
14
+ import { getDefaultCertificateManager, makeSubject, type OPCUACertificateManager } from "node-opcua-certificate-manager";
15
+ import { performCertificateSanityCheck } from "node-opcua-client";
16
+ import { type IOPCUASecureObjectOptions, makeApplicationUrn, OPCUASecureObject } from "node-opcua-common";
17
+ import { exploreCertificate } from "node-opcua-crypto/web";
18
+ import { coerceLocalizedText } from "node-opcua-data-model";
22
19
  import { installPeriodicClockAdjustment, uninstallPeriodicClockAdjustment } from "node-opcua-date-time";
23
- import { checkDebugFlag, make_debugLog, make_errorLog } from "node-opcua-debug";
24
- import { displayTraceFromThisProjectOnly } from "node-opcua-debug";
20
+ import { checkDebugFlag, displayTraceFromThisProjectOnly, make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
25
21
  import {
26
22
  extractFullyQualifiedDomainName,
27
23
  getFullyQualifiedDomainName,
28
24
  getHostname,
25
+ getIpAddresses,
26
+ ipv4ToHex,
29
27
  resolveFullyQualifiedDomainName
30
28
  } from "node-opcua-hostname";
31
- import { Message, Response, ServerSecureChannelLayer, ServerSecureChannelParent } from "node-opcua-secure-channel";
29
+ import type { Message, Response, ServerSecureChannelLayer } from "node-opcua-secure-channel";
32
30
  import { FindServersRequest, FindServersResponse } from "node-opcua-service-discovery";
33
- import { ApplicationType, GetEndpointsResponse } from "node-opcua-service-endpoints";
34
- import { ApplicationDescription } from "node-opcua-service-endpoints";
31
+ import { ApplicationDescription, ApplicationType, GetEndpointsResponse } from "node-opcua-service-endpoints";
35
32
  import { ServiceFault } from "node-opcua-service-secure-channel";
36
- import { StatusCode, StatusCodes } from "node-opcua-status-code";
37
- import { ApplicationDescriptionOptions } from "node-opcua-types";
38
- import { EndpointDescription, GetEndpointsRequest } from "node-opcua-types";
39
- import { matchUri, checkFileExistsAndIsNotEmpty } from "node-opcua-utils";
40
-
41
- import { performCertificateSanityCheck } from "node-opcua-client";
42
- import { OPCUAServerEndPoint } from "./server_end_point";
43
- import { IChannelData } from "./i_channel_data";
44
- import { ISocketData } from "./i_socket_data";
33
+ import { type StatusCode, StatusCodes } from "node-opcua-status-code";
34
+ import type { ApplicationDescriptionOptions, EndpointDescription, GetEndpointsRequest } from "node-opcua-types";
35
+ import { checkFileExistsAndIsNotEmpty, matchUri } from "node-opcua-utils";
36
+ import type { IChannelData } from "./i_channel_data";
37
+ import type { ISocketData } from "./i_socket_data";
38
+ import type { OPCUAServerEndPoint } from "./server_end_point";
45
39
 
46
40
  const doDebug = checkDebugFlag(__filename);
47
41
  const debugLog = make_debugLog(__filename);
48
42
  const errorLog = make_errorLog(__filename);
49
- const warningLog = errorLog;
43
+ const warningLog = make_warningLog(__filename);
50
44
 
51
45
  const default_server_info = {
52
46
  // The globally unique identifier for the application instance. This URI is used as
@@ -122,7 +116,7 @@ export class OPCUABaseServer extends OPCUASecureObject {
122
116
  public endpoints: OPCUAServerEndPoint[];
123
117
  public readonly serverCertificateManager: OPCUACertificateManager;
124
118
  public capabilitiesForMDNS: string[];
125
- protected _preInitTask: any[];
119
+ protected _preInitTask: (() => Promise<void>)[];
126
120
 
127
121
  protected options: OPCUABaseServerOptions;
128
122
 
@@ -155,9 +149,9 @@ export class OPCUABaseServer extends OPCUASecureObject {
155
149
  errorLog("[NODE-OPCUA-E06] application name cannot be a urn", this.serverInfo.applicationName.toString());
156
150
  }
157
151
 
158
- this.serverInfo.applicationName!.locale = this.serverInfo.applicationName?.locale || "en";
152
+ this.serverInfo.applicationName.locale = this.serverInfo.applicationName.locale || "en";
159
153
 
160
- if (!this.serverInfo.applicationName?.locale) {
154
+ if (!this.serverInfo.applicationName.locale) {
161
155
  warningLog(
162
156
  "[NODE-OPCUA-W24] the server applicationName must have a valid locale : ",
163
157
  this.serverInfo.applicationName.toString()
@@ -166,10 +160,13 @@ export class OPCUABaseServer extends OPCUASecureObject {
166
160
 
167
161
  const __applicationUri = serverInfo.applicationUri || "";
168
162
 
169
- (this.serverInfo as any).__defineGetter__("applicationUri", () => resolveFullyQualifiedDomainName(__applicationUri));
163
+ Object.defineProperty(this.serverInfo, "applicationUri", {
164
+ get: () => resolveFullyQualifiedDomainName(__applicationUri),
165
+ configurable: true
166
+ });
170
167
 
171
168
  this._preInitTask.push(async () => {
172
- const fqdn = await extractFullyQualifiedDomainName();
169
+ await extractFullyQualifiedDomainName();
173
170
  });
174
171
 
175
172
  this._preInitTask.push(async () => {
@@ -177,35 +174,72 @@ export class OPCUABaseServer extends OPCUASecureObject {
177
174
  });
178
175
  }
179
176
 
177
+ /**
178
+ * Return additional DNS hostnames to include in the self-signed
179
+ * certificate's SubjectAlternativeName (SAN).
180
+ *
181
+ * The base implementation returns an empty array. Subclasses
182
+ * (e.g. `OPCUAServer`) override this to include hostnames from
183
+ * `alternateHostname` and `advertisedEndpoints`.
184
+ *
185
+ * @internal
186
+ */
187
+ protected getConfiguredHostnames(): string[] {
188
+ return [];
189
+ }
190
+
191
+ /**
192
+ * Return additional IP addresses to include in the self-signed
193
+ * certificate's SubjectAlternativeName (SAN) iPAddress entries.
194
+ *
195
+ * The base implementation returns an empty array. Subclasses
196
+ * (e.g. `OPCUAServer`) override this to include IP literals
197
+ * found in `alternateHostname` and `advertisedEndpoints`.
198
+ *
199
+ * These IPs are considered **explicitly configured** by the
200
+ * user and are therefore checked by `checkCertificateSAN()`.
201
+ * In contrast, auto-detected IPs from `getIpAddresses()` are
202
+ * included in the certificate at creation time but are NOT
203
+ * checked later — see `checkCertificateSAN()` for rationale.
204
+ *
205
+ * @internal
206
+ */
207
+ protected getConfiguredIPs(): string[] {
208
+ return [];
209
+ }
210
+
180
211
  protected async createDefaultCertificate(): Promise<void> {
181
212
  if (fs.existsSync(this.certificateFile)) {
182
213
  return;
183
214
  }
184
215
 
185
- // collect all hostnames
186
- const hostnames = [];
187
- for (const e of this.endpoints) {
188
- for (const ee of e.endpointDescriptions()) {
189
- /* to do */
190
- }
191
- }
192
216
  if (!checkFileExistsAndIsNotEmpty(this.certificateFile)) {
193
- await withLock({ fileToLock: this.certificateFile + ".mutex" }, async () => {
217
+ await withLock({ fileToLock: `${this.certificateFile}.mutex` }, async () => {
194
218
  if (checkFileExistsAndIsNotEmpty(this.certificateFile)) {
195
219
  return;
196
220
  }
197
- const applicationUri = this.serverInfo.applicationUri!;
221
+ const applicationUri = this.serverInfo.applicationUri || "<missing application uri>";
198
222
  const fqdn = getFullyQualifiedDomainName();
199
223
  const hostname = getHostname();
200
- const dns = [...new Set([fqdn, hostname])];
224
+ const dns = [...new Set([fqdn, hostname, ...this.getConfiguredHostnames()])].sort();
225
+
226
+ // Include both auto-detected IPs and explicitly configured IPs.
227
+ // Auto-detected IPs (getIpAddresses) are ephemeral — they depend on
228
+ // the current network state (WiFi, tethering, VPN, roaming) and may
229
+ // change between reboots. They are included here so that the initial
230
+ // certificate covers the current network configuration, but they are
231
+ // NOT checked by checkCertificateSAN() to avoid noisy warnings when
232
+ // the network changes. Only explicitly configured IPs (from
233
+ // alternateHostname / advertisedEndpoints) are checked at startup.
234
+ const ip = [...new Set([...getIpAddresses(), ...this.getConfiguredIPs()])].sort();
201
235
 
202
236
  await this.serverCertificateManager.createSelfSignedCertificate({
203
237
  applicationUri,
204
238
  dns,
205
- // ip: await getIpAddresses(),
239
+ ip,
206
240
  outputFile: this.certificateFile,
207
241
 
208
- subject: makeSubject(this.serverInfo.applicationName.text!, hostname),
242
+ subject: makeSubject(this.serverInfo.applicationName.text || "<missing application name>", hostname),
209
243
 
210
244
  startDate: new Date(),
211
245
  validity: 365 * 10 // 10 years
@@ -219,17 +253,128 @@ export class OPCUABaseServer extends OPCUASecureObject {
219
253
  await this.createDefaultCertificate();
220
254
  debugLog("privateKey = ", this.privateKeyFile, this.serverCertificateManager.privateKey);
221
255
  debugLog("certificateFile = ", this.certificateFile);
222
- await performCertificateSanityCheck(this, "server", this.serverCertificateManager, this.serverInfo.applicationUri!);
256
+ this._checkCertificateSanMismatch();
257
+ await performCertificateSanityCheck(this, "server", this.serverCertificateManager, this.serverInfo.applicationUri || "");
258
+ }
259
+
260
+ /**
261
+ * Compare the current certificate's SAN entries against all
262
+ * explicitly configured hostnames and IPs, and return any
263
+ * that are missing.
264
+ *
265
+ * Returns an empty array when the certificate covers every
266
+ * configured hostname and IP.
267
+ *
268
+ * **Important — ephemeral IP mitigation:**
269
+ * Auto-detected IPs (from `getIpAddresses()`) are deliberately
270
+ * NOT included in this check. Network interfaces are transient
271
+ * — WiFi IPs change on reconnect, tethering IPs appear/disappear,
272
+ * VPN adapters come and go. Including them would cause the
273
+ * `[NODE-OPCUA-W26]` warning to fire on every server restart
274
+ * whenever the network state differs from when the certificate
275
+ * was originally created.
276
+ *
277
+ * Only **explicitly configured** values are checked:
278
+ * - Hostnames: FQDN, os.hostname(), `alternateHostname` (non-IP),
279
+ * hostnames from `advertisedEndpoints` URLs
280
+ * - IPs: IP literals from `alternateHostname`, IP literals
281
+ * from `advertisedEndpoints` URLs
282
+ *
283
+ * The certificate itself still includes auto-detected IPs at
284
+ * creation time — this is fine because it captures the network
285
+ * state at that moment. But the *mismatch warning* only fires
286
+ * for things the user explicitly asked for.
287
+ */
288
+ public checkCertificateSAN(): string[] {
289
+ const certDer = this.getCertificate();
290
+ const info = exploreCertificate(certDer);
291
+ const sanDns: string[] = info.tbsCertificate.extensions?.subjectAltName?.dNSName || [];
292
+ const sanIpsHex: string[] = info.tbsCertificate.extensions?.subjectAltName?.iPAddress || [];
293
+
294
+ const fqdn = getFullyQualifiedDomainName();
295
+ const hostname = getHostname();
296
+ const expectedDns = [...new Set([fqdn, hostname, ...this.getConfiguredHostnames()])].sort();
297
+
298
+ // Only check explicitly configured IPs — NOT auto-detected ones.
299
+ // See JSDoc above for the rationale (ephemeral network interfaces).
300
+ const expectedIps = [...new Set(this.getConfiguredIPs())].sort();
301
+
302
+ const missingDns = expectedDns.filter((name) => !sanDns.includes(name));
303
+ // exploreCertificate returns iPAddress entries as hex strings
304
+ // Only IPv4 addresses can be converted with ipv4ToHex here; IPv6 (and invalid) IPs are skipped.
305
+ const missingIps = expectedIps.filter((ip) => {
306
+ const family = isIP(ip);
307
+ if (family === 4) {
308
+ return !sanIpsHex.includes(ipv4ToHex(ip));
309
+ }
310
+ // IPv6 or invalid literals are currently not matched against SAN iPAddress entries here.
311
+ return false;
312
+ });
313
+
314
+ return [...missingDns, ...missingIps];
315
+ }
316
+
317
+ /**
318
+ * Delete the existing self-signed certificate and create a new
319
+ * one that includes all currently configured hostnames.
320
+ *
321
+ * @throws if the current certificate was NOT self-signed
322
+ * (i.e. issued by a CA or GDS)
323
+ */
324
+ public async regenerateSelfSignedCertificate(): Promise<void> {
325
+ // guard: only allow regeneration of self-signed certs
326
+ const certDer = this.getCertificate();
327
+ const info = exploreCertificate(certDer);
328
+ const issuer = info.tbsCertificate.issuer;
329
+ const subject = info.tbsCertificate.subject;
330
+ const isSelfSigned = issuer.commonName === subject.commonName && issuer.organizationName === subject.organizationName;
331
+ if (!isSelfSigned) {
332
+ throw new Error("Cannot regenerate certificate: current certificate is not self-signed (issued by a CA or GDS)");
333
+ }
334
+
335
+ // delete old cert
336
+ if (fs.existsSync(this.certificateFile)) {
337
+ fs.unlinkSync(this.certificateFile);
338
+ }
339
+ // recreate with current hostnames
340
+ await this.createDefaultCertificate();
341
+ // invalidate cached cert so next getCertificate() reloads from disk
342
+ const priv = this as unknown as { $$certificate: null; $$certificateChain: null };
343
+ priv.$$certificate = null;
344
+ priv.$$certificateChain = null;
345
+ }
346
+
347
+ private _checkCertificateSanMismatch(): void {
348
+ try {
349
+ const missing = this.checkCertificateSAN();
350
+ if (missing.length > 0) {
351
+ warningLog(
352
+ `[NODE-OPCUA-W26] Certificate SAN is missing the following configured hostnames/IPs: ${missing.join(", ")}. ` +
353
+ "Clients with strict certificate validation may reject connections for these entries. " +
354
+ "Use server.regenerateSelfSignedCertificate() to fix this."
355
+ );
356
+ }
357
+ } catch (_err) {
358
+ // ignore errors during SAN check (e.g. cert not yet loaded)
359
+ }
223
360
  }
224
361
 
225
362
  /**
226
363
  * start all registered endPoint, in parallel, and call done when all endPoints are listening.
227
364
  */
228
- public start(done: (err?: Error | null) => void): void {
229
- assert(typeof done === "function");
230
- this.startAsync()
231
- .then(() => done(null))
232
- .catch((err) => done(err));
365
+ public start(): Promise<void>;
366
+ public start(done: () => void): void;
367
+ public start(...args: [((err?: Error) => void)?]): Promise<void> | void {
368
+ const callback = args[0];
369
+ if (!callback || args.length === 0) {
370
+ return this.startAsync();
371
+ } else {
372
+ this.startAsync()
373
+ .then(() => {
374
+ callback();
375
+ })
376
+ .catch((err) => callback(err));
377
+ }
233
378
  }
234
379
 
235
380
  protected async performPreInitialization(): Promise<void> {
@@ -294,7 +439,7 @@ export class OPCUABaseServer extends OPCUASecureObject {
294
439
  /**
295
440
  * shutdown all server endPoints
296
441
  */
297
- public shutdown(done: (err?: Error) => void): void {
442
+ public shutdown(done: (err?: Error | null) => void): void {
298
443
  assert(typeof done === "function");
299
444
  uninstallPeriodicClockAdjustment();
300
445
  this.serverCertificateManager.dispose().then(() => {
@@ -307,7 +452,7 @@ export class OPCUABaseServer extends OPCUASecureObject {
307
452
  },
308
453
  (err?: Error | null) => {
309
454
  debugLog("shutdown completed");
310
- done(err!);
455
+ done(err);
311
456
  }
312
457
  );
313
458
  });
@@ -317,6 +462,8 @@ export class OPCUABaseServer extends OPCUASecureObject {
317
462
  public shutdownChannels(callback: (err?: Error | null) => void): void;
318
463
  public shutdownChannels(callback?: (err?: Error | null) => void): Promise<void> | void {
319
464
  assert(typeof callback === "function");
465
+ // c8 ignore next
466
+ if (!callback) throw new Error("thenify is not available");
320
467
  debugLog("OPCUABaseServer#shutdownChannels");
321
468
  async.forEach(
322
469
  this.endpoints,
@@ -338,7 +485,7 @@ export class OPCUABaseServer extends OPCUASecureObject {
338
485
  inner_callback
339
486
  );
340
487
  },
341
- callback!
488
+ callback
342
489
  );
343
490
  }
344
491
 
@@ -352,7 +499,7 @@ export class OPCUABaseServer extends OPCUASecureObject {
352
499
 
353
500
  // install channel._on_response so we can intercept its call and emit the "response" event.
354
501
  if (!channel._on_response) {
355
- channel._on_response = (msg: string, response1: Response /*, inner_message: Message*/) => {
502
+ channel._on_response = (_msg: string, response1: Response /*, inner_message: Message*/) => {
356
503
  this.emit("response", response1, channel);
357
504
  };
358
505
  }
@@ -375,12 +522,11 @@ export class OPCUABaseServer extends OPCUASecureObject {
375
522
 
376
523
  try {
377
524
  // handler must be named _on_ActionRequest()
378
- const handler = (this as any)["_on_" + request.schema.name];
525
+ const handler = (this as unknown as Record<string, unknown>)[`_on_${request.schema.name}`];
379
526
  if (typeof handler === "function") {
380
- // eslint-disable-next-line prefer-rest-params
381
- handler.apply(this, arguments);
527
+ handler.call(this, message, channel);
382
528
  } else {
383
- errMessage = "[NODE-OPCUA-W07] Unsupported Service : " + request.schema.name;
529
+ errMessage = `[NODE-OPCUA-W07] Unsupported Service : ${request.schema.name}`;
384
530
  warningLog(errMessage);
385
531
  debugLog(chalk.red.bold(errMessage));
386
532
  response = makeServiceFault(StatusCodes.BadServiceUnsupported, [errMessage]);
@@ -388,14 +534,14 @@ export class OPCUABaseServer extends OPCUASecureObject {
388
534
  }
389
535
  } catch (err) {
390
536
  /* c8 ignore next */
391
- const errMessage1 = "[NODE-OPCUA-W08] EXCEPTION CAUGHT WHILE PROCESSING REQUEST !! " + request.schema.name;
537
+ const errMessage1 = `[NODE-OPCUA-W08] EXCEPTION CAUGHT WHILE PROCESSING REQUEST !! ${request.schema.name}`;
392
538
  warningLog(chalk.red.bold(errMessage1));
393
539
  warningLog(request.toString());
394
540
  displayTraceFromThisProjectOnly(err as Error);
395
541
 
396
542
  let additional_messages = [];
397
- additional_messages.push("EXCEPTION CAUGHT WHILE PROCESSING REQUEST !!! " + request.schema.name);
398
- if (types.isNativeError(err)) {
543
+ additional_messages.push(`EXCEPTION CAUGHT WHILE PROCESSING REQUEST !!! ${request.schema.name}`);
544
+ if (err instanceof Error) {
399
545
  additional_messages.push(err.message);
400
546
  if (err.stack) {
401
547
  additional_messages = additional_messages.concat(err.stack.split("\n"));
@@ -408,9 +554,18 @@ export class OPCUABaseServer extends OPCUASecureObject {
408
554
  }
409
555
 
410
556
  /**
411
- * @private
557
+ * Find endpoint descriptions matching a given endpoint URL.
558
+ *
559
+ * When `endpointUrl` is provided, only endpoints whose URL matches
560
+ * (case-insensitive) are returned. When `null` or omitted, all
561
+ * endpoints from every `OPCUAServerEndPoint` are returned.
562
+ *
563
+ * This is the shared resolution path used by both `GetEndpoints`
564
+ * and `CreateSession` (`validate_security_endpoint`).
565
+ *
566
+ * @internal (was _get_endpoints)
412
567
  */
413
- public _get_endpoints(endpointUrl?: string | null): EndpointDescription[] {
568
+ public findMatchingEndpoints(endpointUrl?: string | null): EndpointDescription[] {
414
569
  let endpoints: EndpointDescription[] = [];
415
570
  for (const endPoint of this.endpoints) {
416
571
  const ep = endPoint.endpointDescriptions();
@@ -423,17 +578,17 @@ export class OPCUABaseServer extends OPCUASecureObject {
423
578
  * get one of the possible endpointUrl
424
579
  */
425
580
  public getEndpointUrl(): string {
426
- return this._get_endpoints()[0].endpointUrl!;
581
+ return this.findMatchingEndpoints()[0].endpointUrl || "";
427
582
  }
428
583
 
429
584
  public getDiscoveryUrls(): string[] {
430
585
  const discoveryUrls = this.endpoints.map((e: OPCUAServerEndPoint) => {
431
- return e.endpointDescriptions()[0].endpointUrl!;
586
+ return e.endpointDescriptions()[0].endpointUrl || "";
432
587
  });
433
588
  return discoveryUrls;
434
589
  }
435
590
 
436
- public getServers(channel: ServerSecureChannelLayer): ApplicationDescription[] {
591
+ public getServers(_channel: ServerSecureChannelLayer): ApplicationDescription[] {
437
592
  this.serverInfo.discoveryUrls = this.getDiscoveryUrls();
438
593
  const servers = [this.serverInfo];
439
594
  return servers;
@@ -447,8 +602,8 @@ export class OPCUABaseServer extends OPCUASecureObject {
447
602
  *
448
603
  */
449
604
  public async suspendEndPoints(): Promise<void>;
450
- public suspendEndPoints(callback: (err?: Error) => void): void;
451
- public suspendEndPoints(callback?: (err?: Error) => void): void | Promise<void> {
605
+ public suspendEndPoints(callback: (err?: Error | null) => void): void;
606
+ public suspendEndPoints(callback?: (err?: Error | null) => void): void | Promise<void> {
452
607
  /* c8 ignore next */
453
608
  if (!callback) {
454
609
  throw new Error("Internal Error");
@@ -469,7 +624,7 @@ export class OPCUABaseServer extends OPCUASecureObject {
469
624
  _inner_callback(err);
470
625
  });
471
626
  },
472
- (err?: Error | null) => callback(err!)
627
+ (err?: Error | null) => callback(err)
473
628
  );
474
629
  }
475
630
 
@@ -479,18 +634,20 @@ export class OPCUABaseServer extends OPCUASecureObject {
479
634
  * this method is useful for testing purpose
480
635
  */
481
636
  public async resumeEndPoints(): Promise<void>;
482
- public resumeEndPoints(callback: (err?: Error) => void): void;
483
- public resumeEndPoints(callback?: (err?: Error) => void): void | Promise<void> {
637
+ public resumeEndPoints(callback: (err?: Error | null) => void): void;
638
+ public resumeEndPoints(callback?: (err?: Error | null) => void): void | Promise<void> {
639
+ // c8 ignore next
640
+ if (!callback) throw new Error("thenify is not available");
484
641
  async.forEach(
485
642
  this.endpoints,
486
643
  (ep: OPCUAServerEndPoint, _inner_callback) => {
487
644
  ep.restoreConnection(_inner_callback);
488
645
  },
489
- (err?: Error | null) => callback!(err!)
646
+ (err?: Error | null) => callback(err)
490
647
  );
491
648
  }
492
649
 
493
- protected prepare(message: Message, channel: ServerSecureChannelLayer): void {
650
+ protected prepare(_message: Message, _channel: ServerSecureChannelLayer): void {
494
651
  /* empty */
495
652
  }
496
653
 
@@ -516,23 +673,26 @@ export class OPCUABaseServer extends OPCUASecureObject {
516
673
  * If the URI is a URL, this URL may have a query string appended.
517
674
  * The Transport Profiles that support query strings are defined in OPC 10000-7.
518
675
  */
519
- response.endpoints = this._get_endpoints(null);
520
- const e = response.endpoints.map((e) => e.endpointUrl);
676
+ response.endpoints = this.findMatchingEndpoints(null);
677
+ const _e = response.endpoints.map((e) => e.endpointUrl);
521
678
  if (request.endpointUrl) {
522
- const filtered = response.endpoints.filter(
523
- (endpoint: EndpointDescription) => endpoint.endpointUrl === request.endpointUrl
679
+ const filtered = response.endpoints.filter((endpoint: EndpointDescription) =>
680
+ matchUri(endpoint.endpointUrl, request.endpointUrl)
524
681
  );
525
682
  if (filtered.length > 0) {
526
683
  response.endpoints = filtered;
527
684
  }
528
685
  }
529
- response.endpoints = response.endpoints.filter((endpoint: EndpointDescription) => !(endpoint as any).restricted);
686
+ response.endpoints = response.endpoints.filter(
687
+ (endpoint: EndpointDescription) => !(endpoint as unknown as { restricted: boolean }).restricted
688
+ );
530
689
 
531
690
  // apply filters
532
691
  if (request.profileUris && request.profileUris.length > 0) {
533
- response.endpoints = response.endpoints.filter((endpoint: any) => {
534
- return request.profileUris!.indexOf(endpoint.transportProfileUri) >= 0;
535
- });
692
+ const profileUris = request.profileUris;
693
+ response.endpoints = response.endpoints.filter(
694
+ (endpoint: EndpointDescription) => profileUris.indexOf(endpoint.transportProfileUri) >= 0
695
+ );
536
696
  }
537
697
 
538
698
  // adjust locale on ApplicationName to match requested local or provide
@@ -567,9 +727,10 @@ export class OPCUABaseServer extends OPCUASecureObject {
567
727
  // apply filters
568
728
  // TODO /
569
729
  if (request.serverUris && request.serverUris.length > 0) {
730
+ const serverUris = request.serverUris;
570
731
  // A serverUri matches the applicationUri from the ApplicationDescription define
571
732
  servers = servers.filter((inner_Server: ApplicationDescription) => {
572
- return request.serverUris!.indexOf(inner_Server.applicationUri) >= 0;
733
+ return serverUris.indexOf(inner_Server.applicationUri) >= 0;
573
734
  });
574
735
  }
575
736
 
@@ -626,6 +787,7 @@ function makeServiceFault(statusCode: StatusCode, messages: string[]): ServiceFa
626
787
 
627
788
  // tslint:disable:no-var-requires
628
789
  import { withCallback } from "thenify-ex";
790
+
629
791
  const opts = { multiArgs: false };
630
792
  OPCUABaseServer.prototype.resumeEndPoints = withCallback(OPCUABaseServer.prototype.resumeEndPoints, opts);
631
793
  OPCUABaseServer.prototype.suspendEndPoints = withCallback(OPCUABaseServer.prototype.suspendEndPoints, opts);