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.
@@ -1,13 +1,14 @@
1
- import { OPCUACertificateManager } from "node-opcua-certificate-manager";
2
- import { IOPCUASecureObjectOptions, OPCUASecureObject } from "node-opcua-common";
3
- import { Message, ServerSecureChannelLayer } from "node-opcua-secure-channel";
4
- import { ApplicationType } from "node-opcua-service-endpoints";
5
- import { ApplicationDescription } from "node-opcua-service-endpoints";
1
+ /**
2
+ * @module node-opcua-server
3
+ */
4
+ import { type OPCUACertificateManager } from "node-opcua-certificate-manager";
5
+ import { type IOPCUASecureObjectOptions, OPCUASecureObject } from "node-opcua-common";
6
+ import type { Message, ServerSecureChannelLayer } from "node-opcua-secure-channel";
7
+ import { ApplicationDescription, ApplicationType } from "node-opcua-service-endpoints";
6
8
  import { ServiceFault } from "node-opcua-service-secure-channel";
7
- import { StatusCode } from "node-opcua-status-code";
8
- import { ApplicationDescriptionOptions } from "node-opcua-types";
9
- import { EndpointDescription } from "node-opcua-types";
10
- import { OPCUAServerEndPoint } from "./server_end_point";
9
+ import { type StatusCode } from "node-opcua-status-code";
10
+ import type { ApplicationDescriptionOptions, EndpointDescription } from "node-opcua-types";
11
+ import type { OPCUAServerEndPoint } from "./server_end_point";
11
12
  /**
12
13
  *
13
14
  */
@@ -31,21 +32,88 @@ export declare class OPCUABaseServer extends OPCUASecureObject {
31
32
  endpoints: OPCUAServerEndPoint[];
32
33
  readonly serverCertificateManager: OPCUACertificateManager;
33
34
  capabilitiesForMDNS: string[];
34
- protected _preInitTask: any[];
35
+ protected _preInitTask: (() => Promise<void>)[];
35
36
  protected options: OPCUABaseServerOptions;
36
37
  constructor(options?: OPCUABaseServerOptions);
38
+ /**
39
+ * Return additional DNS hostnames to include in the self-signed
40
+ * certificate's SubjectAlternativeName (SAN).
41
+ *
42
+ * The base implementation returns an empty array. Subclasses
43
+ * (e.g. `OPCUAServer`) override this to include hostnames from
44
+ * `alternateHostname` and `advertisedEndpoints`.
45
+ *
46
+ * @internal
47
+ */
48
+ protected getConfiguredHostnames(): string[];
49
+ /**
50
+ * Return additional IP addresses to include in the self-signed
51
+ * certificate's SubjectAlternativeName (SAN) iPAddress entries.
52
+ *
53
+ * The base implementation returns an empty array. Subclasses
54
+ * (e.g. `OPCUAServer`) override this to include IP literals
55
+ * found in `alternateHostname` and `advertisedEndpoints`.
56
+ *
57
+ * These IPs are considered **explicitly configured** by the
58
+ * user and are therefore checked by `checkCertificateSAN()`.
59
+ * In contrast, auto-detected IPs from `getIpAddresses()` are
60
+ * included in the certificate at creation time but are NOT
61
+ * checked later — see `checkCertificateSAN()` for rationale.
62
+ *
63
+ * @internal
64
+ */
65
+ protected getConfiguredIPs(): string[];
37
66
  protected createDefaultCertificate(): Promise<void>;
38
67
  initializeCM(): Promise<void>;
68
+ /**
69
+ * Compare the current certificate's SAN entries against all
70
+ * explicitly configured hostnames and IPs, and return any
71
+ * that are missing.
72
+ *
73
+ * Returns an empty array when the certificate covers every
74
+ * configured hostname and IP.
75
+ *
76
+ * **Important — ephemeral IP mitigation:**
77
+ * Auto-detected IPs (from `getIpAddresses()`) are deliberately
78
+ * NOT included in this check. Network interfaces are transient
79
+ * — WiFi IPs change on reconnect, tethering IPs appear/disappear,
80
+ * VPN adapters come and go. Including them would cause the
81
+ * `[NODE-OPCUA-W26]` warning to fire on every server restart
82
+ * whenever the network state differs from when the certificate
83
+ * was originally created.
84
+ *
85
+ * Only **explicitly configured** values are checked:
86
+ * - Hostnames: FQDN, os.hostname(), `alternateHostname` (non-IP),
87
+ * hostnames from `advertisedEndpoints` URLs
88
+ * - IPs: IP literals from `alternateHostname`, IP literals
89
+ * from `advertisedEndpoints` URLs
90
+ *
91
+ * The certificate itself still includes auto-detected IPs at
92
+ * creation time — this is fine because it captures the network
93
+ * state at that moment. But the *mismatch warning* only fires
94
+ * for things the user explicitly asked for.
95
+ */
96
+ checkCertificateSAN(): string[];
97
+ /**
98
+ * Delete the existing self-signed certificate and create a new
99
+ * one that includes all currently configured hostnames.
100
+ *
101
+ * @throws if the current certificate was NOT self-signed
102
+ * (i.e. issued by a CA or GDS)
103
+ */
104
+ regenerateSelfSignedCertificate(): Promise<void>;
105
+ private _checkCertificateSanMismatch;
39
106
  /**
40
107
  * start all registered endPoint, in parallel, and call done when all endPoints are listening.
41
108
  */
42
- start(done: (err?: Error | null) => void): void;
109
+ start(): Promise<void>;
110
+ start(done: () => void): void;
43
111
  protected performPreInitialization(): Promise<void>;
44
112
  protected startAsync(): Promise<void>;
45
113
  /**
46
114
  * shutdown all server endPoints
47
115
  */
48
- shutdown(done: (err?: Error) => void): void;
116
+ shutdown(done: (err?: Error | null) => void): void;
49
117
  shutdownChannels(): Promise<void>;
50
118
  shutdownChannels(callback: (err?: Error | null) => void): void;
51
119
  /**
@@ -53,15 +121,24 @@ export declare class OPCUABaseServer extends OPCUASecureObject {
53
121
  */
54
122
  on_request(message: Message, channel: ServerSecureChannelLayer): void;
55
123
  /**
56
- * @private
124
+ * Find endpoint descriptions matching a given endpoint URL.
125
+ *
126
+ * When `endpointUrl` is provided, only endpoints whose URL matches
127
+ * (case-insensitive) are returned. When `null` or omitted, all
128
+ * endpoints from every `OPCUAServerEndPoint` are returned.
129
+ *
130
+ * This is the shared resolution path used by both `GetEndpoints`
131
+ * and `CreateSession` (`validate_security_endpoint`).
132
+ *
133
+ * @internal (was _get_endpoints)
57
134
  */
58
- _get_endpoints(endpointUrl?: string | null): EndpointDescription[];
135
+ findMatchingEndpoints(endpointUrl?: string | null): EndpointDescription[];
59
136
  /**
60
137
  * get one of the possible endpointUrl
61
138
  */
62
139
  getEndpointUrl(): string;
63
140
  getDiscoveryUrls(): string[];
64
- getServers(channel: ServerSecureChannelLayer): ApplicationDescription[];
141
+ getServers(_channel: ServerSecureChannelLayer): ApplicationDescription[];
65
142
  /**
66
143
  * set all the end point into a state where they do not accept further connections
67
144
  *
@@ -70,15 +147,15 @@ export declare class OPCUABaseServer extends OPCUASecureObject {
70
147
  *
71
148
  */
72
149
  suspendEndPoints(): Promise<void>;
73
- suspendEndPoints(callback: (err?: Error) => void): void;
150
+ suspendEndPoints(callback: (err?: Error | null) => void): void;
74
151
  /**
75
152
  * set all the end point into a state where they do accept connections
76
153
  * note:
77
154
  * this method is useful for testing purpose
78
155
  */
79
156
  resumeEndPoints(): Promise<void>;
80
- resumeEndPoints(callback: (err?: Error) => void): void;
81
- protected prepare(message: Message, channel: ServerSecureChannelLayer): void;
157
+ resumeEndPoints(callback: (err?: Error | null) => void): void;
158
+ protected prepare(_message: Message, _channel: ServerSecureChannelLayer): void;
82
159
  /**
83
160
  * @private
84
161
  */
@@ -1,43 +1,42 @@
1
1
  "use strict";
2
+ /**
3
+ * @module node-opcua-server
4
+ */
5
+ // tslint:disable:no-console
2
6
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
7
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
8
  };
5
9
  Object.defineProperty(exports, "__esModule", { value: true });
6
10
  exports.OPCUABaseServer = void 0;
7
- /**
8
- * @module node-opcua-server
9
- */
10
- // tslint:disable:no-console
11
- const fs_1 = __importDefault(require("fs"));
12
- const path_1 = __importDefault(require("path"));
13
- const os_1 = __importDefault(require("os"));
14
- const util_1 = require("util");
11
+ const node_fs_1 = __importDefault(require("node:fs"));
12
+ const node_os_1 = __importDefault(require("node:os"));
13
+ const node_path_1 = __importDefault(require("node:path"));
14
+ const node_net_1 = require("node:net");
15
+ const global_mutex_1 = require("@ster5/global-mutex");
15
16
  const async_1 = __importDefault(require("async"));
16
17
  const chalk_1 = __importDefault(require("chalk"));
17
18
  const node_opcua_assert_1 = require("node-opcua-assert");
18
- const global_mutex_1 = require("@ster5/global-mutex");
19
19
  const node_opcua_certificate_manager_1 = require("node-opcua-certificate-manager");
20
+ const node_opcua_client_1 = require("node-opcua-client");
20
21
  const node_opcua_common_1 = require("node-opcua-common");
22
+ const web_1 = require("node-opcua-crypto/web");
21
23
  const node_opcua_data_model_1 = require("node-opcua-data-model");
22
24
  const node_opcua_date_time_1 = require("node-opcua-date-time");
23
25
  const node_opcua_debug_1 = require("node-opcua-debug");
24
- const node_opcua_debug_2 = require("node-opcua-debug");
25
26
  const node_opcua_hostname_1 = require("node-opcua-hostname");
26
27
  const node_opcua_service_discovery_1 = require("node-opcua-service-discovery");
27
28
  const node_opcua_service_endpoints_1 = require("node-opcua-service-endpoints");
28
- const node_opcua_service_endpoints_2 = require("node-opcua-service-endpoints");
29
29
  const node_opcua_service_secure_channel_1 = require("node-opcua-service-secure-channel");
30
30
  const node_opcua_status_code_1 = require("node-opcua-status-code");
31
31
  const node_opcua_utils_1 = require("node-opcua-utils");
32
- const node_opcua_client_1 = require("node-opcua-client");
33
32
  const doDebug = (0, node_opcua_debug_1.checkDebugFlag)(__filename);
34
33
  const debugLog = (0, node_opcua_debug_1.make_debugLog)(__filename);
35
34
  const errorLog = (0, node_opcua_debug_1.make_errorLog)(__filename);
36
- const warningLog = errorLog;
35
+ const warningLog = (0, node_opcua_debug_1.make_warningLog)(__filename);
37
36
  const default_server_info = {
38
37
  // The globally unique identifier for the application instance. This URI is used as
39
38
  // ServerUri in Services if the application is a Server.
40
- applicationUri: (0, node_opcua_common_1.makeApplicationUrn)(os_1.default.hostname(), "NodeOPCUA-Server"),
39
+ applicationUri: (0, node_opcua_common_1.makeApplicationUrn)(node_os_1.default.hostname(), "NodeOPCUA-Server"),
41
40
  // The globally unique identifier for the product.
42
41
  productUri: "NodeOPCUA-Server",
43
42
  // A localized descriptive name for the application.
@@ -93,7 +92,7 @@ class OPCUABaseServer extends node_opcua_common_1.OPCUASecureObject {
93
92
  }
94
93
  options.privateKeyFile = options.privateKeyFile || options.serverCertificateManager.privateKey;
95
94
  options.certificateFile =
96
- options.certificateFile || path_1.default.join(options.serverCertificateManager.rootDir, "own/certs/certificate.pem");
95
+ options.certificateFile || node_path_1.default.join(options.serverCertificateManager.rootDir, "own/certs/certificate.pem");
97
96
  super(options);
98
97
  this.serverCertificateManager = options.serverCertificateManager;
99
98
  this.capabilitiesForMDNS = [];
@@ -105,49 +104,86 @@ class OPCUABaseServer extends node_opcua_common_1.OPCUASecureObject {
105
104
  ...options.serverInfo
106
105
  };
107
106
  serverInfo.applicationName = (0, node_opcua_data_model_1.coerceLocalizedText)(serverInfo.applicationName);
108
- this.serverInfo = new node_opcua_service_endpoints_2.ApplicationDescription(serverInfo);
107
+ this.serverInfo = new node_opcua_service_endpoints_1.ApplicationDescription(serverInfo);
109
108
  if (this.serverInfo.applicationName.toString().match(/urn:/)) {
110
109
  errorLog("[NODE-OPCUA-E06] application name cannot be a urn", this.serverInfo.applicationName.toString());
111
110
  }
112
- this.serverInfo.applicationName.locale = this.serverInfo.applicationName?.locale || "en";
113
- if (!this.serverInfo.applicationName?.locale) {
111
+ this.serverInfo.applicationName.locale = this.serverInfo.applicationName.locale || "en";
112
+ if (!this.serverInfo.applicationName.locale) {
114
113
  warningLog("[NODE-OPCUA-W24] the server applicationName must have a valid locale : ", this.serverInfo.applicationName.toString());
115
114
  }
116
115
  const __applicationUri = serverInfo.applicationUri || "";
117
- this.serverInfo.__defineGetter__("applicationUri", () => (0, node_opcua_hostname_1.resolveFullyQualifiedDomainName)(__applicationUri));
116
+ Object.defineProperty(this.serverInfo, "applicationUri", {
117
+ get: () => (0, node_opcua_hostname_1.resolveFullyQualifiedDomainName)(__applicationUri),
118
+ configurable: true
119
+ });
118
120
  this._preInitTask.push(async () => {
119
- const fqdn = await (0, node_opcua_hostname_1.extractFullyQualifiedDomainName)();
121
+ await (0, node_opcua_hostname_1.extractFullyQualifiedDomainName)();
120
122
  });
121
123
  this._preInitTask.push(async () => {
122
124
  await this.initializeCM();
123
125
  });
124
126
  }
127
+ /**
128
+ * Return additional DNS hostnames to include in the self-signed
129
+ * certificate's SubjectAlternativeName (SAN).
130
+ *
131
+ * The base implementation returns an empty array. Subclasses
132
+ * (e.g. `OPCUAServer`) override this to include hostnames from
133
+ * `alternateHostname` and `advertisedEndpoints`.
134
+ *
135
+ * @internal
136
+ */
137
+ getConfiguredHostnames() {
138
+ return [];
139
+ }
140
+ /**
141
+ * Return additional IP addresses to include in the self-signed
142
+ * certificate's SubjectAlternativeName (SAN) iPAddress entries.
143
+ *
144
+ * The base implementation returns an empty array. Subclasses
145
+ * (e.g. `OPCUAServer`) override this to include IP literals
146
+ * found in `alternateHostname` and `advertisedEndpoints`.
147
+ *
148
+ * These IPs are considered **explicitly configured** by the
149
+ * user and are therefore checked by `checkCertificateSAN()`.
150
+ * In contrast, auto-detected IPs from `getIpAddresses()` are
151
+ * included in the certificate at creation time but are NOT
152
+ * checked later — see `checkCertificateSAN()` for rationale.
153
+ *
154
+ * @internal
155
+ */
156
+ getConfiguredIPs() {
157
+ return [];
158
+ }
125
159
  async createDefaultCertificate() {
126
- if (fs_1.default.existsSync(this.certificateFile)) {
160
+ if (node_fs_1.default.existsSync(this.certificateFile)) {
127
161
  return;
128
162
  }
129
- // collect all hostnames
130
- const hostnames = [];
131
- for (const e of this.endpoints) {
132
- for (const ee of e.endpointDescriptions()) {
133
- /* to do */
134
- }
135
- }
136
163
  if (!(0, node_opcua_utils_1.checkFileExistsAndIsNotEmpty)(this.certificateFile)) {
137
- await (0, global_mutex_1.withLock)({ fileToLock: this.certificateFile + ".mutex" }, async () => {
164
+ await (0, global_mutex_1.withLock)({ fileToLock: `${this.certificateFile}.mutex` }, async () => {
138
165
  if ((0, node_opcua_utils_1.checkFileExistsAndIsNotEmpty)(this.certificateFile)) {
139
166
  return;
140
167
  }
141
- const applicationUri = this.serverInfo.applicationUri;
168
+ const applicationUri = this.serverInfo.applicationUri || "<missing application uri>";
142
169
  const fqdn = (0, node_opcua_hostname_1.getFullyQualifiedDomainName)();
143
170
  const hostname = (0, node_opcua_hostname_1.getHostname)();
144
- const dns = [...new Set([fqdn, hostname])];
171
+ const dns = [...new Set([fqdn, hostname, ...this.getConfiguredHostnames()])].sort();
172
+ // Include both auto-detected IPs and explicitly configured IPs.
173
+ // Auto-detected IPs (getIpAddresses) are ephemeral — they depend on
174
+ // the current network state (WiFi, tethering, VPN, roaming) and may
175
+ // change between reboots. They are included here so that the initial
176
+ // certificate covers the current network configuration, but they are
177
+ // NOT checked by checkCertificateSAN() to avoid noisy warnings when
178
+ // the network changes. Only explicitly configured IPs (from
179
+ // alternateHostname / advertisedEndpoints) are checked at startup.
180
+ const ip = [...new Set([...(0, node_opcua_hostname_1.getIpAddresses)(), ...this.getConfiguredIPs()])].sort();
145
181
  await this.serverCertificateManager.createSelfSignedCertificate({
146
182
  applicationUri,
147
183
  dns,
148
- // ip: await getIpAddresses(),
184
+ ip,
149
185
  outputFile: this.certificateFile,
150
- subject: (0, node_opcua_certificate_manager_1.makeSubject)(this.serverInfo.applicationName.text, hostname),
186
+ subject: (0, node_opcua_certificate_manager_1.makeSubject)(this.serverInfo.applicationName.text || "<missing application name>", hostname),
151
187
  startDate: new Date(),
152
188
  validity: 365 * 10 // 10 years
153
189
  });
@@ -159,16 +195,114 @@ class OPCUABaseServer extends node_opcua_common_1.OPCUASecureObject {
159
195
  await this.createDefaultCertificate();
160
196
  debugLog("privateKey = ", this.privateKeyFile, this.serverCertificateManager.privateKey);
161
197
  debugLog("certificateFile = ", this.certificateFile);
162
- await (0, node_opcua_client_1.performCertificateSanityCheck)(this, "server", this.serverCertificateManager, this.serverInfo.applicationUri);
198
+ this._checkCertificateSanMismatch();
199
+ await (0, node_opcua_client_1.performCertificateSanityCheck)(this, "server", this.serverCertificateManager, this.serverInfo.applicationUri || "");
163
200
  }
164
201
  /**
165
- * start all registered endPoint, in parallel, and call done when all endPoints are listening.
202
+ * Compare the current certificate's SAN entries against all
203
+ * explicitly configured hostnames and IPs, and return any
204
+ * that are missing.
205
+ *
206
+ * Returns an empty array when the certificate covers every
207
+ * configured hostname and IP.
208
+ *
209
+ * **Important — ephemeral IP mitigation:**
210
+ * Auto-detected IPs (from `getIpAddresses()`) are deliberately
211
+ * NOT included in this check. Network interfaces are transient
212
+ * — WiFi IPs change on reconnect, tethering IPs appear/disappear,
213
+ * VPN adapters come and go. Including them would cause the
214
+ * `[NODE-OPCUA-W26]` warning to fire on every server restart
215
+ * whenever the network state differs from when the certificate
216
+ * was originally created.
217
+ *
218
+ * Only **explicitly configured** values are checked:
219
+ * - Hostnames: FQDN, os.hostname(), `alternateHostname` (non-IP),
220
+ * hostnames from `advertisedEndpoints` URLs
221
+ * - IPs: IP literals from `alternateHostname`, IP literals
222
+ * from `advertisedEndpoints` URLs
223
+ *
224
+ * The certificate itself still includes auto-detected IPs at
225
+ * creation time — this is fine because it captures the network
226
+ * state at that moment. But the *mismatch warning* only fires
227
+ * for things the user explicitly asked for.
166
228
  */
167
- start(done) {
168
- (0, node_opcua_assert_1.assert)(typeof done === "function");
169
- this.startAsync()
170
- .then(() => done(null))
171
- .catch((err) => done(err));
229
+ checkCertificateSAN() {
230
+ const certDer = this.getCertificate();
231
+ const info = (0, web_1.exploreCertificate)(certDer);
232
+ const sanDns = info.tbsCertificate.extensions?.subjectAltName?.dNSName || [];
233
+ const sanIpsHex = info.tbsCertificate.extensions?.subjectAltName?.iPAddress || [];
234
+ const fqdn = (0, node_opcua_hostname_1.getFullyQualifiedDomainName)();
235
+ const hostname = (0, node_opcua_hostname_1.getHostname)();
236
+ const expectedDns = [...new Set([fqdn, hostname, ...this.getConfiguredHostnames()])].sort();
237
+ // Only check explicitly configured IPs — NOT auto-detected ones.
238
+ // See JSDoc above for the rationale (ephemeral network interfaces).
239
+ const expectedIps = [...new Set(this.getConfiguredIPs())].sort();
240
+ const missingDns = expectedDns.filter((name) => !sanDns.includes(name));
241
+ // exploreCertificate returns iPAddress entries as hex strings
242
+ // Only IPv4 addresses can be converted with ipv4ToHex here; IPv6 (and invalid) IPs are skipped.
243
+ const missingIps = expectedIps.filter((ip) => {
244
+ const family = (0, node_net_1.isIP)(ip);
245
+ if (family === 4) {
246
+ return !sanIpsHex.includes((0, node_opcua_hostname_1.ipv4ToHex)(ip));
247
+ }
248
+ // IPv6 or invalid literals are currently not matched against SAN iPAddress entries here.
249
+ return false;
250
+ });
251
+ return [...missingDns, ...missingIps];
252
+ }
253
+ /**
254
+ * Delete the existing self-signed certificate and create a new
255
+ * one that includes all currently configured hostnames.
256
+ *
257
+ * @throws if the current certificate was NOT self-signed
258
+ * (i.e. issued by a CA or GDS)
259
+ */
260
+ async regenerateSelfSignedCertificate() {
261
+ // guard: only allow regeneration of self-signed certs
262
+ const certDer = this.getCertificate();
263
+ const info = (0, web_1.exploreCertificate)(certDer);
264
+ const issuer = info.tbsCertificate.issuer;
265
+ const subject = info.tbsCertificate.subject;
266
+ const isSelfSigned = issuer.commonName === subject.commonName && issuer.organizationName === subject.organizationName;
267
+ if (!isSelfSigned) {
268
+ throw new Error("Cannot regenerate certificate: current certificate is not self-signed (issued by a CA or GDS)");
269
+ }
270
+ // delete old cert
271
+ if (node_fs_1.default.existsSync(this.certificateFile)) {
272
+ node_fs_1.default.unlinkSync(this.certificateFile);
273
+ }
274
+ // recreate with current hostnames
275
+ await this.createDefaultCertificate();
276
+ // invalidate cached cert so next getCertificate() reloads from disk
277
+ const priv = this;
278
+ priv.$$certificate = null;
279
+ priv.$$certificateChain = null;
280
+ }
281
+ _checkCertificateSanMismatch() {
282
+ try {
283
+ const missing = this.checkCertificateSAN();
284
+ if (missing.length > 0) {
285
+ warningLog(`[NODE-OPCUA-W26] Certificate SAN is missing the following configured hostnames/IPs: ${missing.join(", ")}. ` +
286
+ "Clients with strict certificate validation may reject connections for these entries. " +
287
+ "Use server.regenerateSelfSignedCertificate() to fix this.");
288
+ }
289
+ }
290
+ catch (_err) {
291
+ // ignore errors during SAN check (e.g. cert not yet loaded)
292
+ }
293
+ }
294
+ start(...args) {
295
+ const callback = args[0];
296
+ if (!callback || args.length === 0) {
297
+ return this.startAsync();
298
+ }
299
+ else {
300
+ this.startAsync()
301
+ .then(() => {
302
+ callback();
303
+ })
304
+ .catch((err) => callback(err));
305
+ }
172
306
  }
173
307
  async performPreInitialization() {
174
308
  const tasks = this._preInitTask;
@@ -230,6 +364,9 @@ class OPCUABaseServer extends node_opcua_common_1.OPCUASecureObject {
230
364
  }
231
365
  shutdownChannels(callback) {
232
366
  (0, node_opcua_assert_1.assert)(typeof callback === "function");
367
+ // c8 ignore next
368
+ if (!callback)
369
+ throw new Error("thenify is not available");
233
370
  debugLog("OPCUABaseServer#shutdownChannels");
234
371
  async_1.default.forEach(this.endpoints, (endpoint, inner_callback) => {
235
372
  debugLog(" shutting down endpoint ", endpoint.endpointDescriptions()[0].endpointUrl);
@@ -256,7 +393,7 @@ class OPCUABaseServer extends node_opcua_common_1.OPCUASecureObject {
256
393
  const request = message.request;
257
394
  // install channel._on_response so we can intercept its call and emit the "response" event.
258
395
  if (!channel._on_response) {
259
- channel._on_response = (msg, response1 /*, inner_message: Message*/) => {
396
+ channel._on_response = (_msg, response1 /*, inner_message: Message*/) => {
260
397
  this.emit("response", response1, channel);
261
398
  };
262
399
  }
@@ -270,13 +407,12 @@ class OPCUABaseServer extends node_opcua_common_1.OPCUASecureObject {
270
407
  this.emit("request", request, channel);
271
408
  try {
272
409
  // handler must be named _on_ActionRequest()
273
- const handler = this["_on_" + request.schema.name];
410
+ const handler = this[`_on_${request.schema.name}`];
274
411
  if (typeof handler === "function") {
275
- // eslint-disable-next-line prefer-rest-params
276
- handler.apply(this, arguments);
412
+ handler.call(this, message, channel);
277
413
  }
278
414
  else {
279
- errMessage = "[NODE-OPCUA-W07] Unsupported Service : " + request.schema.name;
415
+ errMessage = `[NODE-OPCUA-W07] Unsupported Service : ${request.schema.name}`;
280
416
  warningLog(errMessage);
281
417
  debugLog(chalk_1.default.red.bold(errMessage));
282
418
  response = makeServiceFault(node_opcua_status_code_1.StatusCodes.BadServiceUnsupported, [errMessage]);
@@ -285,13 +421,13 @@ class OPCUABaseServer extends node_opcua_common_1.OPCUASecureObject {
285
421
  }
286
422
  catch (err) {
287
423
  /* c8 ignore next */
288
- const errMessage1 = "[NODE-OPCUA-W08] EXCEPTION CAUGHT WHILE PROCESSING REQUEST !! " + request.schema.name;
424
+ const errMessage1 = `[NODE-OPCUA-W08] EXCEPTION CAUGHT WHILE PROCESSING REQUEST !! ${request.schema.name}`;
289
425
  warningLog(chalk_1.default.red.bold(errMessage1));
290
426
  warningLog(request.toString());
291
- (0, node_opcua_debug_2.displayTraceFromThisProjectOnly)(err);
427
+ (0, node_opcua_debug_1.displayTraceFromThisProjectOnly)(err);
292
428
  let additional_messages = [];
293
- additional_messages.push("EXCEPTION CAUGHT WHILE PROCESSING REQUEST !!! " + request.schema.name);
294
- if (util_1.types.isNativeError(err)) {
429
+ additional_messages.push(`EXCEPTION CAUGHT WHILE PROCESSING REQUEST !!! ${request.schema.name}`);
430
+ if (err instanceof Error) {
295
431
  additional_messages.push(err.message);
296
432
  if (err.stack) {
297
433
  additional_messages = additional_messages.concat(err.stack.split("\n"));
@@ -302,9 +438,18 @@ class OPCUABaseServer extends node_opcua_common_1.OPCUASecureObject {
302
438
  }
303
439
  }
304
440
  /**
305
- * @private
441
+ * Find endpoint descriptions matching a given endpoint URL.
442
+ *
443
+ * When `endpointUrl` is provided, only endpoints whose URL matches
444
+ * (case-insensitive) are returned. When `null` or omitted, all
445
+ * endpoints from every `OPCUAServerEndPoint` are returned.
446
+ *
447
+ * This is the shared resolution path used by both `GetEndpoints`
448
+ * and `CreateSession` (`validate_security_endpoint`).
449
+ *
450
+ * @internal (was _get_endpoints)
306
451
  */
307
- _get_endpoints(endpointUrl) {
452
+ findMatchingEndpoints(endpointUrl) {
308
453
  let endpoints = [];
309
454
  for (const endPoint of this.endpoints) {
310
455
  const ep = endPoint.endpointDescriptions();
@@ -317,15 +462,15 @@ class OPCUABaseServer extends node_opcua_common_1.OPCUASecureObject {
317
462
  * get one of the possible endpointUrl
318
463
  */
319
464
  getEndpointUrl() {
320
- return this._get_endpoints()[0].endpointUrl;
465
+ return this.findMatchingEndpoints()[0].endpointUrl || "";
321
466
  }
322
467
  getDiscoveryUrls() {
323
468
  const discoveryUrls = this.endpoints.map((e) => {
324
- return e.endpointDescriptions()[0].endpointUrl;
469
+ return e.endpointDescriptions()[0].endpointUrl || "";
325
470
  });
326
471
  return discoveryUrls;
327
472
  }
328
- getServers(channel) {
473
+ getServers(_channel) {
329
474
  this.serverInfo.discoveryUrls = this.getDiscoveryUrls();
330
475
  const servers = [this.serverInfo];
331
476
  return servers;
@@ -350,11 +495,14 @@ class OPCUABaseServer extends node_opcua_common_1.OPCUASecureObject {
350
495
  }, (err) => callback(err));
351
496
  }
352
497
  resumeEndPoints(callback) {
498
+ // c8 ignore next
499
+ if (!callback)
500
+ throw new Error("thenify is not available");
353
501
  async_1.default.forEach(this.endpoints, (ep, _inner_callback) => {
354
502
  ep.restoreConnection(_inner_callback);
355
503
  }, (err) => callback(err));
356
504
  }
357
- prepare(message, channel) {
505
+ prepare(_message, _channel) {
358
506
  /* empty */
359
507
  }
360
508
  /**
@@ -376,10 +524,10 @@ class OPCUABaseServer extends node_opcua_common_1.OPCUASecureObject {
376
524
  * If the URI is a URL, this URL may have a query string appended.
377
525
  * The Transport Profiles that support query strings are defined in OPC 10000-7.
378
526
  */
379
- response.endpoints = this._get_endpoints(null);
380
- const e = response.endpoints.map((e) => e.endpointUrl);
527
+ response.endpoints = this.findMatchingEndpoints(null);
528
+ const _e = response.endpoints.map((e) => e.endpointUrl);
381
529
  if (request.endpointUrl) {
382
- const filtered = response.endpoints.filter((endpoint) => endpoint.endpointUrl === request.endpointUrl);
530
+ const filtered = response.endpoints.filter((endpoint) => (0, node_opcua_utils_1.matchUri)(endpoint.endpointUrl, request.endpointUrl));
383
531
  if (filtered.length > 0) {
384
532
  response.endpoints = filtered;
385
533
  }
@@ -387,9 +535,8 @@ class OPCUABaseServer extends node_opcua_common_1.OPCUASecureObject {
387
535
  response.endpoints = response.endpoints.filter((endpoint) => !endpoint.restricted);
388
536
  // apply filters
389
537
  if (request.profileUris && request.profileUris.length > 0) {
390
- response.endpoints = response.endpoints.filter((endpoint) => {
391
- return request.profileUris.indexOf(endpoint.transportProfileUri) >= 0;
392
- });
538
+ const profileUris = request.profileUris;
539
+ response.endpoints = response.endpoints.filter((endpoint) => profileUris.indexOf(endpoint.transportProfileUri) >= 0);
393
540
  }
394
541
  // adjust locale on ApplicationName to match requested local or provide
395
542
  // a string with neutral locale (locale === null)
@@ -419,13 +566,14 @@ class OPCUABaseServer extends node_opcua_common_1.OPCUASecureObject {
419
566
  // apply filters
420
567
  // TODO /
421
568
  if (request.serverUris && request.serverUris.length > 0) {
569
+ const serverUris = request.serverUris;
422
570
  // A serverUri matches the applicationUri from the ApplicationDescription define
423
571
  servers = servers.filter((inner_Server) => {
424
- return request.serverUris.indexOf(inner_Server.applicationUri) >= 0;
572
+ return serverUris.indexOf(inner_Server.applicationUri) >= 0;
425
573
  });
426
574
  }
427
575
  function adapt(applicationDescription) {
428
- return new node_opcua_service_endpoints_2.ApplicationDescription({
576
+ return new node_opcua_service_endpoints_1.ApplicationDescription({
429
577
  applicationName: applicationDescription.applicationName,
430
578
  applicationType: applicationDescription.applicationType,
431
579
  applicationUri: applicationDescription.applicationUri,