写一个浏览器工具插件

概述

最近项目上本地开发,调用线上某个环境的接口去调试,需要复制对应环境的token,然后存到本地localstorage本地后,才能通过用户鉴权(直白点说就是登录了),由于token过期时间比较短,每天上班本地调试都会遇到token过期的问题,需要重新打开线上链接重新拷贝,比较麻烦,因此自己写了一个小工具---Chrome浏览器插件,可以直接拷贝线上环境本地存储的token到本地。

效果图

原理和流程

Chrome浏览器插件开发基于HTML、CSS和JavaScript技术,通过扩展浏览器功能来增强用户体验。其核心原理和开发流程如下:

核心组件

  • ‌ manifest.json ‌:定义插件名称、版本、权限等基本信息。 ‌
  • ‌背景脚本‌(background scripts):长期运行的脚本,负责后台任务。 ‌
  • ‌内容脚本‌(content scripts):注入到网页中的脚本,用于增强或修改页面功能。 ‌
  • ‌用户界面‌:包括弹出页面(popup)和选项页(options)。 ‌

工作流程

  1. ‌创建项目文件夹‌:包含上述文件结构。 ‌
  2. ‌安装插件‌:通过Chrome扩展页面加载解压的扩展程序。 ‌
  3. ‌功能实现‌:通过背景脚本和内容脚本交互实现,例如自动下载网页为Markdown文件或注入自定义脚本。 ‌

开发工具

可使用 Tampermonkey** 等扩展简化开发流程,支持跨域请求和自动化脚本注入。

文件结构

命名插件名称为copy-localstorage-extension text

bash 复制代码
copy-localstorage-extension/
|------popup                # 弹出层
   |------ popup.css        # 弹出层样式
   |------popup.html        # 弹出层html结构
   |------popup.js          # 弹出层js逻辑
├── manifest.json       # 核心配置文件
├── icon.png            # 扩展图标(可选)
└── (其他脚本/资源文件)

文件内容如下

popup/popup.css

css 复制代码
body {
    width: 300px;
    padding: 15px;
    font-family: Arial, sans-serif;
  }
  
  .container {
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
  
  h1 {
    font-size: 18px;
    margin: 0 0 15px 0;
    color: #333;
  }
  
  .form-group {
    display: flex;
    flex-direction: column;
    gap: 5px;
  }
  
  label {
    font-weight: bold;
    font-size: 14px;
  }
  
  input {
    padding: 8px;
    border: 1px solid #ccc;
    border-radius: 4px;
    font-size: 14px;
  }
  
  button {
    padding: 10px;
    background-color: #4CAF50;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
    margin-top: 10px;
  }
  
  button:hover {
    background-color: #45a049;
  }
  
  .status {
    margin-top: 15px;
    padding: 10px;
    border-radius: 4px;
    font-size: 14px;
    display: none;
  }
  
  .status.success {
    display: block;
    background-color: #dff0d8;
    color: #3c763d;
  }
  
  .status.error {
    display: block;
    background-color: #f2dede;
    color: #a94442;
  }

popup/popup.html

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <title>LocalStorage 拷贝工具</title>
  <link rel="stylesheet" href="popup.css">
  <meta charset="utf-8"></meta>
</head>
<body>
  <div class="container">
    <h1>LocalStorage 拷贝</h1>
    
    <div class="form-group">
      <label for="source-domain">源域名:</label>
      <input type="text" id="source-domain" value="https://xx.aa.cn" placeholder="例如: https://example.com">
    </div>
    
    <div class="form-group">
      <label for="target-domain">目标域名:</label>
      <input type="text" id="target-domain" value="https://aa.test.com" placeholder="例如: https://another-site.com">
    </div>
    
    <div class="form-group">
      <label for="storage-key">要拷贝的键名:</label>
      <input type="text" id="storage-key"  value="token" placeholder="例如: user_token">
    </div>
    
    <button id="copy-btn">执行拷贝</button>
    
    <div id="status" class="status"></div>
  </div>
  
  <script src="popup.js"></script>
</body>
</html>

popup/popup.js

js 复制代码
document.getElementById('copy-btn').addEventListener('click', async () => {
    const sourceDomain = document.getElementById('source-domain').value.trim();
    const targetDomain = document.getElementById('target-domain').value.trim();
    const storageKey = document.getElementById('storage-key').value.trim();
    
    const statusEl = document.getElementById('status');
    
    if (!sourceDomain || !targetDomain || !storageKey) {
      showStatus('请填写所有字段', 'error');
      return;
    }
    
    try {
      showStatus('正在处理...', 'info');
      
      // 发送消息给后台脚本
      const response = await chrome.runtime.sendMessage({
        action: 'copyLocalStorage',
        sourceDomain,
        targetDomain,
        storageKey
      });
      
      if (response.success) {
        showStatus(`成功拷贝: ${storageKey} = ${response.value}`, 'success');
      } else {
        showStatus(`错误: ${response.message}`, 'error');
      }
    } catch (error) {
      showStatus(`发生错误: ${error.message}`, 'error');
    }
  });
  
  function showStatus(message, type) {
    const statusEl = document.getElementById('status');
    statusEl.textContent = message;
    statusEl.className = 'status ' + (type || 'info');
  }

background.js

js 复制代码
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
    if (request.action === 'copyLocalStorage') {
      handleCopyLocalStorage(request, sendResponse);
      return true; // 保持消息通道开放以支持异步响应
    }
  });
  
  async function handleCopyLocalStorage(request, sendResponse) {
    const { sourceDomain, targetDomain, storageKey } = request;
    
    try {
      // 第一步:从源域名获取数据
      const sourceValue = await getLocalStorageFromDomain(sourceDomain, storageKey);
      
      if (sourceValue === null) {
        sendResponse({
          success: false,
          message: `源域名 ${sourceDomain} 中未找到键 ${storageKey}`
        });
        return;
      }
      
      // 第二步:将数据写入目标域名
      const writeSuccess = await setLocalStorageToDomain(targetDomain, storageKey, sourceValue);
      
      if (writeSuccess) {
        sendResponse({
          success: true,
          value: sourceValue,
          message: `成功将 ${storageKey} 从 ${sourceDomain} 拷贝到 ${targetDomain}`
        });
      } else {
        sendResponse({
          success: false,
          message: `无法写入目标域名 ${targetDomain}`
        });
      }
    } catch (error) {
      sendResponse({
        success: false,
        message: error.message
      });
    }
  }
  
  function getLocalStorageFromDomain(domain, key) {
    return new Promise((resolve) => {
      chrome.tabs.create({ url: domain, active: false }, (tab) => {
        chrome.scripting.executeScript({
          target: { tabId: tab.id },
          func: (key) => {
            return localStorage.getItem(key);
          },
          args: [key]
        }, (results) => {
          chrome.tabs.remove(tab.id);
          if (results && results[0] && results[0].result !== undefined) {
            resolve(results[0].result);
          } else {
            resolve(null);
          }
        });
      });
    });
  }
  
  function setLocalStorageToDomain(domain, key, value) {
    return new Promise((resolve) => {
      chrome.tabs.create({ url: domain, active: false }, (tab) => {
        chrome.scripting.executeScript({
          target: { tabId: tab.id },
          func: (key, value) => {
            localStorage.setItem(key, value);
            return true;
          },
          args: [key, value]
        }, (results) => {
          chrome.tabs.remove(tab.id);
          resolve(results && results[0] && results[0].result === true);
        });
      });
    });
  }

manifest.json

json 复制代码
{
    "manifest_version": 3,
    "name": "LocalStorage 拷贝工具",
    "version": "1.0",
    "description": "跨域名拷贝 LocalStorage 数据",
    "action": {
      "default_popup": "popup/popup.html"
        },
    "permissions": [
      "storage",
      "scripting",
      "activeTab",
      "tabs"
    ],
    "host_permissions": [
      "<all_urls>"
    ],
    "background": {
      "service_worker": "background.js"
    }
  }

注册插件

上面插件代码写好后,需要将插件注册到谷歌浏览器的插件中心 到谷歌浏览器扩展中心,点击加载未打包的扩展程序,然后选择上面我们开发插件文件夹就可以使用了。

总结

按照上面的规范,就可以写任意我们想要的插件,本质还是前端html+css+js逻辑,日常工作中,可以鉴于此,写一些小工具插件,提升我们的开发效率

相关推荐
子兮曰3 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰3 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万3 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝3 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋3 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁3 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
李少兄3 天前
JavaScript 隐式全局变量解析
javascript
汉堡大王95273 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大3 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师3 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端