fix(webui): 中文用户名登录后所有写操作静默失败
为什么:headers() 把用户名塞进 X-Farm-Id 请求头,而 HTTP 头只允许 Latin-1 字符——中文用户名(如"阳")会让 fetch 在发送前直接抛 TypeError(String contains non ISO-8859-1 code point)。登录时 me 还是 null(头为 ASCII 的 default)所以能登录成功,登录之后 me 变成中文,所有走 api() 的写操作(播种/浇水/收获/卖出)全部 静默失败;页面靠 SSE 推送照常渲染,看起来一切正常,用户表现为 "点播种没反应"。 - headers():非 ASCII 的牧场名/令牌不再塞头。登录态本来就由 Cookie 鉴权,X-Farm-Id 只是匿名脚本模式的备用通道;ASCII 名 (default、bot 等)行为不变。 - api():捕获 fetch 网络异常并 toast 提示。此前 Promise 拒绝 无人接住,这类故障完全不可见,正是本 bug 难排查的原因。 - localStorage 读写加 try/catch 兜底:sandbox iframe 若不带 allow-same-origin,裸访问即抛 SecurityError,会炸掉整个启动 脚本(启动段就有三处裸调用)。 验证:headless Chrome 真机复现 → 修复 → UI 全流程回归(登录 "阳" → 点播种 → 选小麦 → toast 与地块状态正确更新);ruff / pyright / 528 项测试全绿。
This commit is contained in:
1 parent
698a5e497a
commit
2676927c1a
1 file changed
+33
-6
+33
-6
@@ -212,6 +212,15 @@ function goodsName(key) {
|
|||||||
return cropNames[key] || key;
|
return cropNames[key] || key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* sandbox iframe 可能禁用 localStorage(无 allow-same-origin 时访问即抛
|
||||||
|
SecurityError),任何一处裸调用都会炸掉整个启动脚本。 */
|
||||||
|
function lsGet(key) {
|
||||||
|
try { return localStorage.getItem(key); } catch (e) { return null; }
|
||||||
|
}
|
||||||
|
function lsSet(key, val) {
|
||||||
|
try { localStorage.setItem(key, val); } catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
function toast(text) {
|
function toast(text) {
|
||||||
const el = $("toast");
|
const el = $("toast");
|
||||||
el.textContent = text;
|
el.textContent = text;
|
||||||
@@ -228,14 +237,32 @@ function setStatus(text, cls) {
|
|||||||
function farmName() { return me || "default"; }
|
function farmName() { return me || "default"; }
|
||||||
function token() { return ($("token").value || "").trim(); }
|
function token() { return ($("token").value || "").trim(); }
|
||||||
|
|
||||||
|
/* HTTP 头只允许 Latin-1 字符:中文用户名塞进 X-Farm-Id 会让 fetch 在发送前
|
||||||
|
就抛 TypeError,登录后所有写操作静默失败(表现为“点播种没反应”)。
|
||||||
|
登录态由 Cookie 鉴权,非 ASCII 的值一律不塞头。 */
|
||||||
|
function latin1(s) {
|
||||||
|
for (let i = 0; i < s.length; i++) {
|
||||||
|
const c = s.charCodeAt(i);
|
||||||
|
if (c < 32 || c > 126) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function headers() {
|
function headers() {
|
||||||
const h = { "Content-Type": "application/json", "X-Farm-Id": farmName() };
|
const h = { "Content-Type": "application/json" };
|
||||||
if (token()) h["X-API-Token"] = token();
|
if (latin1(farmName())) h["X-Farm-Id"] = farmName();
|
||||||
|
if (token() && latin1(token())) h["X-API-Token"] = token();
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function api(path, options) {
|
async function api(path, options) {
|
||||||
const res = await fetch(path, options);
|
let res;
|
||||||
|
try {
|
||||||
|
res = await fetch(path, options);
|
||||||
|
} catch (e) {
|
||||||
|
toast("网络错误:" + e.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
let data = null;
|
let data = null;
|
||||||
try { data = await res.json(); } catch (e) { data = { ok: false, message: "响应不是 JSON" }; }
|
try { data = await res.json(); } catch (e) { data = { ok: false, message: "响应不是 JSON" }; }
|
||||||
if (!res.ok || data.ok === false) {
|
if (!res.ok || data.ok === false) {
|
||||||
@@ -507,7 +534,7 @@ function applyIdentity() {
|
|||||||
$("logout").classList.toggle("hidden", !me);
|
$("logout").classList.toggle("hidden", !me);
|
||||||
$("loginmask").classList.toggle("show", !me);
|
$("loginmask").classList.toggle("show", !me);
|
||||||
if (me) {
|
if (me) {
|
||||||
localStorage.setItem("sf.farm", me);
|
lsSet("sf.farm", me);
|
||||||
connect();
|
connect();
|
||||||
} else if (es) {
|
} else if (es) {
|
||||||
try { es.close(); } catch (e) {}
|
try { es.close(); } catch (e) {}
|
||||||
@@ -534,9 +561,9 @@ $("logout").addEventListener("click", async () => {
|
|||||||
$("loginname").focus();
|
$("loginname").focus();
|
||||||
});
|
});
|
||||||
/* ---- 启动 ---- */
|
/* ---- 启动 ---- */
|
||||||
$("token").value = localStorage.getItem("sf.token") || "";
|
$("token").value = lsGet("sf.token") || "";
|
||||||
$("token").addEventListener("change", () => {
|
$("token").addEventListener("change", () => {
|
||||||
localStorage.setItem("sf.token", token());
|
lsSet("sf.token", token());
|
||||||
if (me) connect();
|
if (me) connect();
|
||||||
});
|
});
|
||||||
whoami();
|
whoami();
|
||||||
|
|||||||
Reference in new issue
Block a user