@serfab/cadre-host 1.14.0 → 1.15.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 (107) hide show
  1. package/README.md +19 -4
  2. package/dist/bin/host.js +32 -20
  3. package/dist/bin/host.js.map +1 -1
  4. package/dist/bin/nat-output.d.ts +5 -0
  5. package/dist/bin/nat-output.js +12 -1
  6. package/dist/bin/nat-output.js.map +1 -1
  7. package/dist/bin/ui-access.d.ts +22 -0
  8. package/dist/bin/ui-access.js +44 -0
  9. package/dist/bin/ui-access.js.map +1 -0
  10. package/dist/hosted/claim-details.d.ts +25 -3
  11. package/dist/hosted/claim-details.js +31 -37
  12. package/dist/hosted/claim-details.js.map +1 -1
  13. package/dist/hosted/hosted-node-service.d.ts +7 -0
  14. package/dist/hosted/hosted-node-service.js +16 -4
  15. package/dist/hosted/hosted-node-service.js.map +1 -1
  16. package/dist/hosted/hosted-node-watcher.d.ts +16 -4
  17. package/dist/hosted/hosted-node-watcher.js +45 -9
  18. package/dist/hosted/hosted-node-watcher.js.map +1 -1
  19. package/dist/hosted/node-status.d.ts +19 -3
  20. package/dist/hosted/node-status.js +21 -5
  21. package/dist/hosted/node-status.js.map +1 -1
  22. package/dist/hosted/types.d.ts +12 -5
  23. package/dist/hosted/types.js.map +1 -1
  24. package/dist/installer/config.d.ts +23 -0
  25. package/dist/installer/config.js +46 -0
  26. package/dist/installer/config.js.map +1 -1
  27. package/dist/installer/index.d.ts +2 -0
  28. package/dist/installer/index.js +7 -16
  29. package/dist/installer/index.js.map +1 -1
  30. package/dist/installer/wizard.d.ts +12 -2
  31. package/dist/installer/wizard.js +15 -7
  32. package/dist/installer/wizard.js.map +1 -1
  33. package/dist/nat/index.d.ts +2 -1
  34. package/dist/nat/index.js +1 -0
  35. package/dist/nat/index.js.map +1 -1
  36. package/dist/nat/nat-service.d.ts +22 -5
  37. package/dist/nat/nat-service.js +75 -26
  38. package/dist/nat/nat-service.js.map +1 -1
  39. package/dist/nat/nat-store.js +11 -0
  40. package/dist/nat/nat-store.js.map +1 -1
  41. package/dist/nat/network-mode.d.ts +19 -0
  42. package/dist/nat/network-mode.js +32 -0
  43. package/dist/nat/network-mode.js.map +1 -0
  44. package/dist/nat/reachability.d.ts +5 -4
  45. package/dist/nat/reachability.js +19 -6
  46. package/dist/nat/reachability.js.map +1 -1
  47. package/dist/nat/types.d.ts +26 -6
  48. package/dist/nat/types.js.map +1 -1
  49. package/dist/orchestrator/host-process-orchestrator.d.ts +3 -3
  50. package/dist/orchestrator/host-process-orchestrator.js +6 -15
  51. package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
  52. package/dist/package-version.d.ts +1 -0
  53. package/dist/package-version.js +4 -0
  54. package/dist/package-version.js.map +1 -0
  55. package/dist/server/routes/settings.d.ts +1 -0
  56. package/dist/server/routes/settings.js +15 -1
  57. package/dist/server/routes/settings.js.map +1 -1
  58. package/dist/server/routes/status.js +2 -32
  59. package/dist/server/routes/status.js.map +1 -1
  60. package/dist/ui/assets/index-BHd8CWZA.css +1 -0
  61. package/dist/ui/assets/index-Ccr2GjPT.js +23 -0
  62. package/dist/ui/assets/{index-4FWuU8ye.js.map → index-Ccr2GjPT.js.map} +1 -1
  63. package/dist/ui/index.html +2 -2
  64. package/package.json +4 -5
  65. package/src/__tests__/cli.smoke.test.ts +1 -1
  66. package/src/__tests__/orchestrator-claim-secret.test.ts +1 -2
  67. package/src/__tests__/orchestrator-env-scrub.test.ts +160 -163
  68. package/src/__tests__/orchestrator-handle-contract.ts +1 -1
  69. package/src/__tests__/orchestrator-node-identity.test.ts +0 -5
  70. package/src/__tests__/orchestrator-push.test.ts +3 -3
  71. package/src/__tests__/orchestrator.test.ts +3 -5
  72. package/src/__tests__/ui-access.test.ts +30 -0
  73. package/src/bin/host.ts +30 -20
  74. package/src/bin/nat-output.ts +12 -1
  75. package/src/bin/ui-access.ts +50 -0
  76. package/src/hosted/__tests__/fake-orchestrator.test.ts +0 -1
  77. package/src/hosted/__tests__/fake-orchestrator.ts +263 -265
  78. package/src/hosted/__tests__/hosted-node-service.test.ts +708 -609
  79. package/src/hosted/__tests__/hosted-node-supervisor.test.ts +475 -474
  80. package/src/hosted/claim-details.ts +44 -37
  81. package/src/hosted/hosted-node-service.ts +23 -4
  82. package/src/hosted/hosted-node-watcher.ts +288 -250
  83. package/src/hosted/node-status.ts +31 -5
  84. package/src/hosted/types.ts +160 -153
  85. package/src/installer/__tests__/installer.smoke.test.ts +184 -182
  86. package/src/installer/__tests__/wizard.test.ts +76 -56
  87. package/src/installer/config.ts +66 -0
  88. package/src/installer/index.ts +252 -259
  89. package/src/installer/wizard.ts +101 -87
  90. package/src/nat/__tests__/nat-service.test.ts +608 -540
  91. package/src/nat/__tests__/nat-store.test.ts +130 -127
  92. package/src/nat/__tests__/network-mode.test.ts +38 -0
  93. package/src/nat/index.ts +91 -87
  94. package/src/nat/nat-service.ts +79 -26
  95. package/src/nat/nat-store.ts +249 -237
  96. package/src/nat/network-mode.ts +36 -0
  97. package/src/nat/reachability.ts +155 -143
  98. package/src/nat/types.ts +232 -209
  99. package/src/orchestrator/host-process-orchestrator.ts +1089 -1098
  100. package/src/package-version.ts +4 -0
  101. package/src/server/__tests__/fakes.ts +41 -40
  102. package/src/server/__tests__/hosted-nodes-route.test.ts +161 -160
  103. package/src/server/__tests__/settings-route.test.ts +31 -0
  104. package/src/server/routes/settings.ts +128 -115
  105. package/src/server/routes/status.ts +72 -102
  106. package/dist/ui/assets/index-4FWuU8ye.js +0 -19
  107. package/dist/ui/assets/index-Di9Q4d01.css +0 -1
@@ -35,6 +35,27 @@ export interface PushSettings {
35
35
  /** Per-strand burst-coalescing window (ms) forwarded into the node's push block. */
36
36
  debounceMs?: number;
37
37
  }
38
+ /**
39
+ * Which addresses a hosted node's QR code carries (cadre-core's `selectNodeClaimAddresses`).
40
+ * Absent ⇒ `{ lan: 'auto' }`. Read on every claim-code request, so an edit applies to the
41
+ * next code shown.
42
+ */
43
+ export interface ClaimAddressSettings {
44
+ /**
45
+ * The LAN address a phone at home dials: `'auto'` (the address on the router's subnet, else
46
+ * the machine's primary one; none in public-IP mode), `'none'` (public addresses only), or an
47
+ * IP address. Other private addresses (Docker bridges, VPNs) are left out either way.
48
+ */
49
+ lan?: string;
50
+ /**
51
+ * Exact addresses to put in every code instead, for an operator who knows better than the
52
+ * rule, such as one name behind a proxy. Each names no destination peer (a relay address ends
53
+ * at its `/p2p-circuit` hop); every node's code appends its own peer id.
54
+ */
55
+ addrs?: string[];
56
+ }
57
+ /** Why a `claimAddresses` value is unacceptable, naming the field; undefined when it is fine. */
58
+ export declare function claimAddressSettingsProblem(value: unknown): string | undefined;
38
59
  export interface HostConfigFile {
39
60
  version: 3;
40
61
  /** A stable per-install id, for log correlation. */
@@ -57,6 +78,8 @@ export interface HostConfigFile {
57
78
  * they are read from the secret store at node-spawn time (`src/push/`).
58
79
  */
59
80
  push?: PushSettings;
81
+ /** Which addresses claim codes carry; absent ⇒ automatic. */
82
+ claimAddresses?: ClaimAddressSettings;
60
83
  }
61
84
  export declare function writeHostConfig(filePath: string, cfg: HostConfigFile): void;
62
85
  /** Apply a partial patch to the persisted config (read-modify-write). */
@@ -11,7 +11,50 @@
11
11
  * being silently misread.
12
12
  */
13
13
  import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
14
+ import { isIP } from 'node:net';
14
15
  import { dirname } from 'node:path';
16
+ import { destinationPeerIdOf } from '@serfab/cadre-core';
17
+ /** Why a `claimAddresses` value is unacceptable, naming the field; undefined when it is fine. */
18
+ export function claimAddressSettingsProblem(value) {
19
+ if (!value || typeof value !== 'object' || Array.isArray(value))
20
+ return '"claimAddresses" must be an object';
21
+ const o = value;
22
+ for (const key of Object.keys(o)) {
23
+ if (key !== 'lan' && key !== 'addrs')
24
+ return `Unknown setting "claimAddresses.${key}"`;
25
+ }
26
+ if (o.lan !== undefined) {
27
+ if (typeof o.lan !== 'string' || (o.lan !== 'auto' && o.lan !== 'none' && isIP(o.lan) === 0)) {
28
+ return '"claimAddresses.lan" must be "auto", "none" or an IP address';
29
+ }
30
+ }
31
+ if (o.addrs !== undefined) {
32
+ if (!Array.isArray(o.addrs) || !o.addrs.every((a) => typeof a === 'string')) {
33
+ return '"claimAddresses.addrs" must be a list of multiaddr strings';
34
+ }
35
+ for (const addr of o.addrs) {
36
+ const problem = exactClaimAddressProblem(addr);
37
+ if (problem)
38
+ return `"claimAddresses.addrs" entry "${addr}" ${problem}`;
39
+ }
40
+ }
41
+ return undefined;
42
+ }
43
+ /**
44
+ * Why one `claimAddresses.addrs` entry is unacceptable. It must not name its destination peer:
45
+ * the list is host-wide and each hosted node's code appends its own peer id, so a named one
46
+ * would be some other node's address in every code but that node's.
47
+ */
48
+ function exactClaimAddressProblem(addr) {
49
+ let destination;
50
+ try {
51
+ destination = destinationPeerIdOf(addr.trim());
52
+ }
53
+ catch {
54
+ return 'is not a valid multiaddr';
55
+ }
56
+ return destination === null ? undefined : 'must not end in /p2p/<peer id>: each node\'s own id is appended';
57
+ }
15
58
  const CURRENT_VERSION = 3;
16
59
  export function writeHostConfig(filePath, cfg) {
17
60
  if (cfg.version !== CURRENT_VERSION) {
@@ -51,6 +94,9 @@ export function readHostConfig(filePath) {
51
94
  if (!isHostConfigShape(obj)) {
52
95
  throw new Error(`host.config.json at ${filePath} is missing required fields`);
53
96
  }
97
+ const claimAddressesProblem = obj.claimAddresses === undefined ? undefined : claimAddressSettingsProblem(obj.claimAddresses);
98
+ if (claimAddressesProblem)
99
+ throw new Error(`host.config.json at ${filePath}: ${claimAddressesProblem}`);
54
100
  return obj;
55
101
  }
56
102
  function isHostConfigShape(v) {
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/installer/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/installer/config.ts"],"names":[],"mappings":"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"}
@@ -40,6 +40,8 @@ export interface InstallOptions {
40
40
  serviceHost?: ServiceHost;
41
41
  /** Test-only: stub the interactive wizard. Production callers leave unset. */
42
42
  wizard?: (defaults: WizardDefaults) => Promise<WizardAnswers>;
43
+ /** Test-only: stub the interface read that decides whether to ask about UPnP. */
44
+ publicInterfaceAddress?: () => string | null;
43
45
  }
44
46
  export interface InstallResult {
45
47
  dataDir: string;
@@ -8,10 +8,11 @@
8
8
  * subsystem modules (wizard.ts, config.ts, service-host/{systemd,launchd,nssm}.ts,
9
9
  * browser.ts).
10
10
  */
11
- import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
11
+ import { existsSync, mkdirSync, rmSync } from 'node:fs';
12
12
  import { randomBytes } from 'node:crypto';
13
13
  import debug from 'debug';
14
- import { NatStore } from '../nat/index.js';
14
+ import { NatStore, publicInterfaceAddress } from '../nat/index.js';
15
+ import { packageVersion } from '../package-version.js';
15
16
  import { openBrowser } from './browser.js';
16
17
  import { writeHostConfig } from './config.js';
17
18
  import { configPath, defaultHostJs, defaultServiceDir, logsDir, } from './paths.js';
@@ -23,7 +24,7 @@ const log = debug('cadre:host:installer');
23
24
  export class Installer {
24
25
  constructor(opts = {}) {
25
26
  this.platform = opts.platform ?? detectPlatform();
26
- this.installerVersion = opts.installerVersion ?? readPackageVersion();
27
+ this.installerVersion = opts.installerVersion ?? packageVersion;
27
28
  }
28
29
  async install(opts) {
29
30
  if (opts.system) {
@@ -112,7 +113,6 @@ export class Installer {
112
113
  dataDir: opts.dataDir ?? defaults.dataDir,
113
114
  uiPort: opts.uiPort ?? defaults.uiPort,
114
115
  upnpEnabled: opts.noUpnp ? false : defaults.upnpEnabled,
115
- configureDdns: false,
116
116
  };
117
117
  }
118
118
  async resolveAnswers(opts) {
@@ -129,6 +129,9 @@ export class Installer {
129
129
  dataDir: opts.dataDir ?? platformDefaults.dataDir,
130
130
  uiPort: opts.uiPort ?? platformDefaults.uiPort,
131
131
  upnpEnabled: opts.noUpnp ? false : platformDefaults.upnpEnabled,
132
+ // A flag answers its question: the wizard asks only for what the command line left open.
133
+ given: { dataDir: opts.dataDir !== undefined, uiPort: opts.uiPort !== undefined, upnpEnabled: opts.noUpnp === true },
134
+ publicInterfaceIp: (opts.publicInterfaceAddress ?? publicInterfaceAddress)(),
132
135
  };
133
136
  const wizard = opts.wizard ?? runWizard;
134
137
  return await wizard(defaults);
@@ -143,18 +146,6 @@ function seedNatSettings(dataDir, upnpEnabled) {
143
146
  function randomInstallId() {
144
147
  return randomBytes(16).toString('hex');
145
148
  }
146
- function readPackageVersion() {
147
- // Best-effort: read the package.json next to dist/ at runtime.
148
- try {
149
- const here = new URL('../../package.json', import.meta.url);
150
- const raw = readFileSync(here, 'utf8');
151
- const parsed = JSON.parse(raw);
152
- return parsed.version ?? '0.0.0-unknown';
153
- }
154
- catch {
155
- return '0.0.0-unknown';
156
- }
157
- }
158
149
  // Exposed for tests that drive the wizard with a custom prompt fn.
159
150
  export { runWizard };
160
151
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/installer/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/installer/index.ts"],"names":[],"mappings":"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"}
@@ -10,12 +10,22 @@ export interface WizardDefaults {
10
10
  dataDir: string;
11
11
  uiPort: number;
12
12
  upnpEnabled: boolean;
13
+ /** Values a command-line flag already supplied: the wizard does not ask for them again. */
14
+ given?: {
15
+ dataDir?: boolean;
16
+ uiPort?: boolean;
17
+ upnpEnabled?: boolean;
18
+ };
19
+ /**
20
+ * The public IPv4 an interface holds (`publicInterfaceAddress`), when one does: the machine is
21
+ * reached directly (a VPS), so there is no router to ask about UPnP.
22
+ */
23
+ publicInterfaceIp?: string | null;
13
24
  }
14
25
  export interface WizardAnswers {
15
26
  dataDir: string;
16
27
  uiPort: number;
17
28
  upnpEnabled: boolean;
18
- configureDdns: boolean;
19
29
  }
20
30
  export declare const DEFAULT_UI_PORT = 8765;
21
31
  export declare function defaultsForPlatform(platform: SupportedPlatform): WizardDefaults;
@@ -23,6 +33,6 @@ export declare function defaultsForPlatform(platform: SupportedPlatform): Wizard
23
33
  * Drive the wizard with a function that returns the next user answer for
24
34
  * a prompt. Used both by the readline-backed `runWizard` and by tests.
25
35
  */
26
- export declare function runWizardWith(defaults: WizardDefaults, ask: (label: string, fallback: string) => Promise<string>): Promise<WizardAnswers>;
36
+ export declare function runWizardWith(defaults: WizardDefaults, ask: (label: string, fallback: string) => Promise<string>, say?: (line: string) => void): Promise<WizardAnswers>;
27
37
  /** Production entrypoint — drives the wizard over stdin/stdout. */
28
38
  export declare function runWizard(defaults: WizardDefaults): Promise<WizardAnswers>;
@@ -19,18 +19,26 @@ export function defaultsForPlatform(platform) {
19
19
  * Drive the wizard with a function that returns the next user answer for
20
20
  * a prompt. Used both by the readline-backed `runWizard` and by tests.
21
21
  */
22
- export async function runWizardWith(defaults, ask) {
23
- const dataDir = (await ask('Data directory', defaults.dataDir)).trim() || defaults.dataDir;
24
- const uiPort = parsePort(await ask('UI port (localhost only)', String(defaults.uiPort)), defaults.uiPort);
25
- const upnpEnabled = parseBool(await ask('Attempt UPnP port mapping on this network? [Y/n]', defaults.upnpEnabled ? 'Y' : 'n'), defaults.upnpEnabled);
26
- const configureDdns = parseBool(await ask('Configure DDNS now? [y/N]', 'N'), false);
27
- return { dataDir, uiPort, upnpEnabled, configureDdns };
22
+ export async function runWizardWith(defaults, ask, say = () => { }) {
23
+ const given = defaults.given ?? {};
24
+ const dataDir = given.dataDir ? defaults.dataDir : (await ask('Data directory', defaults.dataDir)).trim() || defaults.dataDir;
25
+ const uiPort = given.uiPort
26
+ ? defaults.uiPort
27
+ : parsePort(await ask('UI port (localhost only)', String(defaults.uiPort)), defaults.uiPort);
28
+ let upnpEnabled = defaults.upnpEnabled;
29
+ if (defaults.publicInterfaceIp) {
30
+ say(`This machine has a public IP address (${defaults.publicInterfaceIp}), so it is reached directly: no router, no UPnP.`);
31
+ }
32
+ else if (!given.upnpEnabled) {
33
+ upnpEnabled = parseBool(await ask('This machine is behind a router. Ask it to map ports automatically (UPnP)? [Y/n]', defaults.upnpEnabled ? 'Y' : 'n'), defaults.upnpEnabled);
34
+ }
35
+ return { dataDir, uiPort, upnpEnabled };
28
36
  }
29
37
  /** Production entrypoint — drives the wizard over stdin/stdout. */
30
38
  export async function runWizard(defaults) {
31
39
  const rl = createInterface({ input: process.stdin, output: process.stdout });
32
40
  try {
33
- return await runWizardWith(defaults, async (label, fallback) => prompt(rl, label, fallback));
41
+ return await runWizardWith(defaults, async (label, fallback) => prompt(rl, label, fallback), (line) => console.log(line));
34
42
  }
35
43
  finally {
36
44
  rl.close();
@@ -1 +1 @@
1
- {"version":3,"file":"wizard.js","sourceRoot":"","sources":["../../src/installer/wizard.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAuC,MAAM,eAAe,CAAC;AAErF,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAgB5C,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC;AAEpC,MAAM,UAAU,mBAAmB,CAAC,QAA2B;IAC7D,OAAO;QACL,OAAO,EAAE,cAAc,CAAC,QAAQ,CAAC;QACjC,MAAM,EAAE,eAAe;QACvB,WAAW,EAAE,IAAI;KAClB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAwB,EACxB,GAAyD;IAEzD,MAAM,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,gBAAgB,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,QAAQ,CAAC,OAAO,CAAC;IAC3F,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,0BAA0B,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC1G,MAAM,WAAW,GAAG,SAAS,CAC3B,MAAM,GAAG,CAAC,kDAAkD,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAC/F,QAAQ,CAAC,WAAW,CACrB,CAAC;IACF,MAAM,aAAa,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,2BAA2B,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;IACpF,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC;AACzD,CAAC;AAED,mEAAmE;AACnE,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,QAAwB;IACtD,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7E,IAAI,CAAC;QACH,OAAO,MAAM,aAAa,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC/F,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAED,SAAS,MAAM,CAAC,EAAqB,EAAE,KAAa,EAAE,QAAgB;IACpE,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE;QACrC,EAAE,CAAC,QAAQ,CAAC,GAAG,KAAK,KAAK,QAAQ,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,SAAS,CAAC,KAAa,EAAE,QAAgB;IAChD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,CAAC,OAAO;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC1B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,iBAAiB,KAAK,EAAE,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAS,SAAS,CAAC,KAAa,EAAE,QAAiB;IACjD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,IAAI,CAAC,OAAO;QAAE,OAAO,QAAQ,CAAC;IAC9B,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7D,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,IAAI,KAAK,CAAC,0BAA0B,KAAK,EAAE,CAAC,CAAC;AACrD,CAAC"}
1
+ {"version":3,"file":"wizard.js","sourceRoot":"","sources":["../../src/installer/wizard.ts"],"names":[],"mappings":"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"}
@@ -10,7 +10,7 @@ export { NAT_ADDRESS_RESTART_MIN_INTERVAL_MS } from './address-watch.js';
10
10
  export type { NodeAddressesStaleListener } from './address-watch.js';
11
11
  export { NatStore } from './nat-store.js';
12
12
  export { NatError, } from './types.js';
13
- export type { NatStatusSnapshot, NatGatewayStatus, NatDdnsStatus, NatSettingsFile, NatDdnsSettings, NatHandlers, NatErrorCode, DirectReachability, NodeReachability, NodeVerdict, PortKind, PortRoute, PortRouteSource, ManualForward, ManualForwardPatch, DdnsProviderInfo, DdnsConfigField, } from './types.js';
13
+ export type { NatStatusSnapshot, NatGatewayStatus, NatDdnsStatus, NatNetworkStatus, NetworkMode, NetworkSetting, NatSettingsFile, NatDdnsSettings, NatHandlers, NatErrorCode, DirectReachability, NodeReachability, NodeVerdict, PortKind, PortRoute, PortRouteSource, ManualForward, ManualForwardPatch, DdnsProviderInfo, DdnsConfigField, } from './types.js';
14
14
  export { ExternalIpDetector, isPlausibleIp, } from './external-ip.js';
15
15
  export type { ExternalIpResult, RouterIpProbe, ExternalIpDetectorOptions, } from './external-ip.js';
16
16
  export { UpnpPortMapper, pickLanAddress, DEFAULT_LEASE_TTL_MS, DEFAULT_REFRESH_MS, GATEWAY_DISCOVERY_TIMEOUT_MS, } from './port-mapper.js';
@@ -18,6 +18,7 @@ export type { PortMapper, GatewayInfo, PortMapRequest, PortMapResult, } from './
18
18
  export { evaluateNodeReachability, evaluateHostReachability } from './reachability.js';
19
19
  export type { NodeVerdictInput, NodeVerdictResult } from './reachability.js';
20
20
  export { buildPublicAddresses, isPublicIpv4 } from './address-resolver.js';
21
+ export { publicInterfaceAddress, effectiveNetworkMode } from './network-mode.js';
21
22
  export type { PublicAddressInput } from './address-resolver.js';
22
23
  export { BUILTIN_PROVIDERS, getProvider, listProviders, duckDnsProvider, DdnsUpdater, } from './ddns/index.js';
23
24
  export type { DdnsProvider, DdnsProviderDeps, DdnsUpdaterOptions, DdnsUpdateOutcome, } from './ddns/index.js';
package/dist/nat/index.js CHANGED
@@ -12,6 +12,7 @@ export { ExternalIpDetector, isPlausibleIp, } from './external-ip.js';
12
12
  export { UpnpPortMapper, pickLanAddress, DEFAULT_LEASE_TTL_MS, DEFAULT_REFRESH_MS, GATEWAY_DISCOVERY_TIMEOUT_MS, } from './port-mapper.js';
13
13
  export { evaluateNodeReachability, evaluateHostReachability } from './reachability.js';
14
14
  export { buildPublicAddresses, isPublicIpv4 } from './address-resolver.js';
15
+ export { publicInterfaceAddress, effectiveNetworkMode } from './network-mode.js';
15
16
  export { BUILTIN_PROVIDERS, getProvider, listProviders, duckDnsProvider, DdnsUpdater, } from './ddns/index.js';
16
17
  export { KeytarSecretsStore, FileSecretsStore, createSecretsStore, ddnsAccount, KEYTAR_SERVICE, } from './secrets/index.js';
17
18
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/nat/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,yBAAyB,EACzB,2BAA2B,GAC5B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,mCAAmC,EAAE,MAAM,oBAAoB,CAAC;AAEzE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EACL,QAAQ,GACT,MAAM,YAAY,CAAC;AAoBpB,OAAO,EACL,kBAAkB,EAClB,aAAa,GACd,MAAM,kBAAkB,CAAC;AAM1B,OAAO,EACL,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,kBAAkB,EAClB,4BAA4B,GAC7B,MAAM,kBAAkB,CAAC;AAO1B,OAAO,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAEvF,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE3E,OAAO,EACL,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,eAAe,EACf,WAAW,GACZ,MAAM,iBAAiB,CAAC;AAOzB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,WAAW,EACX,cAAc,GACf,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/nat/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,yBAAyB,EACzB,2BAA2B,GAC5B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,mCAAmC,EAAE,MAAM,oBAAoB,CAAC;AAEzE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EACL,QAAQ,GACT,MAAM,YAAY,CAAC;AAuBpB,OAAO,EACL,kBAAkB,EAClB,aAAa,GACd,MAAM,kBAAkB,CAAC;AAM1B,OAAO,EACL,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,kBAAkB,EAClB,4BAA4B,GAC7B,MAAM,kBAAkB,CAAC;AAO1B,OAAO,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAEvF,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEjF,OAAO,EACL,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,eAAe,EACf,WAAW,GACZ,MAAM,iBAAiB,CAAC;AAOzB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,WAAW,EACX,cAAc,GACf,MAAM,oBAAoB,CAAC"}
@@ -48,6 +48,8 @@ export interface NatServiceOptions {
48
48
  store?: NatStore;
49
49
  /** Lease TTL asked of the router. Default 1 h. */
50
50
  leaseTtlMs?: number;
51
+ /** Test-only: stub the interface read behind `network: auto`. Default: `publicInterfaceAddress()`. */
52
+ publicInterfaceAddress?: () => string | null;
51
53
  }
52
54
  /**
53
55
  * NatService — one mapping table for every node the host runs, keyed by node
@@ -92,6 +94,9 @@ export declare class NatService {
92
94
  private ddnsUpdater;
93
95
  private currentSettings;
94
96
  private gateway;
97
+ /** The public IPv4 an interface holds (`publicInterfaceAddress`), re-read at start and on every probe. */
98
+ private publicInterfaceIp;
99
+ private readonly readInterfaces;
95
100
  private latestIp;
96
101
  private lastTestedAt;
97
102
  private readonly table;
@@ -166,10 +171,11 @@ export declare class NatService {
166
171
  */
167
172
  renewMappings(): Promise<void>;
168
173
  /**
169
- * The 5-minute probe pass: while UPnP is on and no router has answered,
170
- * search again and map the running nodes as soon as one does — a host that
171
- * boots before its network, or a router that comes up later, is found here
172
- * rather than at the next restart; then re-detect the external IP.
174
+ * The 5-minute probe pass: re-read the interfaces and re-route every node when
175
+ * the mode `auto` lands on changes; while UPnP is in use and no router has
176
+ * answered, search again and map the running nodes as soon as one does — a
177
+ * host that boots before its network, or a router that comes up later, is
178
+ * found here rather than at the next restart; then re-detect the external IP.
173
179
  */
174
180
  redetect(): Promise<void>;
175
181
  /** Re-discover the router if needed, re-map everything, re-detect the IP, then return a fresh snapshot. */
@@ -244,8 +250,19 @@ export declare class NatService {
244
250
  private ipDetector;
245
251
  /** The detected external IP for display and DDNS: the public probe's, else the router's. */
246
252
  private detectedIp;
247
- /** The IP used in `/ip4/` addresses: only a public IPv4 counts. */
253
+ /** The IP used in `/ip4/` addresses: only a public IPv4 counts. Public mode prefers the interface's own. */
248
254
  private publicIpv4;
255
+ /**
256
+ * Re-read the interfaces (a VPS gains an address, a laptop moves networks); true when that
257
+ * changed the mode in effect, so the caller re-routes every node in the same pass.
258
+ */
259
+ private refreshNetworkMode;
260
+ /** The mode in effect: the `network` setting, or what `auto` reads from the interfaces. */
261
+ private networkMode;
262
+ /** UPnP is asked for nothing in public mode: there is no router to ask. */
263
+ private usesUpnp;
264
+ /** An interface holding the public address rules out carrier-grade NAT, whatever the probes say. */
265
+ private cgnatDetected;
249
266
  private describeNode;
250
267
  private hostKnown;
251
268
  private addressesFor;
@@ -7,6 +7,7 @@ import { evaluateHostReachability, evaluateNodeReachability } from './reachabili
7
7
  import { DdnsUpdater } from './ddns/updater.js';
8
8
  import { getProvider, listProviders } from './ddns/index.js';
9
9
  import { buildPublicAddresses, isPublicIpv4 } from './address-resolver.js';
10
+ import { effectiveNetworkMode, publicInterfaceAddress } from './network-mode.js';
10
11
  import { createSecretsStore, ddnsAccount } from './secrets/index.js';
11
12
  import { AddressWatch } from './address-watch.js';
12
13
  const log = debug('cadre:host:nat-service');
@@ -27,6 +28,10 @@ const PORT_KINDS = ['tcp', 'ws'];
27
28
  function noRoute(internalPort) {
28
29
  return { internalPort, externalPort: null, source: null, leaseExpiresAt: null, error: null };
29
30
  }
31
+ /** Public mode: the port is reached at its own number, with nothing to map. */
32
+ function directRoute(internalPort) {
33
+ return { internalPort, externalPort: internalPort, source: 'direct', leaseExpiresAt: null, error: null };
34
+ }
30
35
  function manualRoute(internalPort, externalPort) {
31
36
  return { internalPort, externalPort, source: 'manual', leaseExpiresAt: null, error: null };
32
37
  }
@@ -74,6 +79,8 @@ export class NatService {
74
79
  this.detector = null;
75
80
  this.ddnsUpdater = null;
76
81
  this.gateway = { found: false, lanAddress: null, routerExternalIp: null, lastError: null };
82
+ /** The public IPv4 an interface holds (`publicInterfaceAddress`), re-read at start and on every probe. */
83
+ this.publicInterfaceIp = null;
77
84
  this.latestIp = null;
78
85
  this.lastTestedAt = null;
79
86
  this.table = new Map();
@@ -94,6 +101,7 @@ export class NatService {
94
101
  this.secretsStore = opts.secretsStore ?? null;
95
102
  this.injectedDetector = opts.externalIpDetector ?? null;
96
103
  this.secretsRootDir = opts.rootDir;
104
+ this.readInterfaces = opts.publicInterfaceAddress ?? (() => publicInterfaceAddress());
97
105
  this.currentSettings = this.store.load();
98
106
  this.addressWatch = new AddressWatch(() => this.nowFn().getTime());
99
107
  }
@@ -110,7 +118,9 @@ export class NatService {
110
118
  if (!this.secretsStore) {
111
119
  this.secretsStore = await createSecretsStore(this.secretsRootDir);
112
120
  }
113
- if (this.currentSettings.upnpEnabled)
121
+ this.publicInterfaceIp = this.readInterfaces();
122
+ log('network mode %s (setting %s, public interface address %s)', this.networkMode(), this.currentSettings.network, this.publicInterfaceIp ?? 'none');
123
+ if (this.usesUpnp())
114
124
  await this.discoverGateway();
115
125
  await this.detectIp();
116
126
  this.ddnsUpdater = new DdnsUpdater({
@@ -168,11 +178,12 @@ export class NatService {
168
178
  // --- reads ---
169
179
  /** Cheap read of the current snapshot. */
170
180
  getStatus() {
171
- const cgnatDetected = this.latestIp?.cgnat ?? false;
181
+ const cgnatDetected = this.cgnatDetected();
172
182
  const nodes = [...this.table.values()]
173
183
  .sort((a, b) => a.nodeId.localeCompare(b.nodeId))
174
184
  .map((entry) => this.describeNode(entry, cgnatDetected));
175
185
  return {
186
+ network: { setting: this.currentSettings.network, mode: this.networkMode(), publicInterfaceIp: this.publicInterfaceIp },
176
187
  upnpEnabled: this.currentSettings.upnpEnabled,
177
188
  gateway: { ...this.gateway, routerExternalIp: this.latestIp?.routerIp ?? null },
178
189
  externalIp: this.detectedIp(),
@@ -247,19 +258,21 @@ export class NatService {
247
258
  return this.enqueue(() => this.renewOnce());
248
259
  }
249
260
  /**
250
- * The 5-minute probe pass: while UPnP is on and no router has answered,
251
- * search again and map the running nodes as soon as one does — a host that
252
- * boots before its network, or a router that comes up later, is found here
253
- * rather than at the next restart; then re-detect the external IP.
261
+ * The 5-minute probe pass: re-read the interfaces and re-route every node when
262
+ * the mode `auto` lands on changes; while UPnP is in use and no router has
263
+ * answered, search again and map the running nodes as soon as one does — a
264
+ * host that boots before its network, or a router that comes up later, is
265
+ * found here rather than at the next restart; then re-detect the external IP.
254
266
  */
255
267
  async redetect() {
256
- if (this.currentSettings.upnpEnabled && !this.gateway.found) {
257
- await this.enqueue(async () => {
268
+ await this.enqueue(async () => {
269
+ const modeChanged = this.refreshNetworkMode();
270
+ const gatewayWasFound = this.gateway.found;
271
+ if (this.usesUpnp() && !gatewayWasFound)
258
272
  await this.discoverGateway();
259
- if (this.gateway.found)
260
- await this.reconcileOnce({ mapMissing: true });
261
- });
262
- }
273
+ if (modeChanged || (!gatewayWasFound && this.gateway.found))
274
+ await this.reconcileOnce({ mapMissing: true });
275
+ });
263
276
  await this.detectIp();
264
277
  this.publish();
265
278
  }
@@ -267,7 +280,8 @@ export class NatService {
267
280
  /** Re-discover the router if needed, re-map everything, re-detect the IP, then return a fresh snapshot. */
268
281
  async testReachability() {
269
282
  await this.enqueue(async () => {
270
- if (this.currentSettings.upnpEnabled && !this.gateway.found)
283
+ this.refreshNetworkMode();
284
+ if (this.usesUpnp() && !this.gateway.found)
271
285
  await this.discoverGateway();
272
286
  await this.reconcileOnce({ mapMissing: false });
273
287
  await this.renewOnce();
@@ -283,7 +297,8 @@ export class NatService {
283
297
  /** Replace settings; persists to disk and reconfigures the runtime. */
284
298
  async putSettings(patch) {
285
299
  const next = this.store.update(patch);
286
- const upnpChanged = next.upnpEnabled !== this.currentSettings.upnpEnabled;
300
+ const wasUsingUpnp = this.usesUpnp();
301
+ const modeBefore = this.networkMode();
287
302
  const ddnsChanged = next.ddns.providerId !== this.currentSettings.ddns.providerId ||
288
303
  next.ddns.hostname !== this.currentSettings.ddns.hostname ||
289
304
  next.ddns.externallyManaged !== this.currentSettings.ddns.externallyManaged ||
@@ -293,12 +308,12 @@ export class NatService {
293
308
  if (ddnsChanged && this.ddnsUpdater) {
294
309
  await this.ddnsUpdater.updateSettings(next.ddns);
295
310
  }
296
- if (upnpChanged) {
297
- // Turning UPnP on with no gateway known yet: look for one before the
298
- // pass, so the pass can map. Turning it off: the pass releases every
299
- // `upnp` route and keeps the manual ones.
311
+ if (this.usesUpnp() !== wasUsingUpnp || this.networkMode() !== modeBefore) {
312
+ // UPnP coming into use (turned on, or leaving public mode) with no gateway known yet:
313
+ // look for one before the pass, so the pass can map. Going out of use: the pass
314
+ // releases every `upnp` route and keeps the manual ones; public mode adds `direct` ones.
300
315
  await this.enqueue(async () => {
301
- if (next.upnpEnabled && !this.gateway.found)
316
+ if (this.usesUpnp() && !this.gateway.found)
302
317
  await this.discoverGateway();
303
318
  await this.reconcileOnce({ mapMissing: true });
304
319
  });
@@ -464,9 +479,15 @@ export class NatService {
464
479
  entry.routes[kind] = manualRoute(route.internalPort, forward);
465
480
  continue;
466
481
  }
467
- if (route.source === 'manual') {
482
+ if (route.source === 'manual' || route.source === 'direct') {
468
483
  entry.routes[kind] = noRoute(route.internalPort);
469
484
  }
485
+ if (this.networkMode() === 'public') {
486
+ if (route.source === 'upnp')
487
+ await this.unmapRoute(entry, kind);
488
+ entry.routes[kind] = directRoute(route.internalPort);
489
+ continue;
490
+ }
470
491
  if (!this.currentSettings.upnpEnabled) {
471
492
  if (entry.routes[kind].source === 'upnp')
472
493
  await this.unmapRoute(entry, kind);
@@ -485,7 +506,7 @@ export class NatService {
485
506
  async renewOnce() {
486
507
  if (!this.started)
487
508
  return;
488
- if (!this.currentSettings.upnpEnabled || !this.gateway.found)
509
+ if (!this.usesUpnp() || !this.gateway.found)
489
510
  return;
490
511
  for (const entry of this.table.values()) {
491
512
  if (!this.started)
@@ -493,7 +514,7 @@ export class NatService {
493
514
  if (!entry.running)
494
515
  continue;
495
516
  for (const kind of this.kindsOf(entry)) {
496
- if (entry.routes[kind].source === 'manual')
517
+ if (entry.routes[kind].source !== 'upnp' && entry.routes[kind].source !== null)
497
518
  continue;
498
519
  await this.mapRoute(entry, kind);
499
520
  }
@@ -614,11 +635,37 @@ export class NatService {
614
635
  detectedIp() {
615
636
  return this.latestIp?.publicIp ?? this.latestIp?.routerIp ?? null;
616
637
  }
617
- /** The IP used in `/ip4/` addresses: only a public IPv4 counts. */
638
+ /** The IP used in `/ip4/` addresses: only a public IPv4 counts. Public mode prefers the interface's own. */
618
639
  publicIpv4() {
640
+ if (this.networkMode() === 'public' && this.publicInterfaceIp)
641
+ return this.publicInterfaceIp;
619
642
  const ip = this.detectedIp();
620
643
  return ip && isPublicIpv4(ip) ? ip : null;
621
644
  }
645
+ /**
646
+ * Re-read the interfaces (a VPS gains an address, a laptop moves networks); true when that
647
+ * changed the mode in effect, so the caller re-routes every node in the same pass.
648
+ */
649
+ refreshNetworkMode() {
650
+ const modeBefore = this.networkMode();
651
+ this.publicInterfaceIp = this.readInterfaces();
652
+ const changed = this.networkMode() !== modeBefore;
653
+ if (changed)
654
+ log('network mode changed to %s (public interface address %s)', this.networkMode(), this.publicInterfaceIp ?? 'none');
655
+ return changed;
656
+ }
657
+ /** The mode in effect: the `network` setting, or what `auto` reads from the interfaces. */
658
+ networkMode() {
659
+ return effectiveNetworkMode(this.currentSettings.network, this.publicInterfaceIp);
660
+ }
661
+ /** UPnP is asked for nothing in public mode: there is no router to ask. */
662
+ usesUpnp() {
663
+ return this.currentSettings.upnpEnabled && this.networkMode() === 'lan';
664
+ }
665
+ /** An interface holding the public address rules out carrier-grade NAT, whatever the probes say. */
666
+ cgnatDetected() {
667
+ return this.networkMode() === 'public' ? false : this.latestIp?.cgnat ?? false;
668
+ }
622
669
  // --- status assembly ---
623
670
  describeNode(entry, cgnatDetected) {
624
671
  const tcp = this.effectiveRoute(entry.routes.tcp);
@@ -628,7 +675,7 @@ export class NatService {
628
675
  ws,
629
676
  hostKnown: this.hostKnown(),
630
677
  cgnatDetected,
631
- upnpEnabled: this.currentSettings.upnpEnabled,
678
+ upnpEnabled: this.usesUpnp(),
632
679
  gatewayFound: this.gateway.found,
633
680
  lanAddress: this.gateway.lanAddress,
634
681
  });
@@ -649,7 +696,7 @@ export class NatService {
649
696
  return buildPublicAddresses({
650
697
  ddnsHostname: this.currentSettings.ddns.hostname,
651
698
  externalIp: this.publicIpv4(),
652
- cgnatDetected: this.latestIp?.cgnat ?? false,
699
+ cgnatDetected: this.cgnatDetected(),
653
700
  tcp,
654
701
  ws,
655
702
  });
@@ -658,13 +705,15 @@ export class NatService {
658
705
  const forward = this.currentSettings.forwards[nodeId]?.[kind];
659
706
  if (forward !== undefined)
660
707
  return manualRoute(internalPort, forward);
708
+ if (this.networkMode() === 'public')
709
+ return directRoute(internalPort);
661
710
  const existing = entry?.routes[kind];
662
711
  if (existing && existing.internalPort === internalPort) {
663
712
  const route = this.effectiveRoute(existing);
664
713
  if (route.source !== null || route.error !== null)
665
714
  return route;
666
715
  }
667
- if (this.currentSettings.upnpEnabled && this.gateway.found) {
716
+ if (this.usesUpnp() && this.gateway.found) {
668
717
  return { internalPort, externalPort: internalPort, source: 'upnp', leaseExpiresAt: null, error: null };
669
718
  }
670
719
  return noRoute(internalPort);