浏览器网络插件

🌐 浏览器网络代理插件

0️⃣ 前言

Firefox 浏览器网络设置功能我觉得还是蛮实用的,但是, ChromeEdge 浏览器却没有类似的简便插件或内建选项。我发现浏览器的网络配置接口实际上是存在的, 看着功能实现不难, 简单的做了一个手动配置网络的插件 🔧🌐.


😎 快速体验

👉 项目源码: Browser_Proxy_Plugin

🎉 欢迎一起交流!💬 (⭐️ start 和 🍴 fork,感谢大家的支持!🙏)

在这里插入图片描述


🔍 浏览器兼容性

目前已在以下浏览器中测试,功能完全正常:

  • 🟢 Chrome 131
  • 🟢 Edge 131

其他现代浏览器(如 Firefox 等)理论上也支持,但没有去测试。


🚀 使用方法

  1. 打开 Chrome 设置 ⚙️

    • 点击 Chrome 窗口右上角的 (三个点)。
    • 依次选择 更多工具 > 扩展程序
  2. 启用开发者模式 🧑‍💻

    • 在扩展程序页面右上角,找到 开发者模式 开关,点击将其打开。
  3. 加载解压后的文件夹 📁

    • 点击页面左上角的 加载已解压的扩展程序 按钮。
    • 在弹出的文件选择窗口中,选择刚刚解压的插件文件夹。
  4. 确认加载成功

    • 如果一切正常,插件会立即出现在扩展程序列表中。
    • 你可以在 Chrome 右上角的扩展图标中看到它。

🔨 简单的实现逻辑

  1. HTML 语言写 popup.html 也就是这个弹出的界面的样式

我尝试引入 VUE3ElementPlus 框架 发现不是那么容易 并且容易出错, 后面调查发现用 Bootstrap 是不错的 有机会可以试试.

  1. 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"));
    }
  );
});

大概的框图可以这么表示, 一看就明白

在这里插入图片描述


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值