feat: connect 消息上报 local_ip 和 external_ip

Made-with: Cursor
This commit is contained in:
stswangzhiping
2026-03-21 08:18:27 +08:00
parent af5248f2a6
commit 878f3592bf
8 changed files with 1595 additions and 1392 deletions

View File

@@ -8,7 +8,7 @@ const { getBoxId } = require('./fingerprint');
const { collect } = require('./metrics'); const { collect } = require('./metrics');
const { getDashboardInfo, startTtyd, FrpcManager } = require('./frpc'); // getDashboardInfo 也用于心跳中定期刷新 const { getDashboardInfo, startTtyd, FrpcManager } = require('./frpc'); // getDashboardInfo 也用于心跳中定期刷新
const { ProvisionManager } = require('./provisioning'); const { ProvisionManager } = require('./provisioning');
const { hasInternet } = require('./network'); const { hasInternet, getLocalIps } = require('./network');
const led = require('./led'); const led = require('./led');
const MAX_BACKOFF_MS = 60_000; const MAX_BACKOFF_MS = 60_000;
@@ -35,6 +35,7 @@ class ClawClient {
this._frpc = new FrpcManager(); this._frpc = new FrpcManager();
this._dashInfo = {}; this._dashInfo = {};
this._hbCount = 0; // 心跳计数器,用于定期刷新 dashboard 信息 this._hbCount = 0; // 心跳计数器,用于定期刷新 dashboard 信息
this._externalIp = null; // 外网 IP连接时查询一次用于服务端地理位置解析
// WS 层活性检测 // WS 层活性检测
this._pingTimer = null; this._pingTimer = null;
@@ -111,6 +112,27 @@ class ClawClient {
startTtyd().catch(e => log.warn('ttyd', '启动失败:', e.message)), startTtyd().catch(e => log.warn('ttyd', '启动失败:', e.message)),
]); ]);
this._dashInfo = dashInfo || {}; this._dashInfo = dashInfo || {};
// 查询外网 IP用于服务端地理位置解析失败不阻断连接
try {
const https = require('https');
this._externalIp = await new Promise((resolve) => {
const req = https.get('https://api.ipify.org?format=json', { timeout: 5000 }, (res) => {
let body = '';
res.on('data', d => { body += d; });
res.on('end', () => {
try { resolve(JSON.parse(body).ip || null); } catch { resolve(null); }
});
});
req.on('error', () => resolve(null));
req.on('timeout', () => { req.destroy(); resolve(null); });
});
if (this._externalIp) log.info('clawd', `外网 IP: ${this._externalIp}`);
} catch (e) {
log.warn('clawd', '外网 IP 查询失败:', e.message);
this._externalIp = null;
}
this._connect(); this._connect();
} }
@@ -266,6 +288,8 @@ class ClawClient {
box_id: this._boxId, box_id: this._boxId,
claw_id: this._cfg.claw_id ?? null, claw_id: this._cfg.claw_id ?? null,
token: this._cfg.token ?? null, token: this._cfg.token ?? null,
local_ip: getLocalIps(),
external_ip: this._externalIp ?? null,
...this._dashInfo, ...this._dashInfo,
}; };
this._send(msg); this._send(msg);

View File

@@ -2,6 +2,7 @@
const { execSync } = require('child_process'); const { execSync } = require('child_process');
const fs = require('fs'); const fs = require('fs');
const os = require('os');
const log = require('./logger'); const log = require('./logger');
const AP_SSID_PREFIX = 'ClawBox-'; const AP_SSID_PREFIX = 'ClawBox-';
@@ -223,6 +224,29 @@ function isWifiStaConnected() {
return false; return false;
} }
/**
* 获取本机所有非回环 IPv4 地址,逗号拼接返回
* 例:'192.168.1.100' 或 '192.168.1.100,10.0.0.5'
*/
function getLocalIps() {
try {
const ifaces = os.networkInterfaces();
const ips = [];
for (const [name, addrs] of Object.entries(ifaces)) {
if (!addrs) continue;
for (const addr of addrs) {
if (addr.family === 'IPv4' && !addr.internal) {
ips.push(addr.address);
}
}
}
return ips.length > 0 ? ips.join(',') : null;
} catch (e) {
log.warn('network', '获取本机 IP 失败:', e.message);
return null;
}
}
module.exports = { module.exports = {
hasInternet, hasInternet,
hasWiredCarrier, hasWiredCarrier,
@@ -234,4 +258,5 @@ module.exports = {
startAP, startAP,
stopAP, stopAP,
AP_IP, AP_IP,
getLocalIps,
}; };

148
package-lock.json generated Normal file
View File

@@ -0,0 +1,148 @@
{
"name": "clawd",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "clawd",
"version": "0.1.0",
"license": "MIT",
"dependencies": {
"ssh2": "^1.17.0",
"systeminformation": "^5.25.0",
"ws": "^8.18.0"
},
"bin": {
"clawd": "bin/clawd.js"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
"license": "MIT",
"dependencies": {
"safer-buffer": "~2.1.0"
}
},
"node_modules/bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
"license": "BSD-3-Clause",
"dependencies": {
"tweetnacl": "^0.14.3"
}
},
"node_modules/buildcheck": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.7.tgz",
"integrity": "sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==",
"optional": true,
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/cpu-features": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz",
"integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==",
"hasInstallScript": true,
"optional": true,
"dependencies": {
"buildcheck": "~0.0.6",
"nan": "^2.19.0"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/nan": {
"version": "2.26.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.26.1.tgz",
"integrity": "sha512-vodKprLlmaKmraa9E/TxHQwpH4eKYTJbLdeQE49pb9GOmrLs68zESjJu0LQOz1W6JwJmftOWD5Ls4dpd/elQtQ==",
"license": "MIT",
"optional": true
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/ssh2": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz",
"integrity": "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==",
"hasInstallScript": true,
"dependencies": {
"asn1": "^0.2.6",
"bcrypt-pbkdf": "^1.0.2"
},
"engines": {
"node": ">=10.16.0"
},
"optionalDependencies": {
"cpu-features": "~0.0.10",
"nan": "^2.23.0"
}
},
"node_modules/systeminformation": {
"version": "5.31.4",
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.4.tgz",
"integrity": "sha512-lZppDyQx91VdS5zJvAyGkmwe+Mq6xY978BDUG2wRkWE+jkmUF5ti8cvOovFQoN5bvSFKCXVkyKEaU5ec3SJiRg==",
"license": "MIT",
"os": [
"darwin",
"linux",
"win32",
"freebsd",
"openbsd",
"netbsd",
"sunos",
"android"
],
"bin": {
"systeminformation": "lib/cli.js"
},
"engines": {
"node": ">=8.0.0"
},
"funding": {
"type": "Buy me a coffee",
"url": "https://www.buymeacoffee.com/systeminfo"
}
},
"node_modules/tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
"license": "Unlicense"
},
"node_modules/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
}
}

View File

@@ -9,14 +9,20 @@
"scripts": { "scripts": {
"start": "node bin/clawd.js" "start": "node bin/clawd.js"
}, },
"keywords": ["claw", "iot", "websocket", "daemon"], "keywords": [
"claw",
"iot",
"websocket",
"daemon"
],
"author": "stswangzhiping", "author": "stswangzhiping",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
}, },
"dependencies": { "dependencies": {
"ws": "^8.18.0", "ssh2": "^1.17.0",
"systeminformation": "^5.25.0" "systeminformation": "^5.25.0",
"ws": "^8.18.0"
} }
} }