first commit
This commit is contained in:
276
background.js
Normal file
276
background.js
Normal file
@ -0,0 +1,276 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user