@fibscope/agent 0.1.2 → 0.1.3

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/.env.example CHANGED
@@ -5,3 +5,5 @@ FIBSCOPE_AGENT_CHAIN=testnet
5
5
  FIBSCOPE_AGENT_FIBER_RPC_URL=http://127.0.0.1:8227
6
6
  FIBSCOPE_AGENT_PUSH_INTERVAL_MS=15000
7
7
  FIBSCOPE_AGENT_PUBLIC_PROFILE=false
8
+ # Optional override; by default the agent discovers its own public IPv4.
9
+ # FIBSCOPE_AGENT_PUBLIC_IP=54.179.226.154
package/README.md CHANGED
@@ -48,7 +48,7 @@ The agent also supports `.fiber/agent.json`:
48
48
  }
49
49
  ```
50
50
 
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.
51
+ The agent discovers its own public IPv4 through `https://api.ipify.org`, combines it with the local FNN P2P port, and includes both the IP and resulting public multiaddr in registration and report requests. Loopback, wildcard, private, and documentation addresses are never published. Set `FIBSCOPE_AGENT_PUBLIC_IP` only when intentionally overriding automatic discovery, or `FIBSCOPE_AGENT_PUBLIC_IP_URL` to use another compatible JSON or plain-text discovery endpoint.
52
52
 
53
53
  ## Commands
54
54
 
@@ -74,9 +74,11 @@ The agent pushes to `POST /agent/v1/report` with:
74
74
 
75
75
  ```json
76
76
  {
77
- "agentVersion": "0.1.2",
77
+ "agentVersion": "0.1.3",
78
78
  "chain": "testnet",
79
79
  "collectedAt": 1710000000000,
80
+ "publicIp": "54.179.226.154",
81
+ "peerAddress": "/ip4/54.179.226.154/tcp/8228",
80
82
  "node": {},
81
83
  "peers": [],
82
84
  "channels": [],
@@ -117,7 +119,7 @@ The Agent Setup page generates this command from a masked password field. The `f
117
119
  ## NPM Usage
118
120
 
119
121
  ```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
122
+ npm install -g @fibscope/agent pm2 && agent init && pm2 start "$(command -v agent)" --name fibscope-agent --interpreter node -- start && pm2 save
121
123
  ```
122
124
 
123
125
  Or run without a global install:
package/index.mjs CHANGED
@@ -31,15 +31,17 @@ 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
+ import { buildObservedPeerAddress, discoverPublicIpv4, publishablePeerAddresses } from "./lib/public-peer-address.mjs";
35
35
 
36
- const AGENT_VERSION = "0.1.2";
36
+ const AGENT_VERSION = "0.1.3";
37
37
  const DEFAULT_CONFIG_PATH = ".fiber/agent.json";
38
38
  const DEFAULT_STATUS_PATH = ".fiber/agent-status.json";
39
39
  const DEFAULT_PUSH_INTERVAL_MS = 15_000;
40
40
  const MAX_BACKOFF_MS = 300_000;
41
41
  const DEFAULT_PULSE_MODULE = new URL("./lib/pulse/index.mjs", import.meta.url).href;
42
42
  const DEFAULT_FNN_PREFLIGHT_TIMEOUT_MS = 2_500;
43
+ const DEFAULT_PUBLIC_IP_DISCOVERY_URL = "https://api.ipify.org?format=json";
44
+ const DEFAULT_PUBLIC_IP_DISCOVERY_TIMEOUT_MS = 5_000;
43
45
  const DEFAULT_REPORT_URL = "https://ejchwtvdxpyojxsursmh.supabase.co/functions/v1/fibscope-gateway/agent/v1/report";
44
46
  const AGENT_ENV_PATHS = [resolve("agent/.env"), resolve(".env")];
45
47
  const ENV_REPORT_URL = "FIBGATE";
@@ -367,9 +369,8 @@ export async function createRuntime(options = {}) {
367
369
  const statusPath = resolve(options.status ?? DEFAULT_STATUS_PATH);
368
370
  const config = await loadConfig(configPath, options);
369
371
  await ensureFnnPreflight(config, statusPath, options);
372
+ await resolveAgentPublicIdentity(config);
370
373
  const remoteConfig = await fetchAgentConfig(config).catch(() => undefined);
371
- config.observedPublicIp = remoteConfig?.observedPublicIp ?? config.observedPublicIp;
372
- config.observedPeerAddress = remoteConfig?.observedPeerAddress ?? config.observedPeerAddress;
373
374
  if (!config.nodeDisplayName && remoteConfig?.node?.displayName) {
374
375
  config.nodeDisplayName = remoteConfig.node.displayName;
375
376
  }
@@ -481,6 +482,8 @@ export async function collectReport(config, previousReport = undefined) {
481
482
  agentVersion: AGENT_VERSION,
482
483
  chain: config.chain ?? "testnet",
483
484
  collectedAt,
485
+ publicIp: config.publicIp,
486
+ peerAddress: config.publicPeerAddress,
484
487
  node: state.node,
485
488
  peers: state.peers ?? [],
486
489
  channels: state.channels ?? [],
@@ -509,8 +512,8 @@ async function enrichReportNode(node, config) {
509
512
  ...await inferPeerAddressesFromLocalFnn(config),
510
513
  ]);
511
514
  const addresses = publishablePeerAddresses([
512
- config.observedPeerAddress,
513
- buildObservedPeerAddress(config.observedPublicIp, [config.fnnP2pListenAddr, ...candidates]),
515
+ config.publicPeerAddress,
516
+ buildObservedPeerAddress(config.publicIp, [config.fnnP2pListenAddr, ...candidates]),
514
517
  ...candidates,
515
518
  ]);
516
519
  const enriched = {
@@ -2294,6 +2297,8 @@ async function loadConfig(configPath, options = {}) {
2294
2297
  ckbIndexerUrl: process.env.FIBSCOPE_AGENT_CKB_INDEXER_URL,
2295
2298
  walletPath: process.env.FIBSCOPE_AGENT_WALLET_PATH ?? envValue(ENV_WALLET_PATH),
2296
2299
  peerAddress: process.env.FIBSCOPE_AGENT_PEER_ADDRESS,
2300
+ publicIp: process.env.FIBSCOPE_AGENT_PUBLIC_IP,
2301
+ publicIpDiscoveryUrl: process.env.FIBSCOPE_AGENT_PUBLIC_IP_URL,
2297
2302
  };
2298
2303
  label = "agent/.env";
2299
2304
  } else if (!existsSync(configPath)) {
@@ -2315,10 +2320,6 @@ function resolveReportUrl(config = {}) {
2315
2320
 
2316
2321
  async function fetchAgentConfig(config) {
2317
2322
  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)));
2322
2323
  const response = await fetch(configUrl, {
2323
2324
  headers: {
2324
2325
  "x-agent-token": config.agentToken,
@@ -2368,6 +2369,8 @@ async function registerAgentNode(config, report, remoteConfig) {
2368
2369
  displayName: config.nodeDisplayName ?? remoteConfig?.node?.displayName,
2369
2370
  publicProfileEnabled: Boolean(report.publicProfileEnabled),
2370
2371
  overwrite: Boolean(config.overwriteNodeRegistration),
2372
+ publicIp: report.publicIp ?? config.publicIp,
2373
+ peerAddress: report.peerAddress ?? config.publicPeerAddress,
2371
2374
  node: report.node ?? {},
2372
2375
  }),
2373
2376
  });
@@ -2505,6 +2508,8 @@ function normalizeConfig(config, label) {
2505
2508
  ckbRpcUrl: process.env.FIBSCOPE_AGENT_CKB_RPC_URL ?? envValue(ENV_CKB_RPC_URL) ?? config.ckbRpcUrl,
2506
2509
  ckbIndexerUrl: process.env.FIBSCOPE_AGENT_CKB_INDEXER_URL ?? config.ckbIndexerUrl,
2507
2510
  walletPath: process.env.FIBSCOPE_AGENT_WALLET_PATH ?? envValue(ENV_WALLET_PATH) ?? config.walletPath,
2511
+ publicIp: process.env.FIBSCOPE_AGENT_PUBLIC_IP ?? config.publicIp,
2512
+ publicIpDiscoveryUrl: process.env.FIBSCOPE_AGENT_PUBLIC_IP_URL ?? config.publicIpDiscoveryUrl,
2508
2513
  pushIntervalMs: positiveNumber(config.pushIntervalMs, DEFAULT_PUSH_INTERVAL_MS),
2509
2514
  commandPollMs: positiveNumber(config.commandPollMs, DEFAULT_COMMAND_POLL_MS),
2510
2515
  knownPeerReconnectMs: positiveNumber(config.knownPeerReconnectMs, DEFAULT_KNOWN_PEER_RECONNECT_MS),
@@ -2516,6 +2521,25 @@ function normalizeConfig(config, label) {
2516
2521
  return normalized;
2517
2522
  }
2518
2523
 
2524
+ async function resolveAgentPublicIdentity(config) {
2525
+ try {
2526
+ const publicIp = await discoverPublicIpv4({
2527
+ publicIp: config.publicIp,
2528
+ endpoint: config.publicIpDiscoveryUrl ?? DEFAULT_PUBLIC_IP_DISCOVERY_URL,
2529
+ timeoutMs: config.publicIpDiscoveryTimeoutMs ?? DEFAULT_PUBLIC_IP_DISCOVERY_TIMEOUT_MS,
2530
+ });
2531
+ config.publicIp = publicIp;
2532
+ config.publicPeerAddress = buildObservedPeerAddress(publicIp, [
2533
+ config.fnnP2pListenAddr,
2534
+ ...configuredPeerAddresses(config),
2535
+ ]);
2536
+ } catch (error) {
2537
+ console.warn(`Fibscope agent public IP unavailable: ${message(error)}`);
2538
+ config.publicIp = undefined;
2539
+ config.publicPeerAddress = publishablePeerAddresses(configuredPeerAddresses(config))[0];
2540
+ }
2541
+ }
2542
+
2519
2543
  async function bindAgentConfigToFnn(config, label, options = {}) {
2520
2544
  const next = { ...config };
2521
2545
  if (options.fiberRpcUrl) {
@@ -5,6 +5,39 @@ export function buildObservedPeerAddress(observedPublicIp, candidates = [], fall
5
5
  return `/ip4/${ip}/tcp/${port}`;
6
6
  }
7
7
 
8
+ export async function discoverPublicIpv4(options = {}) {
9
+ const configured = normalizePublicIpv4(options.publicIp);
10
+ if (configured) return configured;
11
+
12
+ const endpoint = String(options.endpoint ?? "https://api.ipify.org?format=json").trim();
13
+ if (!endpoint) throw new Error("Public IPv4 discovery endpoint is not configured.");
14
+ const timeoutMs = positiveTimeout(options.timeoutMs, 5_000);
15
+ const controller = new AbortController();
16
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
17
+ try {
18
+ const response = await (options.fetchImpl ?? fetch)(endpoint, {
19
+ headers: { accept: "application/json, text/plain;q=0.9" },
20
+ signal: controller.signal,
21
+ });
22
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
23
+ const body = await response.text();
24
+ let candidate = body;
25
+ try {
26
+ candidate = JSON.parse(body)?.ip ?? body;
27
+ } catch {
28
+ // Configurable plain-text IP endpoints are supported too.
29
+ }
30
+ const publicIp = normalizePublicIpv4(candidate);
31
+ if (!publicIp) throw new Error("response did not contain a public IPv4 address");
32
+ return publicIp;
33
+ } catch (error) {
34
+ const detail = error instanceof Error ? error.message : String(error);
35
+ throw new Error(`Public IPv4 discovery failed at ${endpoint}: ${detail}`);
36
+ } finally {
37
+ clearTimeout(timer);
38
+ }
39
+ }
40
+
8
41
  export function peerTcpPort(values, fallback = 8228) {
9
42
  for (const value of flatStrings(values)) {
10
43
  const match = value.match(/\/tcp\/(\d{1,5})(?:\/|$)/i);
@@ -52,3 +85,8 @@ function flatStrings(values) {
52
85
  const input = Array.isArray(values) ? values.flat(Infinity) : [values];
53
86
  return input.map((value) => String(value ?? "").trim()).filter(Boolean);
54
87
  }
88
+
89
+ function positiveTimeout(value, fallback) {
90
+ const number = Number(value);
91
+ return Number.isFinite(number) && number > 0 ? number : fallback;
92
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fibscope/agent",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "Fibscope Agent for Fiber node owners.",
6
6
  "bin": {