Files
auto-login/background.js
T
2026-09-14 13:45:17 +08:00

174 lines
5.8 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.
// background.js —— Service Worker,负责:
// 1. Side Panel 打开行为
// 2. 口令锁状态管理(解锁后内存持有 AES 密钥)
// 3. session 数据变化时自动加密回写 local
// 4. 闲置自动锁定
// 5. 旧版明文库迁移到加密库
importScripts("crypto-store.js");
// 内存中的派生密钥,解锁后才有值,锁定/SW 重启后清空
let masterKey = null;
// 闲置自动锁定计时器
let autoLockTimer = null;
const AUTO_LOCK_MS = 10 * 60 * 1000; // 10 分钟无操作自动锁定
// 全局启用 Side Panel
chrome.runtime.onInstalled.addListener(() => {
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true })
.catch((err) => console.warn("[AutoLogin] 设置 sidePanel 行为失败:", err));
// 安装/升级时迁移旧版明文库
migrateLegacyVault();
console.log("[AutoLogin] 扩展已安装");
});
chrome.action.onClicked.addListener((tab) => {
if (tab?.id !== undefined) {
chrome.sidePanel.open({ tabId: tab.id }).catch(() => {});
}
});
/**
* 旧版迁移:如果 local 中存在明文 environments 但没有 vaultMeta,
* 则提示用户需要先设置口令(无法在无口令下加密)。
* 这里把明文数据暂存,等用户设置口令时由 setupVault 导入。
*/
async function migrateLegacyVault() {
const data = await chrome.storage.local.get(["environments", "loginConfigs", "vaultMeta"]);
const hasVault = !!data.vaultMeta;
if (hasVault) return;
// 有旧版明文数据:迁移为待导入状态
const legacyEnvs = data.environments;
const legacyConfigs = data.loginConfigs;
if (legacyEnvs || legacyConfigs) {
let pending = null;
if (Array.isArray(legacyEnvs) && legacyEnvs.length > 0) {
pending = { type: "environments", data: legacyEnvs };
} else if (Array.isArray(legacyConfigs) && legacyConfigs.length > 0) {
pending = {
type: "configs",
data: [{ id: "env_legacy", name: "默认", configs: legacyConfigs }],
};
}
if (pending) {
await chrome.storage.local.set({ pendingImport: pending });
// 清掉明文,避免继续以明文存在
await chrome.storage.local.remove(["environments", "loginConfigs"]);
console.log("[AutoLogin] 旧版明文数据已标记为待导入,等待设置口令后加密");
}
}
}
/** 重置闲置自动锁定计时器 */
function resetAutoLockTimer() {
if (autoLockTimer) clearTimeout(autoLockTimer);
if (masterKey) {
autoLockTimer = setTimeout(() => {
console.log("[AutoLogin] 闲置超时,自动锁定");
doLock();
}, AUTO_LOCK_MS);
}
}
async function doLock() {
masterKey = null;
if (autoLockTimer) { clearTimeout(autoLockTimer); autoLockTimer = null; }
await CryptoStore.lock();
}
/**
* 监听 session 中 environments 变化:用内存密钥重新加密写回 local。
* 这是"加密透明化"的核心——EnvStore 只管 session,加密由这里自动完成。
*/
chrome.storage.onChanged.addListener(async (changes, areaName) => {
if (areaName !== "session") return;
if (!changes.environments || !masterKey) return;
try {
const environments = changes.environments.newValue;
if (!environments) return; // 锁定时被清除,无需加密
await CryptoStore.persistEncrypted(environments, masterKey);
} catch (err) {
console.warn("[AutoLogin] 加密回写失败:", err);
}
});
// 消息处理:来自 popup / content script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
(async () => {
try {
switch (message.action) {
case "getVaultStatus": {
const hasVault = await CryptoStore.hasVault();
const unlocked = hasVault ? !!masterKey : false;
const pending = (await chrome.storage.local.get("pendingImport")).pendingImport || null;
sendResponse({ hasVault, unlocked, pendingImport: !!pending });
break;
}
case "setupVault": {
// 首次设置口令。如果有待导入的旧数据,一并加密。
const pendingData = await chrome.storage.local.get("pendingImport");
let initialEnvs = [];
if (pendingData.pendingImport) {
initialEnvs = pendingData.pendingImport.data;
} else {
// 预置 3 个环境
const PRESET = ["个人", "测试", "生产"];
initialEnvs = PRESET.map((name) => ({
id: "env_" + Date.now().toString(36) + Math.random().toString(36).slice(2, 8),
name,
configs: [],
}));
}
masterKey = await CryptoStore.setupVault(message.password, initialEnvs);
await chrome.storage.local.remove("pendingImport");
resetAutoLockTimer();
sendResponse({ success: true });
break;
}
case "unlock": {
try {
const { key } = await CryptoStore.unlock(message.password);
masterKey = key;
resetAutoLockTimer();
sendResponse({ success: true });
} catch (err) {
sendResponse({ success: false, error: err.message });
}
break;
}
case "lock": {
await doLock();
sendResponse({ success: true });
break;
}
case "getActivity": {
// 供 popup 显示闲置倒计时
sendResponse({
unlocked: !!masterKey,
autoLockMs: AUTO_LOCK_MS,
});
break;
}
case "ping": {
// 任何消息都算用户活动,重置自动锁定计时
resetAutoLockTimer();
sendResponse({ pong: true });
break;
}
default:
sendResponse({ error: "unknown action" });
}
} catch (err) {
sendResponse({ success: false, error: err.message });
}
})();
return true; // 异步响应
});