const STORAGE_KEYS = { ACTIVE_PROFILE_ID: "activeProfileId", PROFILES: "profiles", }; const DEFAULT_PROFILES = [ { id: "direct", name: "直连(关闭代理)", type: "direct" }, { id: "system", name: "跟随系统代理", type: "system" }, { id: "auto", name: "自动检测(WPAD)", type: "auto_detect" }, { id: "local-http", name: "HTTP 127.0.0.1:7897", type: "fixed", scheme: "http", host: "127.0.0.1", port: 7897, bypassList: ["localhost", "127.0.0.1"], }, { id: "local-socks5", name: "SOCKS5 127.0.0.1:7897", type: "fixed", scheme: "socks5", host: "127.0.0.1", port: 7897, bypassList: ["localhost", "127.0.0.1"], }, { id: "pac", name: "PAC(自定义 URL)", type: "pac", pacUrl: "", }, { id: "custom", name: "自定义代理", type: "fixed", scheme: "http", host: "", port: 0, bypassList: ["localhost", "127.0.0.1"], }, ]; // 动态图标(仅使用 PNG) // 只支持 PNG,去掉 SVG 支持与栅格化逻辑 const ICON_FALLBACK_PNG = { open: { 16: "icons/open-16.png", 32: "icons/open-32.png" }, close: { 16: "icons/close-16.png", 32: "icons/close-32.png" }, }; chrome.runtime.onInstalled.addListener(async () => { const { profiles, activeProfileId } = await chrome.storage.local.get([ STORAGE_KEYS.PROFILES, STORAGE_KEYS.ACTIVE_PROFILE_ID, ]); const ensured = ensureProfiles(profiles); await chrome.storage.local.set({ [STORAGE_KEYS.PROFILES]: ensured }); if (!activeProfileId) { await chrome.storage.local.set({ [STORAGE_KEYS.ACTIVE_PROFILE_ID]: "direct", }); } await applyActiveProfile(); }); chrome.runtime.onStartup.addListener(async () => { const { profiles } = await chrome.storage.local.get(STORAGE_KEYS.PROFILES); const ensured = ensureProfiles(profiles); await chrome.storage.local.set({ [STORAGE_KEYS.PROFILES]: ensured }); await applyActiveProfile(); }); chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { (async () => { if (message?.type === "applyProfileById") { await chrome.storage.local.set({ [STORAGE_KEYS.ACTIVE_PROFILE_ID]: message.profileId, }); await applyActiveProfile(); sendResponse({ ok: true }); } else if (message?.type === "updateCustomProxy") { const { scheme, host, port, bypassList } = message; const { profiles } = await chrome.storage.local.get( STORAGE_KEYS.PROFILES ); const updated = (profiles || []).map((p) => p.id === "custom" ? { ...p, type: "fixed", scheme: scheme || "http", host: host || "", port: Number(port) || 0, bypassList: Array.isArray(bypassList) ? bypassList : typeof bypassList === "string" && bypassList.trim() ? bypassList .split(",") .map((s) => s.trim()) .filter(Boolean) : p.bypassList || [], } : p ); await chrome.storage.local.set({ [STORAGE_KEYS.PROFILES]: updated }); const { activeProfileId } = await chrome.storage.local.get( STORAGE_KEYS.ACTIVE_PROFILE_ID ); if (activeProfileId === "custom") { await applyActiveProfile(); } sendResponse({ ok: true }); } else if (message?.type === "updatePacUrl") { const { profiles } = await chrome.storage.local.get( STORAGE_KEYS.PROFILES ); const updated = (profiles || []).map((p) => p.id === "pac" ? { ...p, pacUrl: message.pacUrl || "" } : p ); await chrome.storage.local.set({ [STORAGE_KEYS.PROFILES]: updated }); const { activeProfileId } = await chrome.storage.local.get( STORAGE_KEYS.ACTIVE_PROFILE_ID ); if (activeProfileId === "pac") { await applyActiveProfile(); } sendResponse({ ok: true }); } else if (message?.type === "getState") { const { profiles, activeProfileId } = await chrome.storage.local.get([ STORAGE_KEYS.PROFILES, STORAGE_KEYS.ACTIVE_PROFILE_ID, ]); const ensured = ensureProfiles(profiles); if (ensured !== profiles) { await chrome.storage.local.set({ [STORAGE_KEYS.PROFILES]: ensured }); } sendResponse({ ok: true, profiles: ensured, activeProfileId }); } else if (message?.type === "resetProfiles") { await chrome.storage.local.set({ [STORAGE_KEYS.PROFILES]: DEFAULT_PROFILES, [STORAGE_KEYS.ACTIVE_PROFILE_ID]: "direct", }); await applyActiveProfile(); sendResponse({ ok: true }); } })(); return true; }); async function applyActiveProfile() { const { profiles, activeProfileId } = await chrome.storage.local.get([ STORAGE_KEYS.PROFILES, STORAGE_KEYS.ACTIVE_PROFILE_ID, ]); const profile = (profiles || DEFAULT_PROFILES).find((p) => p.id === activeProfileId) || DEFAULT_PROFILES[0]; const config = buildProxyConfig(profile); // 为避免残留规则影响,先清理再设置 await new Promise((resolve) => chrome.proxy.settings.clear({ scope: "regular" }, resolve) ); await new Promise((resolve) => { chrome.proxy.settings.set({ value: config, scope: "regular" }, () => { if (chrome.runtime.lastError) { console.warn("proxy set error:", chrome.runtime.lastError); } resolve(); }); }); await updateBadge(profile); await updateIcon(profile); } function buildProxyConfig(profile) { if (profile.type === "direct") { return { mode: "direct" }; } if (profile.type === "system") { return { mode: "system" }; } if (profile.type === "auto_detect") { return { mode: "auto_detect" }; } if (profile.type === "pac") { const url = (profile.pacUrl || "").trim(); if (url) { return { mode: "pac_script", pacScript: { url } }; } return { mode: "direct" }; } if (profile.type === "fixed") { const singleProxy = { scheme: profile.scheme || "http", host: profile.host, port: Number(profile.port) || 0, }; if (!singleProxy.host || !singleProxy.port) { // 避免设置非法 fixed 导致联网异常 return { mode: "direct" }; } const bypassList = Array.isArray(profile.bypassList) ? profile.bypassList : []; return { mode: "fixed_servers", rules: { singleProxy, bypassList }, }; } return { mode: "direct" }; } async function updateBadge(profile) { // 仅显示图标,不显示任何徽章文字 if (chrome.action?.setBadgeText) { await chrome.action.setBadgeText({ text: "" }); } if (chrome.action?.setBadgeBackgroundColor) { await chrome.action.setBadgeBackgroundColor({ color: "#00000000" }); } } async function updateIcon(profile) { if (!chrome.action?.setIcon) return; const isProxyOn = profile.type !== "direct"; const path = isProxyOn ? ICON_FALLBACK_PNG.open : ICON_FALLBACK_PNG.close; try { await chrome.action.setIcon({ path }); } catch (e) { console.warn("setIcon failed", e); } } // 已移除 SVG 栅格化与程序化绘制,专用 PNG 路径作为图标资源 function ensureProfiles(existing) { const current = Array.isArray(existing) ? [...existing] : []; const byId = new Map(current.map((p) => [p.id, p])); const need = [ "direct", "system", "auto", "local-http", "local-socks5", "pac", "custom", ]; let changed = false; for (const id of need) { if (!byId.has(id)) { const def = DEFAULT_PROFILES.find((p) => p.id === id); if (def) { current.push(def); byId.set(id, def); changed = true; } } } // 修补关键字段 const custom = byId.get("custom"); if (custom) { if (!custom.scheme) (custom.scheme = "http"), (changed = true); if (typeof custom.port !== "number") (custom.port = Number(custom.port) || 0), (changed = true); if (!Array.isArray(custom.bypassList)) (custom.bypassList = ["localhost", "127.0.0.1"]), (changed = true); custom.type = "fixed"; } const auto = byId.get("auto"); if (auto && auto.type !== "auto_detect") { auto.type = "auto_detect"; changed = true; } return changed ? current : current; }