/** * 锁定 / 解锁界面与顶栏锁定按钮。 * 解锁成功后通过 ctx 进入主界面(恢复草稿 + 渲染环境与配置列表)。 */ import { t } from "../i18n.js"; import { sendMessage } from "../messaging.js"; import type { LockMode } from "../types.js"; import { $ } from "./dom"; import type { PopupCtx } from "./context"; const lockScreen = $("lockScreen"); const mainContainer = $("mainContainer"); const lockTitle = $("lockTitle"); const lockHint = $("lockHint"); const lockPassword = $("lockPassword"); const lockPasswordConfirm = $("lockPasswordConfirm"); const lockConfirmGroup = $("lockConfirmGroup"); const lockSubmitBtn = $("lockSubmitBtn"); const lockWarn = $("lockWarn"); const lockBtn = $("lockBtn"); export function initLock(ctx: PopupCtx) { let lockMode: LockMode = "setup"; // "setup" | "unlock" let lockAfterSetup = false; function showLockScreen(mode: LockMode): void { lockMode = mode; lockScreen.style.display = "flex"; mainContainer.style.display = "none"; lockPassword.value = ""; lockPasswordConfirm.value = ""; lockWarn.textContent = ""; lockWarn.style.display = "none"; if (mode === "setup") { lockTitle.textContent = t("lockSetupTitle"); lockHint.textContent = t("lockSetupHint"); lockConfirmGroup.style.display = "block"; lockSubmitBtn.textContent = t("lockSetupBtn"); } else { lockTitle.textContent = t("lockUnlockTitle"); lockHint.textContent = t("lockUnlockHint"); lockConfirmGroup.style.display = "none"; lockSubmitBtn.textContent = t("lockUnlockBtn"); } setTimeout(() => lockPassword.focus(), 50); } function showMainUI() { lockScreen.style.display = "none"; mainContainer.style.display = "flex"; } async function enterMain() { showMainUI(); await ctx.loadDraft(); await ctx.renderEnvSelect(); await ctx.renderConfigList(); } async function initLockState() { try { const resp = await sendMessage({ action: "getVaultStatus" }); lockBtn.hidden = false; if (!(resp as any).hasVault) { // 无 vault:明文模式,直接进入,不锁定 await enterMain(); } else if (!(resp as any).unlocked) { showLockScreen("unlock"); } else { await enterMain(); } } catch (err) { console.warn("[AutoLogin]", t("logGetLockStatusFail"), err); lockBtn.hidden = false; await enterMain(); } } lockSubmitBtn.addEventListener("click", async () => { const pwd = lockPassword.value; if (!pwd) { lockWarn.textContent = t("lockWarnEmpty"); lockWarn.style.display = "block"; return; } if (lockMode === "setup") { if (pwd.length < 6) { lockWarn.textContent = t("lockWarnShort"); lockWarn.style.display = "block"; return; } if (pwd !== lockPasswordConfirm.value) { lockWarn.textContent = t("lockWarnMismatch"); lockWarn.style.display = "block"; return; } try { await sendMessage({ action: "setupVault", password: pwd }); ctx.showToast(t("toastPasswordSet")); if (lockAfterSetup) { lockAfterSetup = false; await sendMessage({ action: "lock" }); showLockScreen("unlock"); ctx.showToast(t("toastLocked")); } else { await enterMain(); } } catch (err) { lockWarn.textContent = t("lockWarnSetupFail", (err as Error).message || String(err)); lockWarn.style.display = "block"; } } else { try { const resp = await sendMessage({ action: "unlock", password: pwd }); if ((resp as any).success) { ctx.showToast(t("toastUnlocked")); await enterMain(); } else { lockWarn.textContent = (resp as any).error || t("lockWarnUnlockFail", ""); lockWarn.style.display = "block"; } } catch (err) { lockWarn.textContent = t("lockWarnUnlockFail", (err as Error).message || String(err)); lockWarn.style.display = "block"; } } }); lockPassword.addEventListener("keydown", (e: KeyboardEvent) => { if (e.key === "Enter") lockSubmitBtn.click(); }); lockPasswordConfirm.addEventListener("keydown", (e: KeyboardEvent) => { if (e.key === "Enter") lockSubmitBtn.click(); }); lockBtn.addEventListener("click", async () => { const resp = await sendMessage({ action: "getVaultStatus" }).catch(() => null); if (resp && !(resp as any).hasVault) { // 明文模式:先设密码再锁定 lockAfterSetup = true; showLockScreen("setup"); return; } if (!confirm(t("lockConfirmPrompt"))) return; await sendMessage({ action: "lock" }); showLockScreen("unlock"); ctx.showToast(t("toastLocked")); }); // 弹窗内的点击视为用户活动,重置 background 自动锁定计时 document.addEventListener("click", () => { if (lockScreen.style.display === "none") { sendMessage({ action: "ping" }, 1).catch(() => {}); } }); return { initLockState }; }