【免费下载链接】freehire
freehire — the open-source search engine for job seekers
本文围绕 freehire 仓库中的 Firefox 发布流程展开:Firefox 构建与 Chrome 构建同源,但上架 addons.mozilla.org(AMO)有独立的一次性人工步骤、源代码包要求、固定的 gecko id 以及基于 JWT 的自动签名链路。读完后你将掌握如何手工完成首次上架、如何准备 reviewer 可复现的 sources ZIP,以及如何用两个仓库密钥让后续每个版本自动提交审核。
配套阅读:Chrome 上架清单 提供完整文案与权限说明;扩展开发指南 说明构建命令与 Firefox 差异。
Firefox 与 Chrome 构建的关系
extension/docs/firefox-addons.md开宗明义:Firefox 构建与 Chrome 构建来自同一份源码,listing 文案、隐私文本和权限申请理由与 Chrome 版本相同,只是减去sidePanel和debugger——Firefox 的 manifest 根本不申请这两个权限。
这一点在 extension/wxt.config.ts 中是可以直接印证的事实:
permissions: [ 'storage', 'tabs', 'scripting', 'activeTab', 'identity', 'downloads', ...(browser === 'firefox' ? [] : ['sidePanel', 'debugger']), ], host_permissions: ['<all_urls>'],也就是说,Firefox 侧保留storage、tabs、scripting、activeTab、identity、downloads与<all_urls>,而sidePanel、debugger仅在非 Firefox 分支注入。二者各自对应一个产品差异:
- 没有
sidePanel,改用侧边栏。WXT 会把同一个sidepanel入口在 Firefox 下产出为sidebar_action。由于 Firefox 的 sidebar 没有“点击图标自动打开”的设置,extension/entrypoints/background.ts 在点击事件里显式调用sidebarAction.toggle(),这也是 Firefox 要求用户手势才能打开侧栏的原因。 - 没有
debugger,因此没有“附加定制简历”。chrome.debugger在 Firefox 不存在,DOM.setFileInputFiles那条把 PDF 塞进input[type=file]的路径无法实现,面板依据import.meta.env.FIREFOX隐藏该按钮。
其余差异集中在 manifest 的 Firefox 专属字段,后文“gecko id 与登录重定向”一节会详细展开。
第一次上架:必须在浏览器里手工完成
AMO 的发布管道只能给已存在的 add-on 追加版本,无法创建第一个 listing。因此首次上架是一次性人工操作:
拿到两个文件。从
buildworkflow 下载extensionartifact(或最新的extension-v*GitHub Release),也可以本地执行:cd extension && npm run zip:firefox产物为:
freehire-extension-<version>-firefox.zip— 扩展本体;freehire-extension-<version>-sources.zip— 源码包,供 AMO 审核使用。
进入 Developer Hub(addons.mozilla.org/developers)→Submit a New Add-on→On this site(listed,即公开上架)→ 上传
-firefox.zip。回答“代码是否压缩/打包”时选 yes,并上传
-sources.zip。给 reviewer 的构建说明原文为:Node 24, pnpm and npm. From the archive root:
cd design-system && pnpm install --frozen-lockfile && cd ../extension && npm ci && npm run build:firefox. The output isextension/.output/firefox-mv2.用 Chrome 上架文档 里的文案填写 listing 并提交,AMO 为人工审核。
命令与产物在仓库中的对应关系
npm run zip:firefox并非 WXT 的默认行为,它在 extension/package.json 中被定义为:
"build:firefox": "wxt build -b firefox", "zip:firefox": "WXT_RELEASE=1 wxt zip -b firefox",WXT_RELEASE=1是关键开关。它让 extension/wxt.config.ts 跳过key字段——发布产物由商店分配 id,硬编码的key会与之冲突;同时它启用了仅商店产物使用的icon_variants(Chrome 侧),Firefox 分支则因browser === 'firefox'而不注入key,因为 Firefox 忽略key,改用gecko.id固定身份。
sources ZIP 里到底放了什么
AMO 会以源码包为准复核打包产物,因此 sources ZIP 的构成是硬性要求,配置在 extension/wxt.config.ts:
zip: { sourcesRoot: '..', includeSources: [ 'extension/**', 'extension/.env.production', 'design-system/**', ], },三个细节值得注意:
sourcesRoot: '..'把压缩包根设为仓库根,因为扩展通过file:../design-system依赖设计系统,源码包必须同时包含design-system/;extension/.env.production被显式列入:隐藏文件默认会被跳过,而缺少它,reviewer 构建出来的扩展会指向 localhost 而不是生产环境;- 审核方构建指令里的
npm ci会触发postinstall: wxt prepare,生成.wxt/目录,这正是cd extension && npm ci之后能直接npm run build:firefox的原因。
构建前置条件:design-system 必须先装依赖
仓库在多处强调同一个坑:file:/link:链接的包不会被消费方的安装过程带上自己的依赖。因此 reviewer 的指令第一步是cd design-system && pnpm install --frozen-lockfile。这一点在 .github/workflows/extension.yml 中有独立步骤与注释,CI 也因此单独安装 design-system 依赖,否则构建会以MODULE_NOT_FOUND失败。
本地验证 Firefox 产物
首次上架前建议在本地确认侧栏行为。按 extension/AGENTS.md 的说明:执行npm run dev:firefox或npm run build:firefox(输出.output/firefox-mv2),然后打开about:debugging→ This Firefox →Load Temporary Add-on→ 选择该构建的manifest.json。sidepanel入口在 Firefox 下变成sidebar_action,点击工具栏图标即切换显示。
gecko id 与登录重定向:为什么它必须永远不变
extension/docs/firefox-addons.md特别指出:add-on id 在wxt.config.ts中固定为extension@freehire.me,所以上架版登录“已经能正常工作”——它的 redirect hash 已在 hire 的允许列表里。
这句话的机制在源码中非常具体。登录流程见 extension/lib/auth.ts:
const redirectUri = browser.identity.getRedirectURL(); const state = crypto.randomUUID(); const authUrl = `${HIRE_ORIGIN}/api/v1/auth/extension/connect` + `?redirect_uri=${encodeURIComponent(redirectUri)}&state=${encodeURIComponent(state)}`; const responseUrl = await browser.identity.launchWebAuthFlow({ url: authUrl, interactive: true });Firefox 的identity.getRedirectURL()形如https://<sha1(gecko id)>.extensions.allizom.org/,所以 gecko id 决定了这个 hash。对extension@freehire.me,hash 是13f2644fb08a641dc52d5b7c11faf2fb9775ee26——这个值同时出现在 extension/AGENTS.md 与后端测试 internal/api/handler/extension_connect_test.go 中,并被当作“已允许的 Firefox hash”断言通过。
服务端校验逻辑位于 internal/api/handler/extension_connect.go,要点如下:
| 校验项 | 规则 |
|---|---|
| scheme | 必须是https,否则报redirect_uri must be https |
| port | 不允许携带端口 |
| host 后缀 | 只接受.chromiumapp.org或.extensions.allizom.org |
| 标签结构 | 后缀前必须是单个label(不能有点号,防止a.<id>.extensions.allizom.org之类绕过) |
| 允许列表 | 该 label 必须出现在EXTENSION_REDIRECT_ALLOWLIST中 |
| 空列表 | 直接禁用整个扩展登录流程(extension connect is disabled) |
EXTENSION_REDIRECT_ALLOWLIST由 internal/platform/config/config.go 以逗号分隔解析。因此 Firefox 上架的运维动作是明确的:把13f2644fb08a641dc52d5b7c11faf2fb9775ee26加进该环境变量,与 Chrome 的 id 并列。gecko id 一旦改动,hash 随之变化,登录会立刻失败——这与 Chrome 侧“key必须固定”是同一个约束。
顺带一提,minted token 通过 URLfragment回传(#token=…&state=…),而不是 query,避免被日志或Referer带走;扩展侧parseAuthRedirect会校验state后才落盘到storage.local。
后续每个版本:用两个仓库密钥实现自动发布
首次上架完成后,自动发布只需两件事:
- 在 AMO 的 Developer Hub →Manage API Keys生成凭据;
- 把它们加为仓库密钥:
AMO_JWT_ISSUER与AMO_JWT_SECRET。
从此,任何一次推送到main且提升了extension/package.json的version的提交,都会发布一个 GitHub Release,并把同一份构建提交到 addons.mozilla.org(见 .github/workflows/extension.yml)。如果没有配置这两个密钥,该步骤会被跳过。
CI 的完整链路与门控条件
workflow 的触发路径为extension/**、design-system/**与 workflow 文件本身,build作业按顺序执行:安装 design-system 依赖 →npm ci→ lint → 单测 → 类型检查 →npm run zip→npm run zip:firefox→ 上传extension/.output/*.zip(因.output是隐藏目录,需要include-hidden-files: true)。
发布相关步骤有三重门控:
- name: Publish to addons.mozilla.org if: github.event_name == 'push' && steps.gh_release.outputs.created == 'true' && env.AMO_JWT_ISSUER != ''逐条解释:
github.event_name == 'push':PR 构建只产出 artifact,不发布;steps.gh_release.outputs.created == 'true':只有GitHub Release 是新建的才继续。Release 步骤本身是幂等的——若extension-v<version>已存在则输出created=false并跳过,因此“同一版本重复上传”不会被触发(AMO 也会拒绝重复版本号);env.AMO_JWT_ISSUER != '':密钥缺失时这一步为 no-op。
真正调用签名工具的命令如下:
sources_path=$(find .output -maxdepth 1 -name '*-sources.zip') npx --yes web-ext@8 sign \ --source-dir .output/firefox-mv2 \ --channel listed \ --upload-source-code "$sources_path" \ --approval-timeout 0 \ --api-key "$AMO_JWT_ISSUER" \ --api-secret "$AMO_JWT_SECRET"几个参数值得记住:
--channel listed表示提交到公开商店,而不是 self-hosted 的未签名版本;--upload-source-code把 sources ZIP 一并交给审核方,对应首次上架时手工上传的那份包;--approval-timeout 0在上传被接受后立即返回,不等待人工审核。审核需要数天,CI 作业不应阻塞——workflow 注释明确写了这一点。
版本号的唯一来源
版本号来自extension/package.json的version字段(当前为0.1.10),workflow 用node -p "require('./package.json').version"读取并拼成extension-v<version>标签。这意味着:想让 AMO 收到新版本,必须改package.json的version;改代码但不改版本,Release 与 AMO 提交都会因“已存在”而跳过。
上架文案与权限说明的复用边界
extension/docs/firefox-addons.md要求直接复用 Chrome 上架文档 的内容,但要理解“相同”与“不同”的边界:
- 相同的部分:名称 freehire、≤132 字符的简短描述、Single purpose(“Assist the user with job applications on the page they are currently viewing.”)、详细描述、隐私实践(收集 PII、认证信息、网站内容、匿名使用行为;不收集健康、金融、位置、私人通信、浏览历史、键盘记录),以及商店要求的认证勾选项。
- 不同的部分:权限理由表要删掉
sidePanel与debugger两行,因为 Firefox manifest 不申请它们;其余storage、tabs、scripting、activeTab、identity、host_permissions: <all_urls>的理由逐字可用。
Firefox 侧还有一项 Chrome 没有的声明:data_collection_permissions。它写在 extension/wxt.config.ts 的browser_specific_settings.gecko下:
browser_specific_settings: { gecko: { id: GECKO_ID, strict_min_version: '140.0', data_collection_permissions: { required: ['authenticationInfo', 'websiteContent'], }, }, },注释说明140是第一个读取data_collection_permissions的版本,而 AMO 对每个新 listing 都要求该声明。声明的两项正好对应扩展的真实数据面:authenticationInfo(登录后保存在storage.local的会话 JWT)与websiteContent(用户提问时读取的页面内容、Autofill 读取的表单字段)。这也解释了为什么 Chrome 文档里“收集四类数据”的措辞在 Firefox 侧需要落到这两项必填声明上。
上架前后自检清单
| 阶段 | 检查项 | 依据 |
|---|---|---|
| 首次上架前 | extension/package.json的version未被商店占用 | AMO 拒绝重复版本 |
| 首次上架前 | -sources.zip同时包含extension/**、extension/.env.production、design-system/** | extension/wxt.config.ts |
| 首次上架前 | 本地about:debugging加载.output/firefox-mv2,确认侧栏与登录可用 | extension/AGENTS.md |
| 首次上架时 | gecko id 为extension@freehire.me,未改动 | extension/wxt.config.ts |
| 首次上架后 | 服务端EXTENSION_REDIRECT_ALLOWLIST含13f2644fb08a641dc52d5b7c11faf2fb9775ee26 | internal/identity/auth/AGENTS.md |
| 自动化前 | 仓库已配置AMO_JWT_ISSUER、AMO_JWT_SECRET | .github/workflows/extension.yml |
| 每次发版 | version已提升,Release 标签为extension-v<version> | workflow 版本读取步骤 |
| 发版失败重试 | 先删除已存在的 Release 与标签,再重跑 | Release 步骤的幂等判断 |
最后一点值得强调:Firefox 的自动发布是“只追加版本”,不会帮你在 AMO 创建 listing。首次那一步永远留在浏览器里,自动化只是把它之后的手工上传动作去掉了,审核等待并不会消失——同样的逻辑在 Chrome 侧也成立,那里<all_urls>注定触发人工深度审核。理解这条边界,才能正确规划 freehire 扩展在两条商店渠道上的发版节奏。
【免费下载链接】freehire
freehire — the open-source search engine for job seekers
相关推荐
freehire 浏览器扩展上架 Chrome Web Store 实战:清单对齐、权限论证与自动化发布
freehire 浏览器扩展上架 Chrome Web Store 实战:清单对齐、权限论证与自动化发布 本文以 freehire 仓库中的扩展开源实现为依据,
AnLinux-App技术原理揭秘:Termux与PRoot如何实现免root运行Linux?
AnLinux App技术原理揭秘:Termux与PRoot如何实现免root运行Linux? AnLinux App是一款革命性的开源工具,它通过巧妙结合Te
工具调用FastAPI性能监控终极指南:New Relic与Datadog实战配置
FastAPI性能监控终极指南:New Relic与Datadog实战配置 在现代Web开发中,性能监控是确保应用稳定性和用户体验的关键环节。 FastAPI
后端Web框架API设计异步编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考