🌐 浏览器网络代理插件
0️⃣ 前言
Firefox
浏览器网络设置功能我觉得还是蛮实用的,但是, Chrome
和 Edge
浏览器却没有类似的简便插件或内建选项。我发现浏览器的网络配置接口实际上是存在的, 看着功能实现不难, 简单的做了一个手动配置网络的插件 🔧🌐.
😎 快速体验
👉 项目源码: Browser_Proxy_Plugin
🎉 欢迎一起交流!💬 (⭐️ start 和 🍴 fork,感谢大家的支持!🙏)
🔍 浏览器兼容性
目前已在以下浏览器中测试,功能完全正常:
- 🟢 Chrome 131
- 🟢 Edge 131
其他现代浏览器(如 Firefox 等)理论上也支持,但没有去测试。
🚀 使用方法
-
打开 Chrome 设置 ⚙️
- 点击 Chrome 窗口右上角的
⋮
(三个点)。 - 依次选择 更多工具 > 扩展程序。
- 点击 Chrome 窗口右上角的
-
启用开发者模式 🧑💻
- 在扩展程序页面右上角,找到 开发者模式 开关,点击将其打开。
-
加载解压后的文件夹 📁
- 点击页面左上角的 加载已解压的扩展程序 按钮。
- 在弹出的文件选择窗口中,选择刚刚解压的插件文件夹。
-
确认加载成功 ✅
- 如果一切正常,插件会立即出现在扩展程序列表中。
- 你可以在 Chrome 右上角的扩展图标中看到它。
🔨 简单的实现逻辑
- 用
HTML
语言写popup.html
也就是这个弹出的界面的样式
我尝试引入
VUE3
和ElementPlus
框架 发现不是那么容易 并且容易出错, 后面调查发现用Bootstrap
是不错的 有机会可以试试.
- 在
module/popup.js
里面获取元素的id
然后做事件的监听和业务的实现, 代码如下
document.addEventListener("DOMContentLoaded", function () {
// 默认的bypass列表
const defaultBypassList = [
"127.0.0.1/8",
"192.168.1.0/24",
"::1",
"localhost",
".net.nz",
];
// 这个插件默认的浏览器存储的数据格式
let initialSettings = {
bypassList: [],
httpPort: "8080",
httpProxy: "example.com",
httpsPort: "",
httpsProxy: "",
proxyEnabled: false,
socksHost: "",
socksPort: "",
useForHttps: false,
};
/** ================== Step 1 ================== */
// 得到插件的全部 HTML Element
const noProxyToggle = document.getElementById("no-proxy-checkbox");
const manualProxyToggle = document.getElementById("manual-proxy-checkbox");
const proxyConfiguration = document.getElementById("proxy-configuration");
const proxyPanel = document.getElementById("proxy-panel");
const httpProxyInput = document.getElementById("http-proxy");
const httpPortInput = document.getElementById("http-port");
const httpsProxyInput = document.getElementById("https-proxy");
const httpsPortInput = document.getElementById("https-port");
const socksHostInput = document.getElementById("socks-host");
const socksPortInput = document.getElementById("socks-port");
const bypassListInput = document.getElementById("bypass-list");
const useForHttpsCheckbox = document.getElementById("use-for-https");
const applyButton = document.getElementById("apply-button");
const cancelButton = document.getElementById("cancel-button");
/** ================== Step 2 ================== */
// 给插件的HTML Element注入监听事件 观察变化
// 2.1 不使用代理和使用手动代理的互斥行为
noProxyToggle.addEventListener("change", () => {
if (noProxyToggle.checked) manualProxyToggle.checked = false;
else manualProxyToggle.checked = true;
// 主动触发一次使用手动代理的行为促使它去修改样式
manualProxyToggle.dispatchEvent(new Event("change"));
});
manualProxyToggle.addEventListener("change", () => {
if (manualProxyToggle.checked) {
proxyConfiguration.classList.remove("disabled");
proxyPanel.classList.remove("not-allowed");
noProxyToggle.checked = false;
} else {
proxyConfiguration.classList.add("disabled");
proxyPanel.classList.add("not-allowed");
noProxyToggle.checked = true;
}
});
// 2.2 HTTP 和 HTTPS 如果是共享状态的话需要同步更新数据
httpProxyInput.addEventListener("input", () => {
if (useForHttpsCheckbox.checked)
httpsProxyInput.value = httpProxyInput.value;
});
httpPortInput.addEventListener("input", () => {
if (useForHttpsCheckbox.checked) httpsPortInput.value = httpPortInput.value;
});
useForHttpsCheckbox.addEventListener("change", () => {
if (useForHttpsCheckbox.checked) {
httpsProxyInput.classList.add("disabled", "not-allowed");
httpsPortInput.classList.add("disabled", "not-allowed");
httpsProxyInput.value = httpProxyInput.value;
httpsPortInput.value = httpPortInput.value;
} else {
httpsProxyInput.classList.remove("disabled", "not-allowed");
httpsPortInput.classList.remove("disabled", "not-allowed");
}
});
// 2.3 从 HTML Element 上拿出状态写入存储 并且需要立即生效设置
applyButton.addEventListener("click", () => {
// 获取用户输入并将其分割为数组,同时移除多余的空格
const bypassList = bypassListInput.value
.split(";")
.map((url) => url.trim())
.filter(Boolean);
// 将默认的bypassList加入用户输入的bypassList
const finalBypassList = [...new Set([...defaultBypassList, ...bypassList])];
const proxyConfig = {
mode: "fixed_servers",
rules: {
singleProxy: {
scheme: "http",
host: httpProxyInput.value || "example.com",
port: parseInt(httpPortInput.value) || 8080,
},
// 使用合并后的 bypassList
bypassList: finalBypassList,
},
};
// 保存设置到 Chrome 存储中
chrome.storage.sync.set(
{
proxyEnabled: manualProxyToggle.checked,
httpProxy: httpProxyInput.value,
httpPort: httpPortInput.value,
httpsProxy: useForHttpsCheckbox.checked
? httpProxyInput.value
: httpsProxyInput.value,
httpsPort: useForHttpsCheckbox.checked
? httpPortInput.value
: httpsPortInput.value,
socksHost: socksHostInput.value,
socksPort: socksPortInput.value,
// 保存合并后的 bypassList
bypassList: finalBypassList,
useForHttps: useForHttpsCheckbox.checked,
// 保存代理配置
proxySettings: proxyConfig,
},
// 立即应用设置
function () {
if (manualProxyToggle.checked) {
chrome.proxy.settings.set(
{
value: proxyConfig,
scope: "regular",
},
function () {}
);
} else {
chrome.proxy.settings.clear({ scope: "regular" }, function () {});
}
}
);
window.close();
});
// 2.4 取消设置需要回退回存储中记录的状态
cancelButton.addEventListener("click", () => {
// 恢复到初始设置
if (initialSettings.proxyEnabled)
manualProxyToggle.checked = initialSettings.proxyEnabled;
if (initialSettings.httpProxy)
httpProxyInput.value = initialSettings.httpProxy;
if (initialSettings.httpPort)
httpPortInput.value = initialSettings.httpPort;
if (initialSettings.httpsProxy)
httpsProxyInput.value = initialSettings.httpsProxy;
if (initialSettings.httpsPort)
httpsPortInput.value = initialSettings.httpsPort;
if (initialSettings.socksHost)
socksHostInput.value = initialSettings.socksHost;
if (initialSettings.socksPort)
socksPortInput.value = initialSettings.socksPort;
const userBypassList = initialSettings.bypassList
? initialSettings.bypassList
: [];
const combinedBypassList = [
...new Set([...defaultBypassList, ...userBypassList]),
];
bypassListInput.value = combinedBypassList.join(", ");
if (initialSettings.useForHttps !== undefined)
useForHttpsCheckbox.checked = initialSettings.useForHttps;
// 关闭 popup
window.close();
});
/** ================== Step 3 ================== */
// 入口函数 插件初始化的操作
chrome.storage.sync.get(
[
"proxyEnabled",
"httpProxy",
"httpPort",
"httpsProxy",
"httpsPort",
"socksHost",
"socksPort",
"bypassList",
"useForHttps",
],
function (result) {
initialSettings = result;
// 互斥的两个开关选项
if (result.proxyEnabled) manualProxyToggle.checked = true;
manualProxyToggle.dispatchEvent(new Event("change"));
if (result.httpProxy) httpProxyInput.value = result.httpProxy;
if (result.httpPort) httpPortInput.value = result.httpPort;
if (result.httpsProxy) httpsProxyInput.value = result.httpsProxy;
if (result.httpsPort) httpsPortInput.value = result.httpsPort;
if (result.socksHost) socksHostInput.value = result.socksHost;
if (result.socksPort) socksPortInput.value = result.socksPort;
// 合并不显示默认去掉的bypassList
const userBypassList = result.bypassList ? result.bypassList : [];
const combinedBypassList = userBypassList.filter(
(item) => !defaultBypassList.includes(item)
);
bypassListInput.value = combinedBypassList.join("; ");
if (result.useForHttps) useForHttpsCheckbox.checked = result.useForHttps;
useForHttpsCheckbox.dispatchEvent(new Event("change"));
}
);
});
大概的框图可以这么表示, 一看就明白