基于 Reclaim 验证流程的轻量 SDK,用于在**你的网站**或**自有扩展界面(popup/panel)**中发起证明验证。 负责 content ↔ background 通信、打开 Provider 页签、通过 **offscreen** 文档(WebAssembly)生成证明并派发完成事件。
npm install trex-proxy-browser-extension-sdk基于 Reclaim 验证流程的轻量 SDK,用于在你的网站或自有扩展界面(popup/panel)中发起证明验证。
负责 content ↔ background 通信、打开 Provider 页签、通过 offscreen 文档(WebAssembly)生成证明并派发完成事件。
> 支持 Chrome Manifest V3(含 Vite/CRA 构建)。
> 步骤与 Reclaim Browser Extension SDK 保持一致,包名为 trex-proxy-browser-extension-sdk。
---
1. 安装
2. Manifest (MV3) 配置
3. Setup:初始化 Background 与 Content Script
4. Usage:在扩展内使用(popup/panel)
5. 从网页发起(Web Integration)
6. 服务端生成请求配置(推荐生产环境)
7. Vite/CRX 说明
8. 故障排查
9. 类型
---
- 项目:浏览器扩展或带 package.json 的 Web 项目
- 包管理器:npm 6.x+ 或 yarn 1.22.x+
- Node.js:14.x 或更高
``bash`
cd your-project-directory
npm install trex-proxy-browser-extension-sdk
在 package.json 中添加资源部署脚本:
`json`
{
"scripts": {
"trex-extension-setup": "node node_modules/trex-proxy-browser-extension-sdk/build/scripts/install-assets.js --public-dir=public"
}
}
执行部署脚本:
`bash`
npm run trex-extension-setup
脚本会将预构建的 SDK 资源拷贝到项目的 public 目录:
``
public/
└── trex-browser-extension-sdk/
├── content/
│ ├── content.bundle.js
│ └── components/
│ ├── reclaim-provider-verification-popup.css
│ └── reclaim-provider-verification-popup.html
├── offscreen/
│ ├── offscreen.html
│ └── offscreen.bundle.js
├── interceptor/
│ ├── network-interceptor.bundle.js
│ └── injection-scripts.bundle.js
└── ReclaimExtensionSDK.bundle.js
验证安装:
`bash`
ls -la public/trex-browser-extension-sdk/
| 资源 | 说明 |
| ----------------------------------------------------------------------------- | ----------------------------------------------- |
| Content Script (content.bundle.js) | 注入到需要验证的页面,桥接网页与扩展 background |offscreen.html
| Offscreen 文档 (, offscreen.bundle.js) | Manifest V3 下运行 WebAssembly 生成加密证明 |network-interceptor.bundle.js
| 网络拦截 (, injection-scripts.bundle.js) | 在 Provider 验证时捕获数据与请求 |reclaim-provider-verification-popup.*
| UI 组件 () | 验证流程弹窗,可通过 CSS 定制 |
注意: 不要在代码中 import 上述资源,仅通过 manifest 配置引用(见下一节)。
在使用 SDK 前,需在 Reclaim 开发者后台获取 APP_ID 和 APP_SECRET。
---
在 manifest.json 中配置(Manifest V3):
`json`
{
"manifest_version": 3,
"name": "Your Extension Name",
"version": "1.0.0",
"description": "Your extension description",
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; worker-src 'self';"
},
"permissions": ["offscreen", "cookies", "scripting"],
"host_permissions": ["
"background": {
"service_worker": "background.js"
},
"web_accessible_resources": [
{
"resources": [
"trex-browser-extension-sdk/offscreen/offscreen.html",
"trex-browser-extension-sdk/offscreen/offscreen.bundle.js",
"trex-browser-extension-sdk/interceptor/network-interceptor.bundle.js",
"trex-browser-extension-sdk/interceptor/injection-scripts.bundle.js",
"trex-browser-extension-sdk/content/components/reclaim-provider-verification-popup.css",
"trex-browser-extension-sdk/content/components/reclaim-provider-verification-popup.html"
],
"matches": ["
}
],
"content_scripts": [
{
"js": ["trex-browser-extension-sdk/content/content.bundle.js"],
"matches": ["
"run_at": "document_start"
}
]
}
- content_security_policy:启用 'wasm-unsafe-eval',供 WebAssembly 证明生成使用。offscreen
- permissions:(后台证明)、cookies(Provider 认证)、scripting(动态注册 content script 时必填)。
- host_permissions: 供 SDK 与各 Provider 通信。document_start
- web_accessible_resources:使 offscreen、interceptor、弹窗等资源可被扩展与页面访问。
- content_scripts:必须在 注入 content.bundle.js,且路径为 trex-browser-extension-sdk/content/content.bundle.js。
若使用动态注册 content script,必须声明 "scripting" 权限;静态注册时 scripting 可选。
若扩展另有自己的 content script,将 SDK 的脚本放在最前:
`json`
"content_scripts": [
{
"js": [
"trex-browser-extension-sdk/content/content.bundle.js",
"your-content-script.js"
],
"matches": ["
"run_at": "document_start"
}
]
---
在 background(service worker)入口中初始化 SDK:
`js
// background.js (service_worker)
import { reclaimExtensionSDK } from "trex-proxy-browser-extension-sdk";
// 幂等,可多次调用
reclaimExtensionSDK.initializeBackground();
`
方式 A:静态注册(推荐)
在 manifest 中配置 content_scripts 即可(见上一节),无需额外代码。
方式 B:动态注册
需要 "scripting" 权限。在安装时注册:
`js
// background.js
import { reclaimExtensionSDK } from "trex-proxy-browser-extension-sdk";
chrome.runtime.onInstalled.addListener(() => {
reclaimExtensionSDK.initializeBackground();
chrome.scripting.registerContentScripts(
[
{
id: "trex-sdk-bridge",
matches: ["
js: ["trex-browser-extension-sdk/content/content.bundle.js"],
runAt: "document_start",
world: "ISOLATED",
},
],
() => {
if (chrome.runtime.lastError) {
console.error("Failed to register content script:", chrome.runtime.lastError);
}
},
);
});
`
避免重复注册(如 service worker 重启后):
`js
async function ensureContentScriptRegistered() {
const scripts = await chrome.scripting.getRegisteredContentScripts();
const isRegistered = scripts.some((s) => s.id === "trex-sdk-bridge");
if (!isRegistered) {
await chrome.scripting.registerContentScripts([
{
id: "trex-sdk-bridge",
matches: ["
js: ["trex-browser-extension-sdk/content/content.bundle.js"],
runAt: "document_start",
world: "ISOLATED",
},
]);
}
}
chrome.runtime.onStartup.addListener(() => {
reclaimExtensionSDK.initializeBackground();
ensureContentScriptRegistered();
});
chrome.runtime.onInstalled.addListener(() => {
reclaimExtensionSDK.initializeBackground();
ensureContentScriptRegistered();
});
`
---
popup.html
`html`
popup.js
`js
import { reclaimExtensionSDK } from "trex-proxy-browser-extension-sdk";
const appIdInput = document.getElementById("appId");
const appSecretInput = document.getElementById("appSecret");
const providerIdInput = document.getElementById("providerId");
const startBtn = document.getElementById("startBtn");
const outputPre = document.getElementById("output");
startBtn.addEventListener("click", async () => {
const appId = appIdInput.value.trim();
const appSecret = appSecretInput.value.trim();
const providerId = providerIdInput.value.trim();
if (!appId || !appSecret || !providerId) {
outputPre.textContent = "Please fill in all fields";
return;
}
outputPre.textContent = "";
startBtn.disabled = true;
try {
const request = await reclaimExtensionSDK.init(appId, appSecret, providerId);
request.on("started", ({ sessionId }) => {
console.log("Verification started:", sessionId);
});
request.on("completed", (proofs) => {
outputPre.textContent = JSON.stringify(proofs, null, 2);
startBtn.disabled = false;
});
request.on("error", (err) => {
outputPre.textContent = Error: ${err?.message || err};
startBtn.disabled = false;
});
await request.startVerification();
} catch (e) {
outputPre.textContent = Error: ${e?.message || String(e)};`
startBtn.disabled = false;
}
});
- reclaimExtensionSDK.init(appId, appSecret, providerId [, options])
返回 Promise\(网页侧必传)。
- reclaimExtensionSDK.fromConfig(config [, options])
从服务端下发的配置对象创建 Request。
- reclaimExtensionSDK.fromJsonString(jsonString [, options])
从 JSON 字符串配置创建 Request。
- request.on("started" | "completed" | "error", callback)
监听会话开始、完成、错误。
- request.startVerification()
开始验证,返回 Promise,resolve 为 proofs。
---
从网页触发时,必须传入扩展 ID(否则无法通过 chrome.runtime.sendMessage 与扩展通信)。
`ts
import { reclaimExtensionSDK } from "trex-proxy-browser-extension-sdk";
const EXTENSION_ID = "
const request = await reclaimExtensionSDK.init(APP_ID, APP_SECRET, providerId, {
extensionID: EXTENSION_ID,
});
request.on("started", ({ sessionId }) => console.log("started", sessionId));
request.on("completed", (p) => console.log("completed", p));
request.on("error", (e) => console.error("error", e));
const proofs = await request.startVerification();
`
可选:先检测扩展是否已安装(若 SDK 提供 isExtensionInstalled({ extensionID }),可在发起前调用)。
---
使用 @reclaimprotocol/js-sdk 在服务端生成签名请求配置,避免在客户端暴露密钥。
服务端(Node/Express)
`js
const express = require("express");
const { ReclaimProofRequest } = require("@reclaimprotocol/js-sdk");
const app = express();
app.use(express.json());
app.use(express.text({ type: "/", limit: "50mb" }));
app.get("/generate-config", async (_req, res) => {
const APP_ID = process.env.RECLAIM_APP_ID;
const APP_SECRET = process.env.RECLAIM_APP_SECRET;
const PROVIDER_ID = "YOUR_PROVIDER_ID";
try {
const reclaimProofRequest = await ReclaimProofRequest.init(APP_ID, APP_SECRET, PROVIDER_ID);
reclaimProofRequest.setAppCallbackUrl("https://your-domain.com/receive-proofs");
const reclaimProofRequestConfig = reclaimProofRequest.toJsonString();
res.json({ reclaimProofRequestConfig });
} catch (error) {
res.status(500).json({ error: "Failed to generate request config" });
}
});
app.listen(3000);
`
客户端(网页或 popup)
`ts
import { reclaimExtensionSDK } from "trex-proxy-browser-extension-sdk";
const EXTENSION_ID = "
async function startFromServerConfig() {
const r = await fetch("/generate-config").then((x) => x.json());
const { reclaimProofRequestConfig } = r;
const request = reclaimExtensionSDK.fromJsonString(reclaimProofRequestConfig, {
extensionID: EXTENSION_ID,
});
request.on("started", ({ sessionId }) => console.log("started", sessionId));
request.on("completed", (p) => console.log("completed", p));
request.on("error", console.error);
await request.startVerification();
}
`
---
- SDK 资源需原样拷贝到 dist(或 public),不要被 Vite 打包或哈希。content.bundle.js
- Content script 必须引用经典脚本 ,不要用 ESM bundle。vite-plugin-static-copy
- 可使用 在构建时拷贝:
`ts
import { viteStaticCopy } from "vite-plugin-static-copy";
viteStaticCopy({
targets: [
{
src: "node_modules/trex-proxy-browser-extension-sdk/build/content/**",
dest: "trex-browser-extension-sdk/content",
},
{
src: "node_modules/trex-proxy-browser-extension-sdk/build/interceptor/**",
dest: "trex-browser-extension-sdk/interceptor",
},
{
src: "node_modules/trex-proxy-browser-extension-sdk/build/offscreen/**",
dest: "trex-browser-extension-sdk/offscreen",
},
{
src: "node_modules/trex-proxy-browser-extension-sdk/build/ReclaimExtensionSDK.bundle.js",
dest: "trex-browser-extension-sdk",
},
],
});
`
也可在构建前执行 npm run trex-extension-setup,确保 public/trex-browser-extension-sdk/ 已存在。
---
| 现象 | 处理 |
| --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| “Unexpected token 'export'” | 使用经典脚本 content.bundle.js,不要加载 ESM。 |init
| “chrome.runtime.sendMessage called from a web page must specify Extension ID” | 在 /fromJsonString/fromConfig 的 options 中传入 { extensionID }。 |public/trex-browser-extension-sdk/
| Provider 页签不打开 | 检查资源是否拷贝到 、manifest 中 web_accessible_resources 与 content_scripts 路径是否正确、background 是否已调用 reclaimExtensionSDK.initializeBackground()、content script 是否已注册。 |npm run trex-extension-setup
| 资源 404 | 确认已执行 ,且 manifest 中路径为 trex-browser-extension-sdk/...(不要带 public/ 前缀)。 |content_security_policy.extension_pages
| CSP 报错 / WASM 加载失败 | 确保 包含 'wasm-unsafe-eval'。 |
---
- [ ] 已执行 npm run trex-extension-setup,且 public/trex-browser-extension-sdk/ 下资源完整host_permissions
- [ ] Manifest 已配置 CSP、、permissions(含 offscreen、cookies,动态注册时含 scripting)web_accessible_resources
- [ ] 已配置 ,且路径为 trex-browser-extension-sdk/...document_start
- [ ] Content script 已加载(静态或动态),且为 reclaimExtensionSDK.initializeBackground()
- [ ] Background 已调用 extensionID
- [ ] 从网页发起时已传入
---
`ts``
import type {
reclaimExtensionSDK,
ReclaimExtensionProofRequest,
} from "trex-proxy-browser-extension-sdk";