feat(net): late uplink poll + faster provision monitor (15s)

- client: while !_connectionStarted, poll every 3s for hasInternet or
  hasWiredInternetProbe (e.g. Ethernet after AP); start _proceedWithConnection
  once; clear poll on network-ready/stop. Avoids provision-level emit spam.
- provisioning: MONITOR_INTERVAL_MS 30s -> 15s for quicker WiFi/AP UI.

Made-with: Cursor
This commit is contained in:
stswangzhiping
2026-04-02 12:40:41 +08:00
parent fe62b9febe
commit 12aedaaf85
2 changed files with 31 additions and 1 deletions

View File

@@ -21,6 +21,8 @@ const PONG_MISS_MAX = 3;
const PING_INTERVAL_MS = 15_000; const PING_INTERVAL_MS = 15_000;
const HEARTBEAT_INTERVAL_MS = 10_000; // 心跳间隔10 秒,用于快速感知网络状态 const HEARTBEAT_INTERVAL_MS = 10_000; // 心跳间隔10 秒,用于快速感知网络状态
const METRICS_EVERY_N = 3; // 每 N 次心跳采集一次指标(= 30 秒) const METRICS_EVERY_N = 3; // 每 N 次心跳采集一次指标(= 30 秒)
/** 尚未连云时轮询外网(如 AP 模式下后接网线);短于 provision 监控,避免只靠 network-ready */
const LATE_NET_POLL_MS = 3_000;
/** 内核是否暴露蓝牙适配器hci* */ /** 内核是否暴露蓝牙适配器hci* */
function bluetoothAdapterPresent() { function bluetoothAdapterPresent() {
@@ -74,6 +76,8 @@ class ClawClient {
// systemd watchdogsystemd-notify 子进程 + unit 里 NotifyAccess=all // systemd watchdogsystemd-notify 子进程 + unit 里 NotifyAccess=all
this._sdTimer = null; this._sdTimer = null;
/** setInterval等外网出现后启动 _proceedWithConnection与 network-ready 二选一) */
this._lateNetPollTimer = null;
this._setupGlobalHandlers(); this._setupGlobalHandlers();
} }
@@ -132,6 +136,7 @@ class ClawClient {
// 网络就绪时连接云端(仅触发一次) // 网络就绪时连接云端(仅触发一次)
this._provisionMgr.on('network-ready', () => { this._provisionMgr.on('network-ready', () => {
if (!this._connectionStarted) { if (!this._connectionStarted) {
this._clearLateNetPoll();
this._connectionStarted = true; this._connectionStarted = true;
this._proceedWithConnection().catch(e => { this._proceedWithConnection().catch(e => {
log.error('clawd', '连接启动失败:', e.message); log.error('clawd', '连接启动失败:', e.message);
@@ -148,6 +153,30 @@ class ClawClient {
} else if (!hasInternet()) { } else if (!hasInternet()) {
log.info('clawd', '等待外网就绪(可配网;有线已接时将自动识别网卡,含非 eth0 口)...'); log.info('clawd', '等待外网就绪(可配网;有线已接时将自动识别网卡,含非 eth0 口)...');
} }
if (!this._connectionStarted && !this._stopped) {
this._lateNetPollTimer = setInterval(() => {
if (this._stopped || this._connectionStarted) {
this._clearLateNetPoll();
return;
}
if (hasInternet() || hasWiredInternetProbe()) {
this._clearLateNetPoll();
this._connectionStarted = true;
log.info('clawd', '轮询检测到外网,启动连云');
this._proceedWithConnection().catch(e => {
log.error('clawd', '连接启动失败:', e.message);
});
}
}, LATE_NET_POLL_MS);
}
}
_clearLateNetPoll() {
if (this._lateNetPollTimer) {
clearInterval(this._lateNetPollTimer);
this._lateNetPollTimer = null;
}
} }
async _proceedWithConnection() { async _proceedWithConnection() {
@@ -205,6 +234,7 @@ class ClawClient {
stop() { stop() {
this._stopped = true; this._stopped = true;
this._clearLateNetPoll();
this._clearHeartbeat(); this._clearHeartbeat();
this._clearPing(); this._clearPing();
if (this._sdTimer) { clearInterval(this._sdTimer); this._sdTimer = null; } if (this._sdTimer) { clearInterval(this._sdTimer); this._sdTimer = null; }

View File

@@ -7,7 +7,7 @@ const { DnsHijack } = require('./dns-hijack');
const { CaptiveServer } = require('./captive-server'); const { CaptiveServer } = require('./captive-server');
const led = require('./led'); const led = require('./led');
const MONITOR_INTERVAL_MS = 30_000; const MONITOR_INTERVAL_MS = 15_000;
const BOOT_WAIT_MAX_MS = 20_000; // 等待 NM 自动连接的最大时间 const BOOT_WAIT_MAX_MS = 20_000; // 等待 NM 自动连接的最大时间
const BOOT_POLL_MS = 2_000; // 轮询间隔 const BOOT_POLL_MS = 2_000; // 轮询间隔