feat: brand 白名单下发(站点覆盖/租户头像/CORS) + widget/chat 客户端 key 租户化 + 会话站点标

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-10-12 22:26:15 +08:00
parent 49e2b476f5
commit bbc7bc520e
15 changed files with 570 additions and 93 deletions
+34 -7
View File
@@ -83,7 +83,7 @@
<div class="info">
<p><strong>Current Page:</strong> <span id="pathname"></span></p>
<p><strong>Storage Key:</strong> widget_invite_dismissed_<span id="storage-key"></span></p>
<p><strong>Storage Key:</strong> widget_invite_dismissed_&lt;slug&gt;_<span id="storage-key"></span> (Task 10: key 按租户 slug 命名空间化)</p>
<p><button class="btn" onclick="clearStorage()">Clear Storage &amp; Refresh</button></p>
</div>
@@ -92,7 +92,12 @@
document.getElementById('storage-key').textContent = window.location.pathname;
function clearStorage() {
sessionStorage.removeItem('widget_invite_dismissed_' + window.location.pathname);
// Task 10: key 已租户化(widget_invite_dismissed_<slug>_<path>)——按前缀清扫全部
try {
Object.keys(sessionStorage).forEach(function (k) {
if (k.indexOf('widget_invite_dismissed_') === 0) sessionStorage.removeItem(k);
});
} catch (e) {}
location.reload();
}
@@ -107,22 +112,36 @@
};
var rebuildLoading = false, rebuildPending = null;
function removeWidgetHosts() {
// Task 10: host id 已租户化(cc-widget-host-<slug>),按前缀清扫
var hosts = document.querySelectorAll('[id^="cc-widget-host-"]');
for (var i = 0; i < hosts.length; i++) hosts[i].remove();
}
function rebuildWidget(extra) {
if (rebuildLoading) { rebuildPending = extra || {}; return; } // 快速连点:只记最新意图,当前加载完接力重建
rebuildLoading = true;
document.getElementById("assertions").innerHTML = "<div>…rebuilding…</div>";
document.querySelectorAll("#cc-widget-host").forEach(function (h) { h.remove(); });
removeWidgetHosts();
document.querySelectorAll("script[src*='widget.js']").forEach(function (s) { s.remove(); });
try { sessionStorage.removeItem("widget_invite_dismissed_" + window.location.pathname); } catch (e) {}
try {
Object.keys(sessionStorage).forEach(function (k) {
if (k.indexOf("widget_invite_dismissed_") === 0) sessionStorage.removeItem(k);
});
} catch (e) {}
var s = document.createElement("script");
s.src = "/static/widget.js?" + Date.now();
var attrs = Object.assign({}, BASE_ATTRS, extra || {});
Object.keys(attrs).forEach(function (k) { s.setAttribute(k, attrs[k]); });
s.onload = function () {
rebuildLoading = false;
var hosts = document.querySelectorAll("#cc-widget-host");
for (var i = 0; i < hosts.length - 1; i++) hosts[i].remove(); // 竞态兜底:只保留最后挂载
runAssertions();
// Task 10: 挂件改等 brand 端点落定后才挂载——脚本 onload 早于挂载,
// 轮询 __chatWidgetDebug(boot 里同步设置,观察到场即 init 已完成)
// 就绪再清竞态残留并跑断言
waitForWidget(function () {
var hosts = document.querySelectorAll('[id^="cc-widget-host-"]');
for (var i = 0; i < hosts.length - 1; i++) hosts[i].remove(); // 竞态兜底:只保留最后挂载
runAssertions(); // 挂件未就绪(超时)时其内部 !d 分支自行报红
});
var next = rebuildPending; rebuildPending = null;
if (next) rebuildWidget(next);
};
@@ -134,6 +153,14 @@
};
document.body.appendChild(s);
}
function waitForWidget(cb) {
var started = Date.now();
(function poll() {
if (window.__chatWidgetDebug) return cb(true);
if (Date.now() - started > 5000) return cb(false); // brand 挂起/失败兜底
setTimeout(poll, 100);
})();
}
function applyColor(c) {
rebuildWidget({ "data-color": c || document.getElementById("colorPicker").value });
}