From 91a534514d5d3ecb0c05df20b2195ca7e829c2ad Mon Sep 17 00:00:00 2001
From: cbb <2625274797@qq.com>
Date: Wed, 16 Sep 2026 20:38:37 +0800
Subject: [PATCH] =?UTF-8?q?fix(conn):=20=E6=8B=94=E7=BA=BF=E5=90=8E?=
=?UTF-8?q?=E4=B8=8D=E5=86=8D=E6=B0=B8=E8=BF=9C=E3=80=8C=E5=B7=B2=E8=BF=9E?=
=?UTF-8?q?=E6=8E=A5=E3=80=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1) pollStatus 把 5s 超时 AbortError / HTTP 非 2xx 计入失败,走离线+清指标
2) DeviceLink 快照不得把本页已判离线改回在线(Owner 连败 3 次前会盖绿灯)
---
js/app.js | 2 +-
js/common/connection.js | 6 ++++--
js/common/polling.js | 11 +++++++++--
pages/index.html | 2 +-
4 files changed, 15 insertions(+), 6 deletions(-)
diff --git a/js/app.js b/js/app.js
index 2dcd694..13d34c4 100644
--- a/js/app.js
+++ b/js/app.js
@@ -17,7 +17,7 @@ import {
import {
scheduleStatusPoll, startPolling, pausePollingForUpload,
resumePollingAfterUpload, pollStatus, pollLogs, setStatusPollIntervalMs
-} from './common/polling.js?v=20260916d';
+} from './common/polling.js?v=20260916e';
import {
OtaSessionState, otaSessionState, otaSessionTarget, otaFailReason,
diff --git a/js/common/connection.js b/js/common/connection.js
index 5b7d2d5..bf02a70 100644
--- a/js/common/connection.js
+++ b/js/common/connection.js
@@ -61,8 +61,10 @@ export function updateConnectionUi(connected, text) {
try {
var snap = deviceLink.getSnapshot();
if (snap && snap.ts && (Date.now() - snap.ts) < 5000) {
- /* 只覆盖在线布尔,保留调用方语义文案(设备忙/诊断中/STA…) */
- connected = !!snap.links.esp32.online;
+ /* Hub 可把在线压成离线;**不得**把本页刚判的离线改回在线。
+ * Owner 要连失败 3 次才翻离线,期间若无脑覆盖,
+ * OTA 页超时判离线后灯又会被点绿 → 拔线后永远「已连接」。 */
+ if (!snap.links.esp32.online) connected = false;
if (!text) text = connected ? '已连接' : '离线';
}
} catch (_) {}
diff --git a/js/common/polling.js b/js/common/polling.js
index 39c8383..5430863 100644
--- a/js/common/polling.js
+++ b/js/common/polling.js
@@ -237,12 +237,19 @@ export async function pollStatus() {
if (window.isEsp32Rebooting) {
window.applyRebootingRuntimeState();
}
+ } else {
+ /* HTTP 非 2xx:设备在但不健康,也必须走失败路径,否则 UI 永远停在「已连接」 */
+ var httpErr = new Error('HTTP ' + resp.status);
+ httpErr.status = resp.status;
+ throw httpErr;
}
clearTimeout(pollTimer);
} catch (e) {
clearTimeout(pollTimer);
- // 自身 AbortController 触发的取消不算失败,静默返回
- if (e && e.name === "AbortError") return;
+ /* 5s 超时 abort:拔线后 TCP 常挂死到超时,不是「主动取消」。
+ * 旧逻辑把所有 AbortError 静默 return → 拔掉后永远「已连接」。
+ * 仅上传暂停导致的 abort 不算失败(pausePollingForUpload 已 abort 在途请求)。 */
+ if (e && e.name === "AbortError" && window.pollingPausedForUpload) return;
// 先判定 OTA 是否进行中:OTA 期间所有失败都不计入 consecutivePollFailures,
// 也不触发诊断。避免"上传完成→finalize→重启"窗口期失败计数累积,
// 切回 IDLE 瞬间立即触发 5-fail 诊断,干扰进度显示。
diff --git a/pages/index.html b/pages/index.html
index 686ab9e..690f3d9 100644
--- a/pages/index.html
+++ b/pages/index.html
@@ -569,7 +569,7 @@
必须持有租约,否则服务端在租约归零宽限期后会把循环又停掉。
与 tabs.html / motor.html 并存时连接数>1,互不影响。 -->
-
+