ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

浏览器扩展接入AI助手必踩的四个坑:CSP、密钥、Service Worker与页面冲突

浏览器扩展接入AI助手必踩的四个坑:CSP、密钥、Service Worker与页面冲突 如果你的 AI 助手扩展在本地开发时一切正常一打包发布就报错问题大概率不在模型 API而在浏览器扩展和 AI 服务之间的那几条边界上。很多人做 browser extension 时都会踩同一个坑把扩展当成普通网页来写等到发布才发现CSP 策略、跨域请求、Service Worker 生命周期、密钥存储和认证流程每一个环节都可能成为翻车点。这篇文章想用“复盘”的方式讲清楚当你在浏览器扩展里接入一个 AI assistant 时最容易坏掉的是哪几处为什么坏以及如何用最小成本避免。适合正在做或准备做扩展型 AI 助手的开发者阅读无论是 Chrome 还是 Edge 等 Chromium 系浏览器思路基本一致。读完你会得到三样东西一份扩展加 AI 助手的架构判断一份可以直接运行的 MV3 最小示例一张发布前必须检查的排错清单。文章不会逐条复述官方文档而是把真正影响上线的问题挑出来讲。1. 这篇文章真正要解决的问题先说一个反直觉的事实给浏览器扩展加一个 AI 助手工作量最小的部分是 AI 本身。模型调用就是一次 HTTP 请求难点在于这个请求放在扩展的哪一层、用什么权限发、密钥存在哪里、用户怎么登录、长时间对话会不会被浏览器杀掉。很多团队把“AI 助手扩展”想成了一个 popup 页面加一个 API 调用结果发布后收到大量用户反馈按钮消失了、请求被 CORS 拦截、登录状态莫名其妙丢失、流式回复刚到一半就断掉。这些问题在本地开发时几乎不会暴露因为它们依赖的是运行时环境而不是代码逻辑。这篇文章要解决的是四类核心问题扩展的权限模型和 CSP 策略如何影响 AI 接口调用。API Key 和用户认证应该放在哪一层才能既安全又不影响体验。MV3 Service Worker 生命周期如何打断流式输出和会话状态。content script 与页面 DOM 冲突如何保证 AI 助手入口稳定可用。如果你是前端开发者可能熟悉网页里的 fetch、localStorage 和 OAuth但在扩展里这些用法都有额外限制。如果你是后端开发者可能擅长服务端认证但不熟悉扩展的运行时边界。这篇文章尽量把两端的信息都覆盖到。一个更稳妥的判断是AI 助手扩展能不能顺利发布取决于工程边界意识而不是模型能力。模型可以随时换扩展与浏览器、扩展与 AI 服务之间的那层胶水代码决定了产品能不能稳定跑起来。2. 浏览器扩展 AI 助手的架构边界写扩展 AI 助手之前先要理解 Chrome 扩展Manifest V3的三层运行时架构。这三层各有各的职责和限制AI 调用放错层就会以各种奇怪的方式坏掉。第一层是 popup 页面。它是点击工具栏图标后弹出的窗口本质是一个 HTML 页面生命周期和普通网页类似但窗口一旦关闭或失去焦点页面状态就可能被销毁。适合做输入框、按钮、结果展示这类轻交互不适合做长任务。第二层是 content script。它运行在用户浏览的普通网页上下文中可以操作 DOM、监听页面事件但它处于一个“隔离世界”中不能直接访问页面里的 JavaScript 变量也不能直接调用扩展的 API。它的作用主要是读取页面内容、注入 UI 元素、监听用户操作。第三层是 background service worker。它是扩展的主进程负责全局事件处理、跨域请求、消息中转。MV3 下 service worker 可以随时被浏览器回收不能依赖它长期保存在内存中。AI API 的调用应该尽量放在这一层因为它拥有扩展的完整权限。这三层之间的关系可以用下面这张表来对比层级主要职责是否能直接跨域请求生命周期特点适合放 AI 的哪部分popup交互界面受页面 CSP 和 CORS 限制关闭即销毁输入、展示、简单交互content script操作页面 DOM继承页面 CORS 限制跟随页面生命周期注入按钮、读取页面内容background service worker全局事件、消息中转、业务逻辑配合 host_permissions 可跨域可能随时被回收AI 接口调用、认证逻辑、数据中转为什么说 AI 调用应该放在 background关键原因是权限。content script 的 fetch 会受到当前页面 CORS 策略的约束比如你正在访问某个网站在这个页面里向 AI API 发请求浏览器会阻止跨域响应。而 background service worker 配合 manifest 里的 host_permissions可以绕过页面 CORS像服务端程序一样直接请求 AI 接口。另一个原因是密钥安全。AI 服务的 API Key 如果放在 content script 里会被任何访问该页面的脚本通过调试工具看到等于把密钥暴露给了所有用户。放在 background 里至少多了一层隔离更稳妥的方案是放到你自己的服务端代理扩展侧只保存用户会话。架构上还有一个容易忽略的点popup、content script、background 之间不能直接任意通信。content script 不能直接调 popup 的函数popup 也不能直接读 background 的局部变量。它们只能通过 chrome.runtime.sendMessage 或 chrome.runtime.connect 传递消息。这种异步消息机制决定了 AI 助手扩展需要设计清晰的消息协议。3. 第一个坏掉的东西CSP 与跨域请求先看一个最常见的报错场景开发者在 content script 里直接调用 AI API页面控制台报错CORS policy: No Access-Control-Allow-Origin。本地开发时可能还没问题因为很多 AI 服务商允许 localhost 调试但只要扩展一装到用户浏览器里在真实页面上请求就失败。这里有两个不同的限制需要分开理解。第一是网页自身的安全策略也就是 CORS。浏览器会检查目标服务的响应头里是否允许当前来源访问。content script 的请求虽然由扩展注入但浏览器仍会把它视作页面上下文的一部分因此要遵守页面的 CORS 规则。第二是扩展自身的 Content Security Policy也就是 CSP。MV3 默认不允许扩展页面加载远程脚本也不允许不安全的 eval如果 AI 服务返回的内容被当作脚本执行会直接触发 CSP 报错。解决办法是把请求迁移到 background service worker并在 manifest 中声明 host_permissions。下面是最小可用的 manifest.json 配置{ manifest_version: 3, name: AI Helper Demo, version: 1.0.0, description: A minimal browser extension with AI assistant, permissions: [storage, activeTab], host_permissions: [https://api.example.com/*], background: { service_worker: background.js }, action: { default_popup: popup.html, default_title: AI Helper }, content_scripts: [ { matches: [https://*/*], js: [content.js], run_at: document_idle } ] }配置中这行是关键host_permissions: [https://api.example.com/*]它告诉浏览器扩展有权限向这个域名发起跨域请求即使页面本身的 CORS 不允许。这样 background.js 中的 fetch 就可以直接访问 AI 服务。但这里有个实际的坑host_permissions 声明得越宽扩展在 Chrome Web Store 审核时就越容易被要求解释。如果只需要调用一个 AI 域名就不要写成all_urls。这不仅是为了过审也是为了用户隐私扩展不应该能偷偷读取用户访问的所有网站数据。还有一个 CSP 相关的坑AI 服务的某些返回内容包含 HTML 或脚本如果 popup 直接用 innerHTML 插入可能触发扩展页面的 CSP 限制也可能带来 XSS 风险。更安全的做法是用 textContent 渲染纯文本或者使用 DOMPurify 这样的库做白名单过滤。实际项目中遇到过不少“AI 回复里带一段 HTML结果 popup 白屏”的情况定位到最后都是 innerHTML 导致的。4. 第二个坏掉的东西密钥管理与认证流程如果说 CSP 解决的是“能不能请求”认证解决的是“以谁的身份请求”。很多 AI 助手产品死在了认证环节而不是模型效果上。一个很典型的例子是 JetBrains AI Assistant大量用户反馈“无法激活”“登录回调失败”。这类问题的本质是 AI 服务的认证链路远比普通登录复杂它涉及服务商身份、用户账号、设备绑定、订阅状态多个环节。当一个 AI 助手跑到浏览器扩展里时认证问题会变得更加隐蔽。原因在于扩展环境处理登录的方式和网页完全不同。普通网页登录用的是浏览器统一跳转登录完成后带着 code 回调到页面。但扩展没有传统意义上的“页面地址”如果使用 OAuth 授权码模式回调地址需要配置为扩展自己的 URL而这个地址在开发版、商店版、企业版之间可能不同。Chrome 官方提供的方案是 chrome.identity API它可以发起 OAuth2 授权流程并且通过扩展 ID 来匹配回调。这个方案能减少一部分开发量但它要求你在 AI 服务商的 OAuth 配置中把扩展的真实 ID 加入到允许的回调列表中。如果不使用第三方账号体系而是由你自己的服务器下发 API Key那么问题就变成这个 Key 存在哪里很多扩展开发者会把 Key 放在 chrome.storage.local这是一个常见的错误。chrome.storage.local 虽然不会同步到用户的其他设备但它没有加密任何能访问扩展上下文的人都可以通过扩展的 devtools 或调试接口读取。更危险的是如果 Key 出现在 content script 的消息中它就可能被页面脚本通过消息监听截获。不同存储方案的对比存储方式是否同步是否加密适合存什么风险点chrome.storage.session否否临时会话状态service worker 重启后可用但不持久chrome.storage.local否否非敏感用户数据明文存储不能放密钥chrome.storage.sync是否偏好设置有配额限制不能放敏感信息IndexedDB否否大量结构化数据明文存储访问权限受限但可被扩展调试更稳妥的方案是引入自己的服务端代理BFF 模式。扩展侧只保存用户登录后的 session tokenAI 服务的真实 API Key 只存在于服务端。每次 AI 请求由扩展发给你的代理代理再转发给模型服务商。下面是一个极简的服务端代理示意使用 Node.js 和 Express// 文件路径server/proxy.js示意代码生产环境需要加鉴权、限流、日志 const express require(express); const app express(); app.use(express.json()); app.post(/api/ai, async (req, res) { const { messages } req.body; // 真实密钥只存在于服务端环境变量 const upstream await fetch(process.env.AI_API_URL, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${process.env.AI_API_KEY}, }, body: JSON.stringify({ model: process.env.AI_MODEL, messages, }), }); res.status(upstream.status).json(await upstream.json()); }); app.listen(3000, () { console.log(AI proxy listening on 3000); });采用服务端代理之后扩展侧只需要处理两件事用户如何登录你的服务、登录令牌如何安全保存。令牌建议使用短期有效期尽量通过扩展的 storage 保存同时设置过期时间。这样即使令牌泄露也能在短时间内自动失效。这里还要提一个很容易被忽略的场景很多 AI 助手扩展为了帮用户处理验证码会读取剪贴板或者自动填入两步验证码。比如用户在登录时看到提示“enter the code from your two-factor authentication app or browser extension”如果你的扩展要辅助这类操作就会涉及到剪贴板权限、浏览器 autofill 冲突、以及隐私模式下权限受限的问题。这类功能一旦处理不好用户会觉得扩展“很打扰”甚至“有安全风险”。建议在 MVP 阶段先不做自动填入等核心 AI 对话功能稳定后再评估。5. 第三个坏掉的东西Service Worker 生命周期MV3 的 Service Worker 和 MV2 的 background page 最大的区别是它不是一直存活的。浏览器会在空闲一段时间后把它回收下次有事件触发时再重新启动。这意味着 Service Worker 里的全局变量会丢失定时器会停止正在进行的请求也可能被中断。对 AI 助手来说这个特性是灾难性的。设想一个场景用户在 popup 里发了一条长 prompt模型正在流式输出答案用户中途点了一下页面的其他区域popup 关闭了。如果这个流式请求是在 popup 中发起的它会被直接终止。即使用户没有关 popup如果浏览器判断 Service Worker 空闲时间过长也可能会回收它导致后台的流式响应中断。解决思路有两种。第一种是把请求全部转移到服务端或离屏页面让扩展运行时不做长任务。第二种是接受“会话状态会丢失”的现实在服务端保存会话快照用户重新打开 popup 时恢复上下文。如果你必须在扩展侧发起请求可以这样做// 文件路径background.js流式请求的会话恢复思路代码为示意片段 async function handleStreaming(payload, onChunk) { const controller new AbortController(); // 将当前请求状态写入 session storageService Worker 被唤醒后可读取 await chrome.storage.session.set({ activeAbortController: controller }); const response await fetch(API_URL, { method: POST, headers: { Content-Type: application/json, }, body: JSON.stringify({ model: your-model, messages: payload.messages, stream: true, }), signal: controller.signal, }); const reader response.body.getReader(); const decoder new TextDecoder(); let content ; while (true) { const { done, value } await reader.read(); if (done) break; content decoder.decode(value, { stream: true }); // 把当前已生成的内容同步到 storage避免进程被杀后丢失 await chrome.storage.session.set({ lastChunk: content }); onChunk?.(content); } await chrome.storage.session.remove([activeAbortController]); return content; }这段代码的思路是把流式进度同步到 chrome.storage.session一旦 Service Worker 被回收重启新的请求可以在初始化时读取 session 存储把已经生成的内容恢复给用户。chrome.storage.session 的写入是异步的频繁写入有性能开销所以更合理的做法是每积累一段内容再写入而不是每个 chunk 都写。另一个稳妥的做法是使用 chrome.offscreen API 创建离屏文档把长时间运行的流式任务移交给离屏文档处理。离屏文档是一个真正的 HTML 页面生命周期比 Service Worker 稳定得多适合跑读取流、处理音频、操作 DOM 这类任务。但它需要单独的权限声明并且要控制只能在一个页面中使用。给新手的建议MVP 阶段不要强行做流式输出。先用非流式接口一次性返回完整回答等用户量上来后再处理流式和断点恢复。这样能避开 Service Worker 生命周期这一整类问题。6. 第四个坏掉的东西内容脚本与页面冲突AI 助手扩展通常需要在用户浏览的网页上注入一个悬浮按钮或对话面板。这一步看起来简单实际运行时会遇到至少三类问题。第一类是隔离世界带来的 DOM 限制。content script 虽然能操作 DOM但它运行在 isolated world 中页面主体脚本无法直接访问 content script 创建的变量和函数。反过来说content script 也访问不到页面脚本里的全局变量。如果 AI 助手需要读取页面现有的 React 或 Vue 组件状态通过 DOM 操作是不够的只能通过 DOM 属性或自定义事件间接获取。第二类是页面框架造成的样式冲突。很多网站用了全局 CSS 重置、全局 z-index 设置、Shadow DOM 隔离直接往 body 里塞按钮可能被页面的遮罩层挡住或者被页面的* { all: initial }重置样式。比较稳妥的做法是给注入元素挂载独立的 Shadow DOM这样样式不会被页面覆盖但也会让开发变得复杂。第三类是 SPA 路由变化导致元素丢失。在单页应用里点击链接不会刷新页面但 React Router 或 Vue Router 会切换视图导致之前注入的按钮被卸载。如果只在页面加载时注入一次用户切换路由后 AI 助手入口就消失了。下面这段 content script 给出一个基础的解决方案// 文件路径content.js function injectButton() { if (document.getElementById(ai-helper-entry)) return; const button document.createElement(button); button.id ai-helper-entry; button.textContent AI; button.style.cssText position: fixed; right: 20px; bottom: 20px; z-index: 999999; width: 48px; height: 48px; border-radius: 50%; border: none; background: #1a73e8; color: #fff; font-size: 18px; cursor: pointer; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); ; button.addEventListener(click, () { // 使用 postMessage 与页面脚本通信注意校验消息来源 window.postMessage( { source: ai-helper-ext, type: OPEN_PANEL }, * ); }); document.body.appendChild(button); } // 监听 SPA 页面路由变化重新注入按钮 let lastUrl location.href; new MutationObserver(() { const url location.href; if (url ! lastUrl) { lastUrl url; // 给页面渲染留一点时间再注入按钮 setTimeout(injectButton, 300); } }).observe(document, { subtree: true, childList: true }); injectButton();这段代码有几个可以继续优化的地方。MutationObserver 监听整个 document 的子树变化在大型页面上可能产生性能问题。更精细的做法是在路由变化后用 IntersectionObserver 或直接轮询目标容器是否存在。按钮的点击事件也可以改成通过 chrome.runtime.sendMessage 发给 background由 background 打开面板或调用 AI 接口这样就不用依赖 window.postMessage 与页面通信降低消息伪造的风险。还有一个经常被忽视的安全点如果 content script 监听来自页面的消息必须严格校验 message.source 和 message.data 的结构避免页面恶意脚本冒充你的扩展触发不安全的 AI 调用或系统操作。7. 完整示例一个最小可用的 AI 助手扩展前面几章讲完了各种“会坏掉”的环节这一章用一个最小示例把它们串起来。这个示例包含完整的 manifest、background、popup 和 content script可以跑通“用户点击悬浮按钮 - 打开 popup - 输入问题 - 扩展请求 AI API - 返回结果”这条主链路。先看文件结构ai-helper-demo/ ├── manifest.json ├── background.js ├── content.js ├── popup.html ├── popup.js └── icons/manifest.json 已经在前文出现过这里再看一次完整版本{ manifest_version: 3, name: AI Helper Demo, version: 1.0.0, description: A minimal browser extension with AI assistant, permissions: [storage, activeTab], host_permissions: [https://api.example.com/*], background: { service_worker: background.js }, action: { default_popup: popup.html, default_title: AI Helper }, content_scripts: [ { matches: [https://*/*], js: [content.js], run_at: document_idle } ] }background.js 负责接收 popup 的消息并调用 AI API// 文件路径background.js chrome.runtime.onMessage.addListener((message, sender, sendResponse) { if (message.type ASK_AI) { handleAskAI(message.payload) .then((result) sendResponse({ ok: true, data: result })) .catch((error) sendResponse({ ok: false, error: error.message })); return true; } }); async function handleAskAI(payload) { const API_URL https://api.example.com/v1/chat/completions; const response await fetch(API_URL, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${payload.apiKey}, }, body: JSON.stringify({ model: your-model, messages: payload.messages, stream: false, }), }); if (!response.ok) { throw new Error(API request failed: ${response.status}); } return response.json(); }这里的 payload.apiKey 只能用于本地联调真实项目要把这一层换成服务端代理扩展侧只发 session token。popup.html 提供输入和结果展示!-- 文件路径popup.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 / titleAI Helper/title style body { width: 320px; padding: 12px; font-family: system-ui, sans-serif; } textarea { width: 100%; min-height: 80px; margin-bottom: 8px; box-sizing: border-box; } button { width: 100%; padding: 8px; cursor: pointer; } #result { margin-top: 8px; white-space: pre-wrap; font-size: 14px; line-height: 1.6; } /style /head body h3AI Helper/h3 textarea idinput placeholder输入你的问题/textarea button idsend发送/button div idresult/div script srcpopup.js/script /body /htmlpopup.js 把用户输入转成消息发送给 background// 文件路径popup.js const input document.getElementById(input); const sendBtn document.getElementById(send); const result document.getElementById(result); sendBtn.addEventListener(click, async () { const text input.value.trim(); if (!text) return; result.textContent 请求中...; try { const response await chrome.runtime.sendMessage({ type: ASK_AI, payload: { messages: [{ role: user, content: text }], apiKey: your-api-key, }, }); if (response response.ok) { const content response.data.choices[0].message.content; result.textContent content; } else { result.textContent 错误 (response ? response.error : 无响应); } } catch (error) { result.textContent 请求失败 error.message; } });content.js 负责注入悬浮按钮这部分在前文已经给出这里不再重复。把四个文件放到同一个目录然后在 Chrome 的扩展管理页面打开“开发者模式”选择“加载已解压的扩展程序”选中 ai-helper-demo 目录就能开始测试。验证成功的标准是访问任意一个 HTTPS 页面页面右下角出现 AI 圆形按钮点击工具栏图标打开 popup输入问题后能收到模型返回的内容关闭 popup 再打开扩展不会崩溃storage 中的数据仍然可读。8. 常见问题与排查思路发布后收到的用户反馈大多数可以归纳为下面几类。排查的时候不要先怀疑模型要从扩展运行时的边界出发按顺序检查。问题现象可能原因排查方式解决方案请求被 CORS 拦截content script 中直接调用 AI API打开浏览器控制台看具体报错把请求迁移到 background并配置 host_permissions页面右下角按钮不显示SPA 路由切换导致 DOM 被卸载检查 MutationObserver 是否监听 route 变化监听 location.href 变化后重新注入AI 回复到一半中断Service Worker 被回收或 popup 关闭查看 service worker 日志和网络面板使用离屏文档或改为非流式请求用户登录后很快失效token 存储方式不安全或过期时间过短检查 token 存储位置和过期策略使用短期 token配合刷新逻辑扩展安装后无法调用 AIhost_permissions 未覆盖目标域名检查 manifest 中权限声明精确声明所需域名重新加载扩展popup 白屏AI 返回内容包含 HTML被 CSP 拦截查看 popup 控制台 CSP 报错用 textContent 渲染不使用 innerHTML按钮被页面元素遮挡页面 z-index 高于注入按钮检查页面覆盖层提高注入元素的 z-index或使用 Shadow DOM消息发送后无响应background 没有返回值检查 onMessage 监听是否返回 true异步响应时必须 return true 保持通道排查的时候还有一个通用技巧在扩展的 Service Worker 里加 console.log然后打开 chrome://extensions找到你的扩展点击“Service Worker”链接打开调试面板。所有 background 中的日志和网络请求都能在这里看到这比在 popup 里调试更容易定位问题。如果用户报告的问题在本地无法复现可以让他们提供扩展版本号、浏览器版本、操作步骤和错误截图。很多扩展问题都带有特定环境特征比如企业网络、隐私模式、旧版浏览器缺少这些信息会很难定位。9. 最佳实践与发布检查清单结合前面的踩坑复盘这里整理一份发布前建议逐项确认的检查清单。权限最小化是第一原则。manifest 中的 permissions 和 host_permissions只申请当前功能确实需要的权限。每多一个权限用户安装时的风险提示就更重一层审核时被质问的概率也更高。比如只需要向 api.example.com 发请求就不要写all_urls只需要保存用户偏好就不要申请 history 或 tabs 权限。密钥和 token 管理要遵循“扩展侧不存长期密钥”的原则。API Key 必须放在服务端代理扩展侧只保存短期 session token并且要设置过期时间。如果 token 泄露要支持一键吊销。chrome.storage.local 里的数据虽然不是加密的但可以通过限制访问入口来降低暴露面。AI 调用要做超时、重试和错误分级。扩展环境比服务端更脆弱网络切换、Service Worker 回收、用户关闭 popup都会中断请求。建议设置合理的超时时间对 5xx 错误做有限次数的重试对 4xx 错误直接提示用户检查权限或登录状态不要盲目重试。日志和错误上报要在发布前就接入。浏览器扩展不像服务端有统一日志系统用户侧的错误信息很难收集。比较常见的做法是在 background 中捕获异常把错误信息、扩展版本、浏览器版本、事件类型组装成一条日志发送到自己的日志服务。注意不要在日志中包含用户对话内容和 token。版本兼容和灰度策略也要提前规划。Chrome 和 Edge 的 Web Store 都支持分阶段发布但扩展的更新是自动的用户无法感知到新旧版本差异。如果新版本引入了破坏性变更建议先在小比例用户中灰度观察错误率后再全量发布。同时要维护一个最小浏览器版本策略避免使用新 API 导致老用户白屏。还要准备好隐私政策。AI 助手会收集用户输入的自然语言内容这些数据可能涉及敏感信息。发布到应用商店时需要明确声明数据用途、是否上传、是否用于模型训练、如何删除。隐私政策缺失是 AI 类扩展被下架的常见原因。最后是团队协作层面的建议。扩展 AI 助手的代码通常横跨前端、服务端、AI 三块建议把 manifest 权限变更、消息协议变更和 API 接口变更纳入 code review 重点避免开发环境能跑、生产环境不可用的情况。10. 总结与后续学习方向这篇文章围绕“浏览器扩展 AI 助手”这个组合拆开了发布过程中最容易出问题的四个环节跨域与 CSP 限制、密钥与认证、Service Worker 生命周期、content script 与页面冲突。它们每一个都不复杂但叠加在一起足以让一个本地正常的扩展在发布后崩溃。如果你接下来要做一个真正的 AI 助手扩展我的建议是先跑通最小示例再做三件事把 AI API 调用从 popup 和 content script 迁到 background把密钥从扩展侧移到服务端代理把流式输出从第一版砍掉用非流式接口验证核心价值。这三步做完扩展的稳定性会有明显提升。后续值得继续深入的方向是chrome.identity 的 OAuth 流程、chrome.offscreen 处理长时间任务、Shadow DOM 隔离注入样式、以及如何用 streaming 加服务端快照实现流畅且可恢复的对话体验。这些内容可以作为扩展 AI 助手的第二篇和第三篇来学。希望这份复盘能帮你避掉大部分坑把精力放到真正值得投入的 AI 产品体验上。
返回列表