@fibscope/agent 0.1.1 → 0.1.2

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.
package/README.md CHANGED
@@ -32,7 +32,6 @@ The agent also supports `.fiber/agent.json`:
32
32
  "agentToken": "raw-token-issued-by-platform",
33
33
  "chain": "testnet",
34
34
  "fiberRpcUrl": "http://127.0.0.1:8247",
35
- "peerAddress": "/ip4/127.0.0.1/tcp/8248",
36
35
  "pushIntervalMs": 15000,
37
36
  "publicProfileEnabled": true,
38
37
  "overwriteNodeRegistration": false,
@@ -49,7 +48,7 @@ The agent also supports `.fiber/agent.json`:
49
48
  }
50
49
  ```
51
50
 
52
- `peerAddress` is the Fiber dial address advertised to Fibscope for public routing and channel creation. For local duo development this is usually a loopback multiaddr like `/ip4/127.0.0.1/tcp/8228`; for an internet-facing public node, set it to an address other nodes can actually dial.
51
+ The hosted gateway returns the public IPv4 it observes for the authenticated agent request. The agent combines that IP with the local FNN P2P port and reports the resulting public multiaddr. Loopback, wildcard, private, and documentation addresses are never published. Set `peerAddress` only when intentionally overriding automatic discovery with another publicly dialable multiaddr.
53
52
 
54
53
  ## Commands
55
54
 
@@ -75,7 +74,7 @@ The agent pushes to `POST /agent/v1/report` with:
75
74
 
76
75
  ```json
77
76
  {
78
- "agentVersion": "0.1.1",
77
+ "agentVersion": "0.1.2",
79
78
  "chain": "testnet",
80
79
  "collectedAt": 1710000000000,
81
80
  "node": {},
@@ -117,10 +116,8 @@ The Agent Setup page generates this command from a masked password field. The `f
117
116
 
118
117
  ## NPM Usage
119
118
 
120
- ```powershell
121
- npm install -g @fibscope/agent
122
- agent init
123
- agent start
119
+ ```bash
120
+ npm install -g @fibscope/agent@0.1.2 pm2 && agent init && pm2 start "$(command -v agent)" --name fibscope-agent --interpreter node -- start && pm2 save
124
121
  ```
125
122
 
126
123
  Or run without a global install:
package/index.mjs CHANGED
@@ -31,8 +31,9 @@ import { createTelemetryCollector } from "./lib/observe/collector.mjs";
31
31
  import { recommend } from "./lib/bridge/recommendation.mjs";
32
32
  import { virtualizePayment } from "./lib/route/route.mjs";
33
33
  import { chooseFnnInstance, describeFnnInstance, discoverFnnInstances, rpcResponds } from "./lib/fnn-discovery.mjs";
34
+ import { buildObservedPeerAddress, peerTcpPort, publishablePeerAddresses } from "./lib/public-peer-address.mjs";
34
35
 
35
- const AGENT_VERSION = "0.1.1";
36
+ const AGENT_VERSION = "0.1.2";
36
37
  const DEFAULT_CONFIG_PATH = ".fiber/agent.json";
37
38
  const DEFAULT_STATUS_PATH = ".fiber/agent-status.json";
38
39
  const DEFAULT_PUSH_INTERVAL_MS = 15_000;
@@ -367,6 +368,8 @@ export async function createRuntime(options = {}) {
367
368
  const config = await loadConfig(configPath, options);
368
369
  await ensureFnnPreflight(config, statusPath, options);
369
370
  const remoteConfig = await fetchAgentConfig(config).catch(() => undefined);
371
+ config.observedPublicIp = remoteConfig?.observedPublicIp ?? config.observedPublicIp;
372
+ config.observedPeerAddress = remoteConfig?.observedPeerAddress ?? config.observedPeerAddress;
370
373
  if (!config.nodeDisplayName && remoteConfig?.node?.displayName) {
371
374
  config.nodeDisplayName = remoteConfig.node.displayName;
372
375
  }
@@ -499,19 +502,29 @@ export async function collectReport(config, previousReport = undefined) {
499
502
  async function enrichReportNode(node, config) {
500
503
  if (!node || typeof node !== "object") return node;
501
504
  const displayName = textValue(config.nodeDisplayName);
502
- const addresses = uniqueStrings([
505
+ const candidates = uniqueStrings([
503
506
  ...readAddressList(node.addresses),
504
507
  ...readAddressList(node.address),
505
508
  ...configuredPeerAddresses(config),
506
509
  ...await inferPeerAddressesFromLocalFnn(config),
507
510
  ]);
508
- if (!addresses.length && !displayName) return node;
509
- return {
511
+ const addresses = publishablePeerAddresses([
512
+ config.observedPeerAddress,
513
+ buildObservedPeerAddress(config.observedPublicIp, [config.fnnP2pListenAddr, ...candidates]),
514
+ ...candidates,
515
+ ]);
516
+ const enriched = {
510
517
  ...node,
511
518
  ...(displayName ? { displayName } : {}),
512
- address: node.address ?? addresses[0],
513
- addresses,
514
519
  };
520
+ if (addresses.length) {
521
+ enriched.address = addresses[0];
522
+ enriched.addresses = addresses;
523
+ } else {
524
+ delete enriched.address;
525
+ delete enriched.addresses;
526
+ }
527
+ return enriched;
515
528
  }
516
529
 
517
530
  function configuredPeerAddresses(config) {
@@ -2301,7 +2314,11 @@ function resolveReportUrl(config = {}) {
2301
2314
  }
2302
2315
 
2303
2316
  async function fetchAgentConfig(config) {
2304
- const configUrl = config.configUrl ?? config.reportUrl.replace(/\/report\/?$/, "/config");
2317
+ const configUrl = new URL(config.configUrl ?? config.reportUrl.replace(/\/report\/?$/, "/config"));
2318
+ configUrl.searchParams.set("p2pPort", String(peerTcpPort([
2319
+ config.fnnP2pListenAddr,
2320
+ ...configuredPeerAddresses(config),
2321
+ ], 8228)));
2305
2322
  const response = await fetch(configUrl, {
2306
2323
  headers: {
2307
2324
  "x-agent-token": config.agentToken,
@@ -2539,15 +2556,12 @@ async function enrichConfigFromMatchingFnn(config) {
2539
2556
  }
2540
2557
 
2541
2558
  function applyFnnMatchToAgentConfig(config, match) {
2542
- const peerAddress = normalizeLocalListenAddress(match?.p2pListenAddr);
2543
- const hasConfiguredPeerAddress = configuredPeerAddresses(config).length > 0;
2544
2559
  return {
2545
2560
  ...config,
2546
2561
  ckbRpcUrl: config.ckbRpcUrl ?? match.ckbRpcUrl,
2547
2562
  fnnBaseDir: config.fnnBaseDir ?? match.baseDir,
2548
2563
  fnnConfigPath: config.fnnConfigPath ?? match.configPath,
2549
2564
  fnnP2pListenAddr: config.fnnP2pListenAddr ?? match.p2pListenAddr,
2550
- ...(!hasConfiguredPeerAddress && peerAddress ? { peerAddress } : {}),
2551
2565
  chain: config.chain ?? match.network,
2552
2566
  };
2553
2567
  }
@@ -0,0 +1,54 @@
1
+ export function buildObservedPeerAddress(observedPublicIp, candidates = [], fallbackPort = 8228) {
2
+ const ip = normalizePublicIpv4(observedPublicIp);
3
+ if (!ip) return undefined;
4
+ const port = peerTcpPort(candidates, fallbackPort);
5
+ return `/ip4/${ip}/tcp/${port}`;
6
+ }
7
+
8
+ export function peerTcpPort(values, fallback = 8228) {
9
+ for (const value of flatStrings(values)) {
10
+ const match = value.match(/\/tcp\/(\d{1,5})(?:\/|$)/i);
11
+ const port = Number(match?.[1]);
12
+ if (Number.isInteger(port) && port > 0 && port <= 65_535) return port;
13
+ }
14
+ return fallback;
15
+ }
16
+
17
+ export function publishablePeerAddresses(values) {
18
+ return [...new Set(flatStrings(values).filter(isPublishablePeerAddress))];
19
+ }
20
+
21
+ export function isPublishablePeerAddress(value) {
22
+ const address = String(value ?? "").trim();
23
+ if (!address) return false;
24
+ const ipv4 = /\/ip4\/([^/]+)/i.exec(address)?.[1];
25
+ if (ipv4) return Boolean(normalizePublicIpv4(ipv4));
26
+ const ipv6 = /\/ip6\/([^/]+)/i.exec(address)?.[1]?.toLowerCase();
27
+ if (ipv6) {
28
+ return ipv6 !== "::" && ipv6 !== "::1" && !ipv6.startsWith("fc") && !ipv6.startsWith("fd") && !/^fe[89ab]/.test(ipv6);
29
+ }
30
+ return /\/(?:dns|dns4|dns6)\/[^/]+/i.test(address);
31
+ }
32
+
33
+ export function normalizePublicIpv4(value) {
34
+ let candidate = String(value ?? "").trim();
35
+ if (candidate.startsWith("::ffff:")) candidate = candidate.slice(7);
36
+ const parts = candidate.split(".").map(Number);
37
+ if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return undefined;
38
+ const [a, b, c] = parts;
39
+ if (a === 0 || a === 10 || a === 127 || a >= 224) return undefined;
40
+ if (a === 100 && b >= 64 && b <= 127) return undefined;
41
+ if (a === 169 && b === 254) return undefined;
42
+ if (a === 172 && b >= 16 && b <= 31) return undefined;
43
+ if (a === 192 && b === 168) return undefined;
44
+ if (a === 198 && (b === 18 || b === 19)) return undefined;
45
+ if (a === 192 && b === 0 && (c === 0 || c === 2)) return undefined;
46
+ if (a === 198 && b === 51 && c === 100) return undefined;
47
+ if (a === 203 && b === 0 && c === 113) return undefined;
48
+ return parts.join(".");
49
+ }
50
+
51
+ function flatStrings(values) {
52
+ const input = Array.isArray(values) ? values.flat(Infinity) : [values];
53
+ return input.map((value) => String(value ?? "").trim()).filter(Boolean);
54
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fibscope/agent",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "Fibscope Agent for Fiber node owners.",
6
6
  "bin": {
@@ -16,7 +16,7 @@
16
16
  ".env.example"
17
17
  ],
18
18
  "scripts": {
19
- "check": "node --check index.mjs && node --check lib/pulse/index.mjs && node --check lib/pulse/engine.mjs && node --check lib/pulse/snapshot.mjs && node --check lib/pulse/intelligence.mjs && node --check lib/pulse/providers.mjs && node --check lib/pulse/server.mjs && node --check lib/observe/collector.mjs && node --check lib/bridge/recommendation.mjs && node --check lib/route/route.mjs && node --check lib/fnn-discovery.mjs",
19
+ "check": "node --check index.mjs && node --check lib/public-peer-address.mjs && node --check lib/pulse/index.mjs && node --check lib/pulse/engine.mjs && node --check lib/pulse/snapshot.mjs && node --check lib/pulse/intelligence.mjs && node --check lib/pulse/providers.mjs && node --check lib/pulse/server.mjs && node --check lib/observe/collector.mjs && node --check lib/bridge/recommendation.mjs && node --check lib/route/route.mjs && node --check lib/fnn-discovery.mjs && node --test test/*.test.mjs",
20
20
  "pack:dry-run": "npm pack --dry-run"
21
21
  },
22
22
  "dependencies": {