
Company News
Free Business Plan Upgrades for Open Source Maintainers
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.
@giime/crx
Advanced tools
面向基于 giime 的 Chrome 扩展(MV3)插件:axios 与登录协调器。popup / options / contentScript / background 共用同一套代码;contentScript 经 background 自动桥接。
Chrome 扩展(MV3)专用的 axios + 登录协调封装。让 popup / options / contentScript / background 同一份业务代码调 API,自动桥接 contentScript 经 background 发请求;统一处理 401 / 登录失效 / 自动重发;登录窗口去重 + 冷却期保护。
跟 giime 主库的 createAxios 是平级封装——giime 那套面向 web 项目(document.cookie、iframe LoginDialog),这套面向 CRX(chrome.cookies、chrome.windows.create)。
pnpm add @giime/crx
依赖 vue@^3 / axios@^1 / element-plus / giime(都是 peerDependencies)。
.env# 必须:业务方在自己的 .env 配
VITE_CRX_PLUGIN_ID=your-plugin-id
# 可选:90% 情况下不需要配;不配时自动 fallback 到 Vite 自带 import.meta.env.MODE。
# 只有跑非默认 mode(比如 `vite --mode release` / `--mode test`)才需要显式配
# VITE_GIIME_MODE=develop
四套 mode 对应的 host(包内硬编码,跟着包走,业务不用关心):
| mode | host |
|---|---|
| production | api.giikin.com |
| release | api-pre.giikin.com |
| test | api-test.giikin.cn |
| develop | api-dev.giikin.cn |
src/env.d.ts/// <reference types="@giime/crx/client" />
让 import.meta.env.VITE_CRX_PLUGIN_ID 有类型提示。
// vite.config.ts
import { defineConfig } from 'vite';
import { crxAxiosIdPlugin } from '@giime/crx/vite-plugin';
export default defineConfig({
plugins: [crxAxiosIdPlugin()],
});
插件做的事:编译期给每个 createCrxAxios({...}) 调用注入 __id__: "<源文件相对路径>",让 contentScript 桥接到 background 时能找到对应的 axios 实例。
文件名必须是 service.ts / serviceNext.ts / request.ts 之一(background 通过 glob 加载,文件名定死才能匹配)。
// src/api/basic/request.ts
import { createCrxAxios, currentCrxEnv } from '@giime/crx';
const { service } = createCrxAxios({
// baseURL 推荐用 currentCrxEnv.apiHost 派生,自动跟随 mode 切换
baseURL: `https://${currentCrxEnv.apiHost}/guard`,
successCode: 200,
});
export default service;
service// src/api/basic/controller/postBasicV1OrgList.ts
import request from '@/api/basic/request';
export const postBasicV1OrgList = (config?: AxiosRequestConfig) =>
request.post(`/basic/v1/org/list`, config);
// src/background/main.ts
import { registerAxiosBridge, registerLoginCoordinator } from '@giime/crx';
// 让所有 service 文件在 background bundle 里被加载,自动注册到桥接 registry
import.meta.glob('@/api/**/{service,serviceNext,request}.ts', { eager: true });
registerAxiosBridge(); // background 收 contentScript 桥接消息
registerLoginCoordinator(); // background 协调登录窗 / cookie 监听 / 广播
// src/contentScripts/index.ts
import { createRouter, createWebHashHistory } from 'vue-router';
import { configureCrx } from '@giime/crx';
import Giime from 'giime';
// 创建 shadow DOM root
const root = document.createElement('div');
const shadowDOM = container.attachShadow({ mode: 'open' });
shadowDOM.appendChild(root);
// 必传:让 LoginPrompt / 错误 toast 挂到 shadow root,避免被宿主页面样式干扰
configureCrx({ contentScriptRoot: root });
const app = createApp(App);
const router = createRouter({ history: createWebHashHistory(), routes: [] });
// router 既要传给 Giime(让 giime 内部 useGlobalConfig 拿到),
// 也要 app.use(router) 给 vue-router 自己——否则 GmButton 等组件
// 会报 injection "Symbol(route location)" not found。
//
// disabledVersionAttr: 关掉 giime 写 document.body[data-giime-version]——
// content script 里这个 body 是宿主页的,会跟宿主页自己的 giime 版本号来回打架。
app.use(Giime, {
env: import.meta.env,
router,
disabledLoginDialog: true,
disabledVersionAttr: true,
});
app.use(router);
app.mount(root);
业务直接 import { postBasicV1OrgList } from '@/api/basic/controller' 调即可。
挂 Vue 应用时遵循跟 contentScript 同样的规则:app.use(Giime, { router }) + app.use(router) 必须同时调,缺一个 <GmButton> / 任何用 vue-router 注入的 gm 组件就会运行时报错。
| 入口 | 行为 |
|---|---|
| popup | 跳过 LoginPrompt 直接让 background 开登录标签页(popup 视口太窄 + 失焦即关闭,dialog 体验差) |
| options | LoginPrompt 弹窗 → 用户点立即登录 → 开登录标签页 → 等 cookie 变化广播 → 重发原请求 |
| contentScript | 同 options |
| background 自己调 | 直接 reject(无 UI) |
cookie 检测到 token 写入 → background 广播 __crxLoginCompleted__ + chrome.windows.remove(loginWindowId)。loginWindowId 用 chrome.storage.session 持久化跨 SW 重启。
dk-plugin-id 请求头所有 axios 请求自动带 dk-plugin-id: ${VITE_CRX_PLUGIN_ID},让后端识别请求来源。
append-toElementPlus 的 ElDialog / ElOverlay / ElMessage 默认 <teleport to="body">——这里的 body 是宿主页的 document.body,shadow DOM 隔离不住 teleport,DOM 会跨出 shadow root 渲染到宿主页里,被宿主页样式污染、定位错乱。
修法:每个 dialog 显式传 append-to,指向 shadow root 内的容器元素(也就是你 configureCrx({ contentScriptRoot }) 传进来的那个):
<template>
<gm-dialog v-model="visible" :append-to="appendTo">...</gm-dialog>
</template>
<script setup lang="ts">
import { getCrxConfig } from '@giime/crx';
const appendTo = getCrxConfig().contentScriptRoot as HTMLElement;
</script>
外层 dialog 加上 append-to 后,里面套的 GmTableCtx 自带 search dialog 等子级 portal 会继承同一个 teleport context,跟着一起进 shadow root,不用每个都加。
append-to-body是 ElDialog 的旧 prop(已废弃且默认 true),效果跟 teleport 到宿主 body 一样,shadow DOM 场景别用。
signal / cancelToken / adapter / transformRequest / transformResponse / paramsSerializer / validateStatus / onUploadProgress / onDownloadProgress / httpAgent / httpsAgent + 任何函数字段,在 chrome.runtime.sendMessage 前会被剥掉——它们要么过不了 structured clone,要么跨进程没意义(如 AbortSignal)。详见 DESIGN.md 三。
chrome://extensions 错误面板桥接层 / 真实 axios 拦截器抛 401 时打 __crxHandled__ 标记,包内 unhandledrejection 全局过滤器 preventDefault() 掉。非 401 异常(5xx / 网络错 / 业务错)仍正常报,真 bug 不被埋。详见 DESIGN.md 七。
// 工厂
createCrxAxios(config): { service: AxiosInstance, __id__: string }
// 注册(background entry 用)
registerAxiosBridge(): void
registerLoginCoordinator(): void
// 配置
configureCrx(cfg: Partial<CrxConfig>): void
getCrxConfig(): Readonly<CrxConfig>
// 环境
currentCrxEnv: { apiHost: string, cookieDomain: string }
crxPluginId: string
isBackground() / isContentScript() / isExtensionPage() / isPopup(): boolean
// 登录
crxLoginUrl: string
isOnLoginPage(): boolean
showLoginPrompt(opts?): Promise<boolean> // imperative 弹窗,业务一般用不上
详细架构、__id__ 注入原理、跨 entry 桥接协议、为什么不用 iframe 等讨论见同目录的 DESIGN.md。
FAQs
面向基于 giime 的 Chrome 扩展(MV3)插件:axios 与登录协调器。popup / options / contentScript / background 共用同一套代码;contentScript 经 background 自动桥接。
We found that @giime/crx demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.

Security News
During a UK cyber test, a Mythos 5 agent used sockpuppets, social engineering, and prompt injection to try to get a maintainer to merge malware.