diff --git a/farm/server/webui.py b/farm/server/webui.py index bda662f..37fc79d 100644 --- a/farm/server/webui.py +++ b/farm/server/webui.py @@ -212,6 +212,15 @@ function goodsName(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) { const el = $("toast"); el.textContent = text; @@ -228,14 +237,32 @@ function setStatus(text, cls) { function farmName() { return me || "default"; } 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() { - const h = { "Content-Type": "application/json", "X-Farm-Id": farmName() }; - if (token()) h["X-API-Token"] = token(); + const h = { "Content-Type": "application/json" }; + if (latin1(farmName())) h["X-Farm-Id"] = farmName(); + if (token() && latin1(token())) h["X-API-Token"] = token(); return h; } 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; try { data = await res.json(); } catch (e) { data = { ok: false, message: "响应不是 JSON" }; } if (!res.ok || data.ok === false) { @@ -507,7 +534,7 @@ function applyIdentity() { $("logout").classList.toggle("hidden", !me); $("loginmask").classList.toggle("show", !me); if (me) { - localStorage.setItem("sf.farm", me); + lsSet("sf.farm", me); connect(); } else if (es) { try { es.close(); } catch (e) {} @@ -534,9 +561,9 @@ $("logout").addEventListener("click", async () => { $("loginname").focus(); }); /* ---- 启动 ---- */ -$("token").value = localStorage.getItem("sf.token") || ""; +$("token").value = lsGet("sf.token") || ""; $("token").addEventListener("change", () => { - localStorage.setItem("sf.token", token()); + lsSet("sf.token", token()); if (me) connect(); }); whoami();