import { normalizeBaseUrl, vaultFetch } from "./lib/api.js"; import { loadConfig, loadPersistent } from "./lib/storage.js"; const $ = (id) => document.getElementById(id); function setStatus(text, kind) { const el = $("status"); el.textContent = text || ""; el.className = kind || ""; } function setWorkspaceHint(rootUri, rootPath, source) { const el = $("workspaceHint"); if (!el) return; if (!rootUri) { el.textContent = ""; return; } const path = rootPath || rootUri.replace(/^file:\/\//, ""); const src = source === "default" ? "账号默认工作区" : source === "override" ? "手动覆盖" : "已保存"; el.innerHTML = `密码箱写入:${path}(${src})`; } /** * 必须在用户点击手势内调用:MV3 从 service worker 弹权限常被静默拒绝, * 外网 baseUrl(含非标端口 frp)没有 host 权限时 fetch 就是 Failed to fetch。 * 同时申请 http(s)://*/*,否则外站 Cookie 读不到、登录态无法保存。 * @param {string} baseUrl */ async function requestBaseUrlPermission(baseUrl) { const origin = new URL(normalizeBaseUrl(baseUrl)).origin; // Broad first so cookie capture works on every login site after connect. const broad = ["http://*/*", "https://*/*", `${origin}/*`]; try { const haveBroad = await chrome.permissions.contains({ origins: broad }); if (haveBroad) return true; const ok = await chrome.permissions.request({ origins: broad }); if (ok) return true; } catch { /* fall through to narrow */ } const origins = [`${origin}/*`]; const have = await chrome.permissions.contains({ origins }); if (have) return true; try { return await chrome.permissions.request({ origins }); } catch (e) { throw new Error( `无法申请访问权限 ${origin}:${e?.message || e}(请在扩展详情里允许该站点;登录态还需「所有网站」)` ); } } function formatFetchError(e, baseUrl) { const msg = e?.message || String(e); if (/Failed to fetch|NetworkError|Load failed/i.test(msg)) { return ( `无法访问 ${baseUrl || "baseUrl"}:${msg}。` + `常见原因:① 未点允许扩展访问该站点 ② 外网隧道未通 ③ 证书不受信任。` + `请重新加载扩展后再点「测试连接」,并允许弹窗中的站点权限。` ); } return msg; } async function hydrate() { const p = await loadPersistent(); if (p.baseUrl) $("baseUrl").value = p.baseUrl; // 默认不展示手填;仅当用户曾覆盖时回填并展开 if (p.rootUri) { $("rootUri").value = p.rootUri; const adv = $("advRoot"); if (adv && p.rootUri) { // 不自动展开;连接后 hint 会显示实际路径 } } if (p.email) $("email").value = p.email; const cfg = await loadConfig(); if (cfg.connected) { setStatus( `已连接 · ${cfg.email || cfg.userId || ""} · token 有效`, "ok" ); setWorkspaceHint(cfg.rootUri || p.rootUri, p.rootPath, "saved"); } else if (cfg.baseUrl) { setStatus("已保存配置,但 token 缺失(浏览器重启后需重新登录)", "err"); setWorkspaceHint(p.rootUri, p.rootPath, "saved"); } } $("test").onclick = async () => { setStatus("测试中…"); let baseUrl = ""; try { baseUrl = normalizeBaseUrl($("baseUrl").value); const ok = await requestBaseUrlPermission(baseUrl); if (!ok) { setStatus( `已取消访问权限:需要允许扩展访问 ${new URL(baseUrl).origin}`, "err" ); return; } // whoami 不依赖 /api/health(外网入口可能未挂 health) const data = await vaultFetch(baseUrl, "/api/auth/whoami", { method: "GET", credentials: true, }); setStatus( `可达 · ${baseUrl} · whoami ok=${Boolean(data?.ok ?? true)}`, "ok" ); } catch (e) { setStatus(formatFetchError(e, baseUrl || $("baseUrl").value), "err"); } }; $("connect").onclick = async () => { setStatus("登录中…"); setWorkspaceHint("", ""); const password = $("password").value; let baseUrl = ""; try { baseUrl = normalizeBaseUrl($("baseUrl").value); // 关键:在点击手势内申请 host 权限,再交给 background 登录 const ok = await requestBaseUrlPermission(baseUrl); if (!ok) { setStatus( `已取消访问权限:需要允许扩展访问 ${new URL(baseUrl).origin}(含端口)`, "err" ); return; } const res = await chrome.runtime.sendMessage({ type: "CONNECT", baseUrl, // 留空 → background 调 default workspace rootUri: ($("rootUri").value || "").trim(), email: $("email").value, password, }); $("password").value = ""; if (!res?.ok) { setStatus(res?.error || "连接失败", "err"); return; } const r = res.result || {}; if (r.rootUri) $("rootUri").value = r.rootUri; setStatus( `已连接 · ${r.email || r.userId || ""} · 过期 ${r.expiresAt || "—"}`, "ok" ); setWorkspaceHint(r.rootUri, r.rootPath, r.workspaceSource || "default"); } catch (e) { $("password").value = ""; setStatus(formatFetchError(e, baseUrl || $("baseUrl").value), "err"); } }; $("disconnect").onclick = async () => { await chrome.runtime.sendMessage({ type: "DISCONNECT" }); setStatus("已断开", "err"); setWorkspaceHint("", ""); }; hydrate();