feat(widget): 脚本地址自推导 basePath(路径模式端点前缀)
spec 2026-10-19 §4: 路径模式(/t/<slug>/static/widget.js)下从 script.src 剥尾推导 basePath,brand/avatar 三分支/WS 四处端点构造统一加前缀; Host 模式 indexOf 命中在 0 → basePath="" 端点零变化。 test_theme_static.py 头像锚定守卫随构造更新(wsBase + basePath + …, 语义不变:绝对锚定 + 禁相对路径)。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
+15
-5
@@ -24,6 +24,16 @@
|
||||
var autoInvite = script.getAttribute("data-auto-invite") !== "false";
|
||||
var isMobile = script.getAttribute("data-mobile") === "true";
|
||||
var wsBase = new URL(script.src).origin;
|
||||
// spec 2026-10-19 §4: 脚本地址自携带租户路径(/t/<slug>/static/widget.js)
|
||||
// → 剥掉 /static/widget.js 起的前缀得 basePath("" = Host 模式零变化)。
|
||||
// script.src 永远指向本文件,indexOf 命中即安全;query(?v=)不影响 pathname。
|
||||
// 拼接安全: basePath 仅进 URL 上下文(fetch/WebSocket/img.src),
|
||||
// 绝不进 innerHTML 字符串拼 HTML(防线 §2/§3)。
|
||||
var basePath = (function () {
|
||||
var p = new URL(script.src).pathname;
|
||||
var i = p.indexOf("/static/widget.js");
|
||||
return i > 0 ? p.slice(0, i) : "";
|
||||
})();
|
||||
|
||||
/* ===== 主题派生单元:与 static/js/theme.js 逐字同步,tests/test_theme_static.py 强制;只可整块替换,不可单边修改 ===== */
|
||||
var DEFAULT_COLOR = "#066fc0";
|
||||
@@ -129,7 +139,7 @@
|
||||
var brandSlug = "default";
|
||||
|
||||
function fetchBrand() {
|
||||
var url = wsBase + "/api/public/brand" + (siteAttr ? "?site=" + encodeURIComponent(siteAttr) : "");
|
||||
var url = wsBase + basePath + "/api/public/brand" + (siteAttr ? "?site=" + encodeURIComponent(siteAttr) : "");
|
||||
var p = fetch(url, { cache: "no-store" }).then(function (r) {
|
||||
return r.ok ? r.json() : null;
|
||||
}).catch(function () {
|
||||
@@ -161,9 +171,9 @@
|
||||
email = contactHref(emailAttr || b.email || "", "mail");
|
||||
palette = derivePalette(colorAttr || (b.color || "").trim() || DEFAULT_COLOR);
|
||||
avatarUrl = avatarAttr
|
||||
? (/^https?:\/\//i.test(avatarAttr) ? avatarAttr : wsBase + "/" + avatarAttr.replace(/^\//, ""))
|
||||
: (b.avatar ? wsBase + (b.avatar.charAt(0) === "/" ? b.avatar : "/" + b.avatar)
|
||||
: wsBase + DEFAULT_AVATAR_PATH);
|
||||
? (/^https?:\/\//i.test(avatarAttr) ? avatarAttr : wsBase + basePath + "/" + avatarAttr.replace(/^\//, ""))
|
||||
: (b.avatar ? wsBase + basePath + (b.avatar.charAt(0) === "/" ? b.avatar : "/" + b.avatar)
|
||||
: wsBase + basePath + DEFAULT_AVATAR_PATH);
|
||||
welcome = b.welcome || "";
|
||||
topics = Array.isArray(b.topics)
|
||||
? b.topics.filter(function (t) { return typeof t === "string" && t; })
|
||||
@@ -682,7 +692,7 @@
|
||||
function wConnect() {
|
||||
var proto = wsBase.indexOf("https") === 0 ? "wss" : "ws";
|
||||
var wsHost = wsBase.replace(/^https?/, proto);
|
||||
wWs = new WebSocket(wsHost + "/ws/chat");
|
||||
wWs = new WebSocket(wsHost + basePath + "/ws/chat");
|
||||
|
||||
wWs.onopen = function () {
|
||||
// Task 10: init 带 site(空域名站点如 es 靠 data-site 接入,服务端
|
||||
|
||||
Reference in New Issue
Block a user