A backend-agnostic, privacy-first analytics SDK for modern browsers. Tracelet gives product code one typed API while keeping queues, codecs, compression and delivery behind replaceable contracts.
Live playground · Quickstart · SDK evaluation guide · Examples · API reference · 简体中文
- Backend-agnostic: send the same event model to your own HTTP collector or any custom
Transport. - Reliable in the browser: persist pending events in IndexedDB and coordinate shared queues across tabs.
- Privacy-aware: check consent before identities or events are created; autocapture never reads DOM text or form values.
- Type-safe: model event names and properties with a TypeScript event map.
- Replaceable by design: swap storage, routing, codecs, compression and plugins without changing product instrumentation.
npm install tracelet-browserimport {
HttpTransport,
createAnalytics,
createBrowserEventStorage,
} from "tracelet-browser";
interface Events {
order_submitted: { orderId: string; amount: number };
}
const eventStorage = await createBrowserEventStorage({
databaseName: "storefront-analytics",
onFallback(error) {
console.warn("IndexedDB unavailable; analytics will use memory", error);
},
});
const analytics = createAnalytics<Events>({
appId: "storefront",
eventStorage,
transport: new HttpTransport({
endpoint: "https://events.example.com/v1/batch",
}),
});
await analytics.track("order_submitted", {
orderId: "o_123",
amount: 199,
});Try this lifecycle without creating a backend in the interactive playground. Tracelet is framework-independent and tested in Chromium, Firefox and WebKit on every change.
Runnable starters:
Planning: 0.4 release readiness · 1.0 readiness gates · 0.3 → 0.4 migration · EventBatch v1 · Browser support · Compatibility matrix
Privacy and security: Data inventory · Threat model · Failure semantics · Security policy
Community: Support · Contributing · Code of Conduct · Accessibility · Security · Issues
0.3.xremains the stable line. The published0.4.0-beta.2prerelease fixes Consent delivery and concurrent erasure issues found after beta.1. Installtracelet-browser@0.4.0-beta.2for evaluation; thebetatag points to it whilelateststays on0.3.0. See the migration guide and changelog before upgrading.
Tracelet 是一个后端无关、隐私优先的浏览器埋点 SDK。SDK 核心保持框架无关,React 官网作为独立 workspace 维护。
安装:
npm install tracelet-browser
pnpm add tracelet-browser
yarn add tracelet-browser
tracelet已在 npm 被其他项目占用,因此本项目使用tracelet-browser作为 npm 包名。
- 官网:tracelet-opal.vercel.app
- 在线体验:Playground
- 快速开始:Quickstart
- SDK 选型指南:TypeScript 前端埋点 SDK:从选型到上线
- 场景示例:Examples
- API 查询:Reference
- npm:tracelet-browser
- 源码:shishikami/Tracelet
当前版本见页面顶部的 npm badge,适合评估、原型和对投递行为有明确验证方案的早期项目。
Tracelet 的完整浏览器套件会在固定版本的 Chromium、Firefox 和 WebKit 中运行。精确引擎版本、必需能力、能力缺失时的降级行为,以及尚未单独验证的移动端/隐私模式,见浏览器支持策略;不要把“同一引擎”理解成未经测试的设备兼容承诺。
track / page / identify
-> standard event
-> plugins
-> event storage
-> event route
-> batch
-> codec
-> compression
-> transport
客户端与 Collector 的稳定线协议见 EventBatch v1。协议把任意 2xx 定义为整批事件已持久接收(包括按 eventId 判定的重复事件),因此 Collector 不应在只提交部分事件时返回成功。
import {
BrowserLocalStorage,
HttpTransport,
createAnalytics,
createBrowserEventStorage,
} from "tracelet-browser";
interface Events {
order_submitted: {
orderId: string;
amount: number;
};
}
const eventStorage = await createBrowserEventStorage({
databaseName: "shop-analytics",
onFallback(error) {
console.warn("IndexedDB 不可用,埋点队列将退回内存", error);
},
});
const analytics = createAnalytics<Events>({
appId: "shop-web",
identityStorage: new BrowserLocalStorage(),
eventStorage,
transport: new HttpTransport({
endpoint: "https://events.example.com/v1/batch",
}),
});
await analytics.track("order_submitted", {
orderId: "o_123",
amount: 199,
});自动采集是可选插件,不会扫描 DOM,也不会读取元素文本、表单值或 data-* 属性。它目前只处理 SPA 页面浏览和浏览器全局错误:
import {
createAnalytics,
createAutoCapturePlugin,
} from "tracelet-browser";
const analytics = createAnalytics({
appId: "shop-web",
transport,
plugins: [
createAutoCapturePlugin({
pageViews: {
initial: true,
// Hash 可能包含敏感信息,仅 Hash Router 项目显式开启。
includeHash: false,
},
errors: {
// 堆栈可能包含文件路径或参数,因此默认关闭。
includeStack: false,
},
}),
],
});
await analytics.init();页面浏览通过 page() 事件进入统一管线。URL 上下文默认移除 query 和 hash;Hash Router 可以用 includeHash: true 开启 fragment 路由采集。window.error 生成 $error,未处理的 Promise 拒绝生成 $unhandled_rejection。它们都会遵守 Consent,调用 shutdown() 后会移除监听器并恢复被包装的 History 方法。
关键业务事实仍应使用显式 track(),例如支付成功或订单创建;自动采集适合补充页面访问和技术异常,而不是替代业务事件设计。
需要用户授权后才能采集时,将初始状态设为 pending:
const analytics = createAnalytics({
appId: "shop-web",
transport,
consent: {
initialState: "pending",
clearQueueOnDenied: true,
},
});
await analytics.track("ignored_before_consent", {});
await analytics.setConsent("granted");
await analytics.track("collected_after_consent", {});
// 撤回授权会清空未发送队列并重置身份。
await analytics.setConsent("denied");默认值仍为 granted,便于服务端已统一处理授权的项目保持兼容。
IndexedDbEventStorage按写入顺序持久化未发送事件。- 单事件默认限制为 64 KiB,编码后单批次默认限制为 256 KiB,可通过
maxEventBytes和maxBatchBytes调整。 visibilitychange和pagehide会触发最后一次 Flush。- Web Locks API 会协调同源多 Tab 的共享队列发送,避免并发 Flush 重复读取同一批事件。
- Session 默认在连续 30 分钟无事件后自动续期,可通过
sessionTimeoutMs调整。 - 网络、HTTP 408、429 和 5xx 使用指数退避与随机抖动重试。
- HTTP 4xx 等不可重试错误会从队列移除,防止永久阻塞。
- 每个事件拥有稳定
eventId,服务端应按该字段幂等去重。
可通过构造参数 onDiagnostic 或运行时 subscribeDiagnostics() 观察 SDK 自身状态,不会生成新的业务埋点。投递事件使用 attemptId 关联同一次发送:
const analytics = createAnalytics({
appId: "shop-web",
transport,
onDiagnostic(event) {
console.debug("[analytics]", event);
},
});
const unsubscribe = analytics.subscribeDiagnostics((event) => {
if (event.type === "flush_failed") {
console.warn(event.attemptId, event.status, event.retryable);
}
});
const snapshot = await analytics.getQueueSnapshot();
console.info(snapshot.pendingEvents, snapshot.delivery.phase);
unsubscribe();getQueueSnapshot() 返回只读的近似快照。pendingEvents 来自当前 EventStorage,oldestEventTimestamp 是事件时间而不是精确入队时间;delivery.phase 为 idle、sending 或 waiting-retry。诊断与快照都不会包含事件 Properties、用户 ID、请求体或鉴权头。
使用 IndexedDbEventStorage 时,可以读取浏览器提供的近似容量信息:
const estimate = await analytics.getStorageEstimate();
if (estimate && (estimate.usageRatio ?? 0) >= 0.8) {
console.warn("Origin storage usage is high", estimate);
}usageBytes、quotaBytes、availableBytes 和 usageRatio 都是整个 Origin 的估算值,可能包含应用自己的 IndexedDB、Cache Storage 等数据,并非 Tracelet 独占空间;浏览器也可能为了隐私而舍入或省略字段。当 IndexedDB 写入实际触发配额错误时,SDK 不会让 track() 影响业务流程,而是丢弃当前事件并发出 event_dropped: storage-quota 与 storage_quota_exceeded 诊断。SDK 不会擅自删除其他应用数据。
完整示例见 examples/browser.ts;官网 Quickstart 来自 examples/quickstart.ts,并在 CI 中执行 TypeScript 类型检查。
Tracelet 不要求框架 Wrapper。推荐在渲染过程之外创建一个应用级实例,并只把初始化连接到框架根生命周期:
- React:根部渲染
TraceletBootstrap,避免在普通组件卸载时关闭全局单例。 - Vue:在
app.mount()前注册traceletPlugin,组合式函数通过注入取得同一实例。 - Next.js:从根布局渲染带
"use client"的TraceletClient,避免在 Server Component 中初始化浏览器能力。
可运行代码位于 examples/frameworks,详细边界见 docs/framework-integrations.md。这些示例会在 CI 中由真实 React/Vue 类型检查。
EventRouter 会在 Codec 与 Compression 之前检查原始事件,并把每个事件交给第一个匹配的 Transport。未匹配事件必须进入 fallback,因此不会因规则遗漏永久阻塞队列:
import { EventRouter, HttpTransport, createAnalytics } from "tracelet-browser";
const analytics = createAnalytics({
appId: "shop-web",
transport: new EventRouter({
routes: [
{
name: "technical-errors",
match: (event) =>
event.name === "$error" || event.name === "$unhandled_rejection",
transport: new HttpTransport({
name: "alert-collector",
endpoint: "https://alerts.example.com/v1/batch",
}),
},
],
fallback: new HttpTransport({
name: "product-collector",
endpoint: "https://events.example.com/v1/batch",
}),
}),
});规则按声明顺序匹配。SDK 保持队列的 FIFO 顺序,并在目标 Transport 改变时切分批次;一次 flush() 仍只发送一个批次。flush_started、flush_succeeded 和 flush_failed 诊断事件会携带实际的 transport 名称,方便区分多个端点。完整示例见 examples/routing.ts。
Core 不依赖具体 Protobuf Runtime。使用 protobufjs、ts-proto 或 Buf 生成编码器后,通过 ProtobufCodec 注入:
import { ProtobufCodec, createAnalytics } from "tracelet-browser";
import { EventBatch } from "./generated/analytics.js";
const codec = new ProtobufCodec((batch) => {
return EventBatch.encode(EventBatch.fromJSON(batch)).finish();
});
const analytics = createAnalytics({
appId: "shop-web",
codec,
transport,
});示例协议位于 proto/analytics.proto。实际项目建议把生成代码放在独立包,例如 @tracelet/codec-protobuf。
- 默认队列仍是内存存储;浏览器生产环境应显式使用
createBrowserEventStorage(),优先选择 IndexedDB 并在启动失败时退回内存。 - gzip 使用浏览器原生
CompressionStream,不支持时不会自动降级。 - 自动采集暂不包含点击、输入和 DOM 内容采集;这是有意的隐私与稳定性边界。
properties和context在示例 Protobuf 中以 JSON bytes 保存,优先保持通用性;稳定后可以逐步强类型化。
pnpm build
pnpm build:docs
pnpm local:server
pnpm test:browser浏览器访问 http://127.0.0.1:4318 查看网页版使用手册;访问 http://127.0.0.1:4318/lab 打开完整的 SDK 验证台。页面支持:
- 自定义事件名、Properties 和批量数量。
- 仅入队或立即 Flush,并实时查看 IndexedDB 队列长度。
- Identify、身份重置和 Consent 状态切换。
- 注入 503、429、500、400 以及服务端响应延迟。
- 观察指数退避、失败保留、不可重试丢弃等行为。
- 查看服务端事件、批次统计、请求日志和 SDK 诊断日志。
Collector 同时提供以下调试接口:
http://127.0.0.1:4318/health
http://127.0.0.1:4318/v1/events
http://127.0.0.1:4318/v1/stats
| 方法 | 路径 | 用途 |
|---|---|---|
POST |
/v1/batch |
接收 SDK 批量事件 |
POST |
/v1/batch/:channel |
验证多个 Transport 的条件路由 |
GET |
/v1/events?limit=100 |
查询最近事件 |
DELETE |
/v1/events |
清空事件和统计 |
GET |
/v1/stats |
查询批次、失败、事件名和请求日志 |
POST |
/v1/control/failure |
配置后续请求的失败状态与延迟 |
本地 Collector 仅用于开发验证:事件只保存在内存中,进程退出即清空,不包含生产鉴权、限流和持久化能力。
官网采用 React、Vite、Tailwind CSS 和 shadcn/ui 的源码组件模式;这些依赖只存在于私有的 website workspace,不会进入 SDK 的 npm 包:
pnpm dev:docs
pnpm build:docs官网源码位于 website/,构建产物位于 out/。精简首页、Quickstart、Examples、API Reference 与两篇指南分别使用独立 URL,英文页面统一增加 /en 前缀。所有页面共享组件与中英文搜索,文档页提供粘性侧边目录,并具有各自的 canonical、hreflang、首个 HTML 响应摘要和搜索元数据。仓库附带 vercel.json,导入 Vercel 后会自动安装 workspace 依赖、运行构建并发布 out/。其他静态托管平台只需把构建命令设为 pnpm build:docs、输出目录设为 out。框架演进判断见 docs/website-framework.md。
src/ SDK 源码(不依赖 React)
tests/ SDK 行为测试
examples/ 浏览器接入和本地验证示例
website/ React + Vite + Tailwind 官网
proto/ 可选的 Protobuf 协议示例
项目通过 GitHub Actions 自动完成持续集成与 npm 发布:
- Pull Request 和
main分支推送会在 Node.js 20、24 上运行类型检查、测试、官网构建和 npm 包内容检查。 - CI 会把
npm pack生成的 tarball 安装进空白项目,验证 JavaScript 导入、TypeScript 声明和 Vite 构建,避免“源码能用但发布包不可用”。 - Node.js 24 还会安装同一个 tarball,并完成 React/Vite、Vue/Vite、Next.js/Turbopack、Webpack 与 Rollup 的真实生产构建。
- Node.js 检查通过后,会在 Chromium、Firefox 和 WebKit 上运行浏览器端到端测试。
- 发布 GitHub Release 后,工作流会校验 Git tag 与
package.json版本一致,再通过 npm Trusted Publishing 提交待审核版本;SemVer 预发布版本固定使用betadist-tag,正式版本使用latest。 - Vercel 保持 Git 集成:Pull Request 生成 Preview,
main更新生产站点,不在 Actions 中重复部署。
首次启用自动发布时,需要在 npm 包的 Settings → Trusted Publisher 中添加 GitHub Actions:
- Organization or user:
shishikami - Repository:
Tracelet - Workflow filename:
publish.yml - Environment:留空
- Allowed action:保持直接
npm publish关闭;npm stage publish默认允许
日常发布步骤:
- 更新
package.json版本和CHANGELOG.md。 - 合并到
main,等待 CI 通过。 - 创建同版本 tag(例如版本
0.2.0对应v0.2.0)。 - 发布 GitHub Release;
Publish npm package工作流会把版本提交到 npm 暂存区。 - 在 npm 的 Staged Packages 中检查包内容,并通过 Passkey/2FA 批准,版本才会正式公开。
本地发布前检查仍可使用:
pnpm check
pnpm smoke:package
pnpm smoke:ecosystem
pnpm pack:check完整发布检查表和故障处理见 RELEASING.md。
如果自动发布失败,请先修复 CI、Trusted Publisher 或 staged publishing 配置,再重新创建新的版本;不要绕过人工审批直接执行 npm publish。已经公开到 npm 的版本号不能覆盖。
正式发布前请更新 package.json 版本号。npm 包只包含 dist/、README、CHANGELOG、LICENSE 和 npm 必需的 package.json 清单,不包含官网与测试文件。
0.2.0 里程碑已完成:
- 浏览器测试覆盖 pagehide、gzip 和异常恢复
- 多 Transport 路由与条件投递
- 存储配额估算与 IndexedDB 配额诊断
- 文档搜索和框架适配示例
0.3.0 已完成。0.4.0 聚焦 1.0 前的契约硬化:
-
可观测 API 设计稿:队列快照、投递状态和多订阅者语义。
-
兼容性与韧性测试矩阵:明确自动化、部分覆盖、手工验证和待验证边界。
-
Vercel Collector starter:CORS、安全大小边界、gzip、鉴权扩展、PostgreSQL 迁移与跨实例幂等 Sink。
-
实现并验证可观测 API:队列快照、运行时订阅、Attempt 关联与三浏览器覆盖。
-
发布并验证
0.3.0正式版。 -
增加 CSP、离线恢复、隐藏页 Flush 与真实跨标签页锁的三浏览器夹具。
-
增加 React、Vue、Next.js、Webpack 与 Rollup 的真实生产构建夹具。
-
评估 CDN / IIFE 构建:0.3 保持原生 ESM-only,等待真实无打包器需求后再扩展。
-
增加 IndexedDB 启动探测与显式内存后备队列,避免首个
track()因持久存储不可用而影响业务。 -
对完整 TypeScript 声明建立可审查的 API 快照,而不只检查运行时导出名称。
-
冻结 EventBatch v1、重试、幂等与兼容性语义。
-
明确页面退出、关闭后调用和适配器异常的生命周期边界。
-
将 npm 暂存发布与人工批准后的公共 Registry 验证拆成独立成功门禁。
-
增加 PostgreSQL 参考 Sink 与真实数据库重启幂等 CI。
-
完成真实项目接入与持续运行验证,为 1.0 提供生产证据。
0.3 的公开命名与 ESM-only 分发范围已经冻结;历史发布证据见 0.3 release readiness。
版本变化见 CHANGELOG.md。