From a1dc1ff7a3d6599bdb5a28f115f944f119e4c9f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Mon, 19 Oct 2026 18:28:22 +0800 Subject: [PATCH] =?UTF-8?q?feat(chat):=20=E9=A1=B5=E9=9D=A2=E7=BA=A7=20bas?= =?UTF-8?q?ePath=20=E5=BC=95=E5=AF=BC=EF=BC=88=E8=B7=AF=E5=BE=84=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E9=9D=99=E6=80=81=E9=9D=A2=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Code --- static/chat.html | 37 ++++++++++++++++++++++++++++----- static/js/chat.js | 8 ++++++-- tests/test_chat_static.py | 43 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 tests/test_chat_static.py diff --git a/static/chat.html b/static/chat.html index 3a95cc6..424852d 100644 --- a/static/chat.html +++ b/static/chat.html @@ -4,13 +4,42 @@ AI Support - - +
-
🤖
+
🤖

AI Support

Online now - Usually replies in minutes
@@ -76,7 +105,5 @@
- - diff --git a/static/js/chat.js b/static/js/chat.js index 98aa98b..0a22244 100644 --- a/static/js/chat.js +++ b/static/js/chat.js @@ -1,8 +1,12 @@ +// ---- basePath(spec 2026-10-19 §4):chat.html 引导脚本按 location.pathname ---- +// ---- 推导并挂载 window.__BASE_PATH__(路径模式 /t/,Host 模式空串)。 ---- +var B = window.__BASE_PATH__ || ""; + // ---- Task 10: brand 拉取(同源 /api/public/brand,下方两个 IIFE 共用) ---- // 失败/超时(3s) → resolve(null) → 中性兜底,页面照常可用不白屏。 var chatBrandReady = (function () { try { - var p = fetch("/api/public/brand", { cache: "no-store" }).then(function (r) { + var p = fetch(B + "/api/public/brand", { cache: "no-store" }).then(function (r) { return r.ok ? r.json() : null; }).catch(function () { return null; @@ -129,7 +133,7 @@ var chatBrandReady = (function () { function getWsUrl() { var loc = window.location; var proto = loc.protocol === "https:" ? "wss:" : "ws:"; - return proto + "//" + loc.host + "/ws/chat"; + return proto + "//" + loc.host + B + "/ws/chat"; } function connect(sessionKey) { diff --git a/tests/test_chat_static.py b/tests/test_chat_static.py new file mode 100644 index 0000000..f5270fa --- /dev/null +++ b/tests/test_chat_static.py @@ -0,0 +1,43 @@ +"""chat.html 静态面前缀守卫(spec 2026-10-19 §4):页面级 __BASE_PATH__ 引导。 + +chat 是页面而非嵌入脚本(与 widget.js 的脚本地址自推导不同),由 chat.html +head 内联引导脚本按 location.pathname 推导 basePath 并动态装载 css/js/ +avatar;Host 模式(根路径 "/")推导为空串,所有引用零变化。守卫四点: +引导脚本在场且走正则白名单 match、静态标签不再绝对引用、引导块零 +innerHTML(拼接安全防线 §2/§3)、chat.js 端点构造全部经 __BASE_PATH__。 +""" +from pathlib import Path + +STATIC = Path(__file__).resolve().parent.parent / "static" + + +def read(rel): + return (STATIC / rel).read_text(encoding="utf-8") + + +def test_引导脚本存在且定义_BASE_PATH(): + src = read("chat.html") + assert "__BASE_PATH__" in src + # 代码锚(非注释):base 推导必须走正则白名单 match + assert "location.pathname.match(" in src + + +def test_不再有静态绝对引用标签(): + src = read("chat.html") + assert 'href="/static/' not in src + assert 'src="/static/js/' not in src + assert 'src="/api/public/avatar"' not in src + + +def test_引导脚本零innerHTML_拼接安全防线(): + # 安全防线 §2/§3:base 只做 URL 属性赋值,绝不拼 HTML—— + # 断言首个内联 块(引导脚本)内无 innerHTML + src = read("chat.html") + boot = src.split("", 1)[0] + assert "innerHTML" not in boot + + +def test_chatjs_端点全部走_BASE_PATH(): + src = read("js/chat.js") + assert "__BASE_PATH__" in src + assert 'fetch("/api/' not in src