You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Manifest V3的谷歌文档语音转录扩展开发疑问

Manifest V3下Chrome插件对接Google Docs API的修正方案

核心问题梳理

你的代码在MV3下存在几个关键问题:

  • Manifest配置错误:权限划分混乱、OAuth2字段配置不符合规范、CSP限制了外部API脚本加载
  • 内容脚本直接加载GAPI违反MV3安全策略,且无法直接调用Chrome身份验证API
  • 未利用MV3的Service Worker处理后台逻辑,这是MV3替代background.html的核心方式

修正后的manifest.json

{
  "name": "Docs Live Transcriber Plugin",
  "version": "1.0",
  "description": "A plugin for voice dictation and voice functions for GDocs.",
  "manifest_version": 3,
  "author": "",
  "icons": {
    "128": "img/logo.png"
  },
  "permissions": [
    "identity",
    "tabs",
    "storage",
    "management",
    "notifications",
    "unlimitedStorage"
  ],
  "oauth2": {
    "client_id": "你的客户端ID",
    "scopes": [
      "https://www.googleapis.com/auth/userinfo.email",
      "profile",
      "https://www.googleapis.com/auth/documents"
    ]
  },
  "content_scripts": [
    {
      "matches": ["https://docs.google.com/*"],
      "js": ["foreground.js"]
    }
  ],
  "background": {
    "service_worker": "background.js"
  },
  "content_security_policy": {
    "extension_pages": "script-src 'self' https://apis.google.com https://accounts.google.com; object-src 'self'",
    "content_scripts": "script-src 'self' 'unsafe-inline'; object-src 'self'"
  },
  "host_permissions": [
    "https://docs.google.com/*",
    "https://script.google.com/*",
    "https://www.googleapis.com/*",
    "https://accounts.google.com/*"
  ]
}

改动说明:

  • 把原permissions里的URL移到host_permissions,MV3要求权限和主机权限分开
  • 修正oauth2字段:移除不属于该字段的api_key和discovery_docs,补充Docs操作所需的scope
  • 添加background service worker,用于处理GAPI初始化和OAuth验证
  • 调整CSP,允许加载Google API和GIS的脚本源,同时给内容脚本放宽必要的inline脚本限制

新增background.js(替代原background.html)

let gapiInited = false;
let tokenClient = null;
const API_KEY = "你的API密钥";
const DISCOVERY_DOC = "https://docs.googleapis.com/$discovery/rest?version=v1";
const CLIENT_ID = "你的客户端ID";
const SCOPES = "https://www.googleapis.com/auth/userinfo.email profile https://www.googleapis.com/auth/documents";

// 初始化GAPI
async function initGapi() {
  const script = document.createElement('script');
  script.src = 'https://apis.google.com/js/api.js';
  script.onload = async () => {
    await gapi.load('client', async () => {
      await gapi.client.init({
        apiKey: API_KEY,
        discoveryDocs: [DISCOVERY_DOC],
      });
      gapiInited = true;
      console.log("GAPI初始化完成");
    });
  };
  document.head.appendChild(script);
}

// 初始化GIS(Google Identity Services)
function initGis() {
  const script = document.createElement('script');
  script.src = 'https://accounts.google.com/gsi/client';
  script.onload = () => {
    tokenClient = google.accounts.oauth2.initTokenClient({
      client_id: CLIENT_ID,
      scope: SCOPES,
      callback: handleTokenResponse,
    });
    console.log("GIS初始化完成");
  };
  document.head.appendChild(script);
}

// 处理OAuth令牌响应
function handleTokenResponse(response) {
  if (response.error !== undefined) {
    throw response;
  }
  // 存储令牌到chrome.storage供内容脚本使用
  chrome.storage.local.set({ accessToken: response.access_token }, () => {
    console.log("令牌已存储");
  });
}

// 监听内容脚本的登录请求
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.type === "signIn") {
    if (!tokenClient) {
      initGis();
      // 延迟调用确保GIS加载完成
      setTimeout(() => tokenClient.requestAccessToken({ prompt: '' }), 1000);
    } else {
      tokenClient.requestAccessToken({ prompt: '' });
    }
    sendResponse({ status: "登录请求已发起" });
  } else if (request.type === "getDocsContent") {
    // 示例:调用Docs API获取文档内容
    if (!gapiInited) {
      sendResponse({ error: "GAPI未初始化" });
      return;
    }
    chrome.storage.local.get('accessToken', async (data) => {
      gapi.client.setToken({ access_token: data.accessToken });
      try {
        const response = await gapi.client.documents.get({
          documentId: request.documentId,
        });
        sendResponse({ content: response.result });
      } catch (err) {
        sendResponse({ error: err.message });
      }
    });
    return true; // 表示异步响应
  }
});

// 启动时初始化GAPI
initGapi();

说明:

  • 在Service Worker中加载GAPI和GIS,避开内容脚本的环境限制
  • 用chrome.runtime.onMessage和内容脚本通信,处理登录和API调用请求
  • 把OAuth令牌存储到chrome.storage,方便内容脚本获取使用

修正后的foreground.js

// 创建按钮的函数修正:参数错误(buttonId写成button_type)
const createButton = (buttonId, eventListener, innerText) => {
  const button = document.createElement('button');
  button.setAttribute('id', buttonId);
  button.addEventListener('click', eventListener);
  button.innerText = innerText;
  // 添加样式避免被Docs页面样式覆盖
  button.style.padding = '8px 16px';
  button.style.margin = '8px';
  button.style.background = '#4285F4';
  button.style.color = 'white';
  button.style.border = 'none';
  button.style.borderRadius = '4px';
  button.style.cursor = 'pointer';
  return button;
};

// 插入登录按钮到Docs页面
const injectButton = () => {
  // 找Docs页面的工具栏区域插入按钮
  const toolbar = document.querySelector('.docs-titlebar-buttons');
  if (toolbar) {
    const signInBtn = createButton('signInBtn', handleSignIn, '登录并启用转录');
    toolbar.appendChild(signInBtn);
  }
};

// 处理登录请求:给后台发消息
const handleSignIn = async (e) => {
  e.preventDefault();
  try {
    const response = await chrome.runtime.sendMessage({ type: "signIn" });
    console.log(response.status);
  } catch (err) {
    alert(`登录失败:${err.message}`);
  }
};

// 监听后台的令牌更新(可选)
chrome.storage.onChanged.addListener((changes, area) => {
  if (area === 'local' && changes.accessToken) {
    console.log("已获取访问令牌,可以开始转录");
    // 这里可以初始化语音转录逻辑
  }
});

// 页面加载完成后注入按钮
window.addEventListener('load', injectButton);

改动说明:

  • 修正createButton函数的参数错误(原button_type未定义,改为buttonId,改用addEventListener绑定事件)
  • 移除直接加载GAPI的代码,改为通过消息请求后台处理
  • 添加页面元素注入逻辑,把按钮插入到Docs的工具栏
  • 用chrome.runtime.sendMessage和后台通信,处理登录请求

关键注意事项

  1. 确保在Google Cloud Console中正确配置OAuth客户端ID:
    • 应用类型选"Chrome 应用"
    • 填写插件的ID(在chrome://extensions/中查看)
  2. 语音转录逻辑建议在内容脚本中处理,拿到令牌后直接调用Speech-to-Text API,再通过Docs API写入文档
  3. MV3的Service Worker是无状态的,注意令牌的持久化存储(用chrome.storage.local)

内容的提问来源于stack exchange,提问作者VlAd TbK

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 09:55:04