@bulolo/hermes-link 0.2.6 → 0.2.8

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.
@@ -1462,29 +1462,20 @@ function normalizeLogLevel(level) {
1462
1462
  return defaultLinkConfig.logLevel;
1463
1463
  }
1464
1464
  function normalizeLanHost(value) {
1465
- if (value === null || value === void 0) {
1466
- return null;
1467
- }
1468
- if (typeof value !== "string") {
1469
- return null;
1470
- }
1465
+ if (value === null || value === void 0) return null;
1466
+ if (typeof value !== "string") return null;
1471
1467
  const host = value.trim().replace(/^\[/u, "").replace(/\]$/u, "");
1472
- if (!host) {
1473
- return null;
1474
- }
1475
- if (!isUsableLanIpv4(host)) {
1476
- return null;
1477
- }
1468
+ if (!host) return null;
1469
+ if (!isValidHostIpv4(host)) return null;
1478
1470
  return host;
1479
1471
  }
1480
- function isUsableLanIpv4(value) {
1472
+ function isValidHostIpv4(value) {
1481
1473
  const parts = value.split(".").map((part) => Number.parseInt(part, 10));
1482
1474
  if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
1483
1475
  return false;
1484
1476
  }
1485
- const [first, second, , fourth] = parts;
1486
- const privateRange = first === 10 || first === 172 && second >= 16 && second <= 31 || first === 192 && second === 168;
1487
- return privateRange && fourth !== 0 && fourth !== 255;
1477
+ const [, , , fourth] = parts;
1478
+ return fourth !== 0 && fourth !== 255;
1488
1479
  }
1489
1480
 
1490
1481
  // src/network/topology.ts
@@ -1517,7 +1508,7 @@ function discoverLanIpsFromInterfaces(interfaces) {
1517
1508
  for (const [name, items] of Object.entries(interfaces)) {
1518
1509
  if (shouldIgnoreInterface(name)) continue;
1519
1510
  for (const item of items ?? []) {
1520
- if (!item.internal && item.address && item.family === "IPv4" && isUsableLanIpv42(item.address, item.netmask)) {
1511
+ if (!item.internal && item.address && item.family === "IPv4" && isUsableLanIpv4(item.address, item.netmask)) {
1521
1512
  candidates.push({ name, address: item.address });
1522
1513
  }
1523
1514
  }
@@ -1529,33 +1520,51 @@ function discoverLanIpsFromInterfaces(interfaces) {
1529
1520
  }
1530
1521
  async function observePublicRoute(options) {
1531
1522
  const fetcher = options.fetchImpl ?? fetch;
1532
- const response = await fetcher(
1533
- `${options.relayBaseUrl.replace(/\/+$/u, "")}/api/v1/relay/public-route/observe`,
1534
- {
1535
- method: "POST",
1536
- headers: {
1537
- "content-type": "application/json",
1538
- ...options.relayBootstrapToken ? { authorization: `Bearer ${options.relayBootstrapToken}` } : {}
1539
- },
1540
- body: JSON.stringify({
1541
- install_id: options.installId,
1542
- link_id: options.linkId,
1543
- public_key_pem: options.publicKeyPem
1544
- })
1545
- }
1546
- );
1547
- const payload = await response.json().catch(() => null);
1548
- const record = typeof payload?.record === "object" && payload.record !== null ? payload.record : null;
1549
- const observed = typeof payload?.observed === "object" && payload.observed !== null ? payload.observed : null;
1550
- const values = [
1551
- readIpRecord(record?.ipv4),
1552
- readIpRecord(record?.ipv6),
1553
- typeof observed?.ip === "string" ? observed.ip : null
1554
- ].filter((v) => Boolean(v));
1555
- return {
1556
- publicIpv4s: unique(values.filter(isUsablePublicIpv4)),
1557
- publicIpv6s: unique(values.filter(isUsablePublicIpv6))
1558
- };
1523
+ const simpleIp = await fetchPublicIpSimple(fetcher).catch(() => null);
1524
+ try {
1525
+ const response = await fetcher(
1526
+ `${options.relayBaseUrl.replace(/\/+$/u, "")}/api/v1/relay/public-route/observe`,
1527
+ {
1528
+ method: "POST",
1529
+ headers: {
1530
+ "content-type": "application/json",
1531
+ ...options.relayBootstrapToken ? { authorization: `Bearer ${options.relayBootstrapToken}` } : {}
1532
+ },
1533
+ body: JSON.stringify({
1534
+ install_id: options.installId,
1535
+ link_id: options.linkId,
1536
+ public_key_pem: options.publicKeyPem
1537
+ }),
1538
+ signal: AbortSignal.timeout(5e3)
1539
+ }
1540
+ );
1541
+ const payload = await response.json().catch(() => null);
1542
+ const record = typeof payload?.record === "object" && payload.record !== null ? payload.record : null;
1543
+ const observed = typeof payload?.observed === "object" && payload.observed !== null ? payload.observed : null;
1544
+ const values = [
1545
+ simpleIp,
1546
+ readIpRecord(record?.ipv4),
1547
+ readIpRecord(record?.ipv6),
1548
+ typeof observed?.ip === "string" ? observed.ip : null
1549
+ ].filter((v) => Boolean(v));
1550
+ return {
1551
+ publicIpv4s: unique(values.filter(isUsablePublicIpv4)),
1552
+ publicIpv6s: unique(values.filter(isUsablePublicIpv6))
1553
+ };
1554
+ } catch {
1555
+ const values = [simpleIp].filter((v) => Boolean(v));
1556
+ return {
1557
+ publicIpv4s: unique(values.filter(isUsablePublicIpv4)),
1558
+ publicIpv6s: []
1559
+ };
1560
+ }
1561
+ }
1562
+ async function fetchPublicIpSimple(fetcher) {
1563
+ const res = await fetcher("https://api.ipify.org?format=json", {
1564
+ signal: AbortSignal.timeout(4e3)
1565
+ });
1566
+ const data = await res.json();
1567
+ return typeof data.ip === "string" && data.ip.trim() ? data.ip.trim() : null;
1559
1568
  }
1560
1569
  function readIpRecord(value) {
1561
1570
  if (typeof value !== "object" || value === null) return null;
@@ -1575,7 +1584,7 @@ function compareLanCandidate(left, right) {
1575
1584
  function interfacePriority(name) {
1576
1585
  return /^(en|eth|wlan|wi-fi|wifi)/iu.test(name) ? 0 : 1;
1577
1586
  }
1578
- function isUsableLanIpv42(address, netmask) {
1587
+ function isUsableLanIpv4(address, netmask) {
1579
1588
  return isPrivateIpv4(address) && !isNetworkOrBroadcastIpv4Address(address, netmask);
1580
1589
  }
1581
1590
  function isUsablePublicIpv4(address) {
@@ -3506,8 +3515,9 @@ async function startLinkService(options) {
3506
3515
  const statsRouter = createStatisticsRouter({ db, paths });
3507
3516
  app.use(statsRouter.routes());
3508
3517
  app.use(statsRouter.allowedMethods());
3518
+ const listenHost = process.env.HERMESLINK_LISTEN_HOST ?? "0.0.0.0";
3509
3519
  const server = await new Promise((resolve, reject) => {
3510
- const s = app.listen(config.port, "127.0.0.1", () => resolve(s));
3520
+ const s = app.listen(config.port, listenHost, () => resolve(s));
3511
3521
  s.once("error", reject);
3512
3522
  });
3513
3523
  const relayClient = new RelayClient({
package/dist/cli/index.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  saveConfig,
24
24
  startLinkService,
25
25
  writeJsonFile
26
- } from "../chunk-YARHXGP4.js";
26
+ } from "../chunk-Q546CZFY.js";
27
27
 
28
28
  // src/cli/index.ts
29
29
  import { mkdir as mkdir3 } from "fs/promises";
@@ -275,7 +275,7 @@ async function runPairingPreflight(options) {
275
275
  if (options.openBrowser !== false) {
276
276
  await openSystemBrowser(pairingUrl).catch(() => void 0);
277
277
  }
278
- return { pairingUrl, connectToken: token.token };
278
+ return { pairingUrl, connectToken: token.token, bestUrl };
279
279
  }
280
280
  function buildPairingUrl(params) {
281
281
  const qs = new URLSearchParams({
@@ -288,10 +288,6 @@ function buildPairingUrl(params) {
288
288
  if (params.publicUrls.length > 0) qs.set("public_urls", params.publicUrls.join(","));
289
289
  return `hermesapp://pair?${qs.toString()}`;
290
290
  }
291
- function buildLocalPairingPageUrl(port, connectToken) {
292
- const qs = new URLSearchParams({ connect_token: connectToken });
293
- return `http://127.0.0.1:${port}/pair?${qs.toString()}`;
294
- }
295
291
 
296
292
  // src/cli/index.ts
297
293
  var args = process.argv.slice(2);
@@ -447,13 +443,14 @@ async function cmdPair(paths) {
447
443
  return;
448
444
  }
449
445
  const result = await runPairingPreflight({ identity, config, paths });
450
- const localPageUrl = buildLocalPairingPageUrl(config.port, result.connectToken);
446
+ const pageBase = result.bestUrl.replace(/\/+$/u, "");
447
+ const pageUrl = `${pageBase}/pair?connect_token=${encodeURIComponent(result.connectToken)}`;
451
448
  process.stdout.write("\n");
452
449
  qrcode.generate(result.pairingUrl, { small: true });
453
450
  process.stdout.write(`
454
451
  Pairing URL: ${result.pairingUrl}
455
452
  `);
456
- process.stdout.write(`Pairing page: ${localPageUrl}
453
+ process.stdout.write(`Pairing page: ${pageUrl}
457
454
  `);
458
455
  process.stdout.write(`Connect token: ${result.connectToken}
459
456
  `);
package/dist/http/app.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  startLinkService
3
- } from "../chunk-YARHXGP4.js";
3
+ } from "../chunk-Q546CZFY.js";
4
4
  export {
5
5
  startLinkService
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bulolo/hermes-link",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Hermes Link companion service and CLI for connecting hermes-agent through zhiji",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,7 +36,7 @@
36
36
  "prepack": "npm run build",
37
37
  "start": "node ./dist/cli/index.js",
38
38
  "test": "vitest",
39
- "publish:npm": "npm publish --access public"
39
+ "publish:npm": "npm publish --access public --registry https://registry.npmjs.org"
40
40
  },
41
41
  "dependencies": {
42
42
  "@koa/cors": "^5.0.0",