zcbot/web/static/js/embed.js

117 lines
5.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// embed(iframe)模式:父页面经 postMessage 推送 token → 进入应用;401 后重签。
// wecom 变体(?embed=1&wecom=1):token 来自 /v1/wecom/entry 回调的 URL fragment,
// 401 时 embedWecomRelogin 重定向回 entry 静默重签(企微客户端内无感)。
// app 变体(?embed=1&relogin_url=...):token 同样走 fragment,401 时跳 relogin_url
// (原生壳拦自定义 scheme 换新 token 重进,或 H5 登录页静默重签)。
// 顶层无副作用,boot 决定是否调 embedInit。导出 embedInit(boot 调)+
// embedPostToParent / embedShowWaiting / embedWecomRelogin / embedAppRelogin(auth 的 logout 用)。
import { state, LS_TOKEN, EMBED_WECOM, EMBED_PARENT_ORIGIN, EMBED_RELOGIN_URL, EMBED_INITIAL_TASK_ID, setIdentity } from "./state.js";
import { $ } from "./dom.js";
import { enterApp } from "./main.js";
import { loadTaskList } from "./chat.js";
// embed 首个 task 自动定位的一次性标志(仅 embed 段使用)
let _embedInitialTaskHandled = false;
// ───── embed mode ─────
export function embedPostToParent(msg) {
if (!EMBED_PARENT_ORIGIN || window.parent === window) return;
try { window.parent.postMessage(msg, EMBED_PARENT_ORIGIN); } catch (e) {}
}
export function embedShowWaiting(text, isErr) {
const w = $("embed-waiting");
if (!w) return;
if (isErr) {
w.querySelector(".text").textContent = "";
w.querySelector(".err").textContent = text || "";
w.querySelector(".spinner").style.display = "none";
} else {
w.querySelector(".text").textContent = text || "等待登录…";
w.querySelector(".err").textContent = "";
w.querySelector(".spinner").style.display = "";
}
}
function embedHandleMessage(e) {
if (e.origin !== EMBED_PARENT_ORIGIN) return;
const d = e.data || {};
if (d.type === "zcbot-token" && d.token && d.user_id) {
state.token = d.token;
localStorage.setItem(LS_TOKEN, state.token);
// 父页面可带 name/user_name/email;缺的由 enterApp → loadRole 拉 /v1/me 补齐
setIdentity({ user_id: d.user_id, name: d.name, user_name: d.user_name, email: d.email });
document.body.classList.remove("embed-waiting");
if ($("app").classList.contains("ready")) {
// 401 后重签:重载列表,不重复 enterApp / 不重复定位 task(尊重用户中间切过的选择)
loadTaskList();
} else {
// 首次签发:若 URL 带 task_id直接进入该 taskenterApp 不再先加载根目录。
enterApp(takeInitialTaskId());
}
}
}
// ───── fragment 注入(wecom / app 变体共用)─────
// fragment 里的 token 优先于 localStorage 缓存(每次从登录入口进来都是新签的);
// 读完立即清 hash,token 不留在地址栏 / 浏览历史。name/email 由 enterApp → loadRole 拉 /v1/me 补齐。
function readFragmentToken() {
const h = new URLSearchParams(location.hash.replace(/^#/, ""));
const tok = h.get("token"), uid = h.get("user_id");
if (!tok || !uid) return;
state.token = tok;
localStorage.setItem(LS_TOKEN, tok);
setIdentity({ user_id: uid });
history.replaceState(null, "", location.pathname + location.search);
}
function takeInitialTaskId() {
if (EMBED_INITIAL_TASK_ID && !_embedInitialTaskHandled) {
_embedInitialTaskHandled = true;
return EMBED_INITIAL_TASK_ID;
}
return "";
}
// ───── wecom 免登变体 ─────
export function embedWecomRelogin() {
// 回 entry 重走 OAuth(snsapi_base 静默,企微客户端内用户无感);replace 不留历史,
// 避免返回键回到已失效的页面
location.replace("/v1/wecom/entry");
}
function embedWecomInit() {
document.body.classList.add("embed-mode");
readFragmentToken();
if (!state.token) { embedWecomRelogin(); return; }
enterApp(takeInitialTaskId());
}
// ───── app 套壳变体 ─────
export function embedAppRelogin() {
// 回登录入口重签:原生壳在 shouldOverrideUrlLoading / WKNavigationDelegate 拦自定义
// scheme(如 zcbotapp://relogin)→ 换新 token 重新 loadUrl;H5 登录页则静默重签后跳回
location.replace(EMBED_RELOGIN_URL);
}
function embedAppInit() {
document.body.classList.add("embed-mode");
readFragmentToken();
if (!state.token) { embedAppRelogin(); return; }
enterApp(takeInitialTaskId());
}
export function embedInit() {
if (EMBED_WECOM) { embedWecomInit(); return; }
if (EMBED_RELOGIN_URL) { embedAppInit(); return; }
if (!EMBED_PARENT_ORIGIN) {
document.body.classList.add("embed-mode", "embed-waiting");
embedShowWaiting("embed 模式缺少 parent_origin 或 relogin_url 参数 (iframe 嵌入用 ?embed=1&parent_origin=https://your-portal.com,App 套壳用 ?embed=1&relogin_url=<登录入口地址>)", true);
return;
}
document.body.classList.add("embed-mode");
window.addEventListener("message", embedHandleMessage);
if (state.token) {
enterApp(takeInitialTaskId());
} else {
document.body.classList.add("embed-waiting");
embedShowWaiting("等待登录…", false);
}
embedPostToParent({ type: "zcbot-ready" });
}