@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 +4 -7
- package/index.mjs +24 -10
- package/lib/public-peer-address.mjs +54 -0
- package/package.json +2 -2
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
|
-
|
|
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.
|
|
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
|
-
```
|
|
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.
|
|
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
|
|
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
|
-
|
|
509
|
-
|
|
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.
|
|
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": {
|