@macroui/event-tracker
v1.3.1
Published
A self-developed web event tracking SDK, inspired by Sensors Analytics. Supports image/beacon/ajax/batch send, full auto-tracking, WebView bridge, and pluggable encryption.
Maintainers
Readme
@macroui/event-tracker
自研的 Web 埋点 SDK,参考神策 sa-sdk-javascript 的 API 风格
4 种发送方式 / 全埋点 / WebView 桥 / 加密插件 —— 全 TypeScript 源码,全 Jest 单测
特性
- 多发送方式:
image/beacon/ajax/batch,自动降级 - 多 sink 上报:
server_url: string | string[] - 预置事件:
$pageview/$WebClick/$WebStay/$SignUp/$BindID/$UnbindID - 公共属性:
$lib/$app_id/$screen_width/$viewport_width/$url/$referrer/$latest_utm_*... - 用户身份:
distinct_id/anonymous_id/first_id/login_id/original_id - 3 阶段拦截器:
buildDataStage/businessStage/sendDataStage,priority 排序 - 7 生命周期钩子:
sdkReady/sdkInitPara/sdkAfterInitPara/sdkInitAPI/sdkAfterInitAPI/changeDistinctId/switch - 加密插件:XOR+Base64 / AES-GCM(Web Crypto)
- WebView 桥:vapph5 协议,自动取
app_version/app_id/channel - 失败重试:指数退避 1s→2s→4s→30s cap
- 失败队列:localStorage 暂存,断网时延后上报
- 跨实例通信:
BroadcastChannel(localStorage 兜底) - 签名身份:
HMAC-SHA256防 cookie 篡改 - 13 个官方插件:pageload / pageleave / exposure / session-event / debug-sender / indexeddb-sender / auto-track / webview-bridge / encrypt / etc.
- TypeScript:完整 .d.ts,IDE 提示
- 多格式:
ESM/CJS/UMD/UMD-min - 零依赖:核心 SDK 无第三方依赖
- 测试:Jest 90 测试 / 15 套件 + fast-check 属性测试 + tsd 类型测试
- 覆盖率:51% lines(开发期阈值:30%)
快速开始
安装
npm install @macroui/event-tracker初始化(UMD)
<script src="https://cdn.jsdelivr.net/npm/@macroui/event-tracker/dist/event-tracker.min.js"></script>
<script>
eventTracker.init({
app_id: 'demo',
server_url: 'https://api.example.com/sa',
send_type: 'beacon', // 'image' | 'beacon' | 'ajax' | 'batch'
debug: 1, // 0=关闭 1=basic 2=verbose
});
eventTracker.track('user_login', { method: 'email' });
</script>ESM
import { EventTracker, pageLoad, sessionEvent } from '@macroui/event-tracker';
const sdk = new EventTracker();
sdk.init({
app_id: 'demo',
server_url: 'https://api.example.com/sa',
send_type: 'ajax',
});
sdk.use(pageLoad());
sdk.use(sessionEvent());
sdk.track('button_click', { btn_id: 'submit' });
sdk.setProfile({ $city: 'Beijing', $plan: 'pro' });多 sink / 多实例
sdk.switchServer(['https://primary.example.com/sa', 'https://backup.example.com/sa']);
const tracker2 = initPara({
app_id: 'demo',
server_url: 'https://api2.example.com/sa',
client_id: 'm2',
});API 一览
事件
| API | 说明 |
|---|---|
| track(event, props, cb?) | 自定义事件 |
| trackSignup(userId, props?, cb?) | 注册即触发($SignUp) |
| trackLinkage(props, cb?) | 关联事件 |
| trackLink(el, props?, cb?) | 单链接点击 |
| trackLinks(els, props?, cb?) | 多链接点击 |
| trackHeatMap(props, cb?) | 热力图事件 |
| trackWebClick(props, cb?) | $WebClick |
| trackWebStay(props, cb?) | $WebStay |
| alias(newId, originalId?) | 合并身份 |
Profile
| API | 说明 |
|---|---|
| setProfile(props, cb?) | 设置 |
| setOnceProfile(props, cb?) | 仅首次 |
| incrementProfile(props, cb?) | 累加 |
| appendProfile(props, cb?) | 追加 |
| unsetProfile(prop, cb?) | 取消 |
| deleteProfile(cb?) | 删除 |
身份
| API | 说明 |
|---|---|
| identify(id, save=true) | 设置 distinct_id |
| login(id) | 登录 |
| logout(newAnon?) | 登出 + 重置匿名 |
| resetAnonymousIdentity(newAnon?) | 同 logout |
| getDistinctId() | 当前 distinct_id |
| getAnonymousId() | 匿名 ID |
| getFirstId() | 首次访问 ID |
| getLoginId() | 登录 ID |
| getOriginalId() | 登录前 ID |
公共属性
| API | 说明 |
|---|---|
| register(props) | 永久属性 |
| registerOnce(props) | 一次性 |
| registerPage(props) | 页面(一次性) |
| registerSession(props) | 会话(永久) |
| registerSessionOnce(props) | 会话(一次性) |
| clearAllRegister() | 清空 |
| unsetRegister(key) | 单个清空 |
控制
| API | 说明 |
|---|---|
| flush() | 强制 flush batch + failed queue |
| waitUntilIdle() | 等待所有 in-flight 完成 |
| abort(reason?) | Abort 所有挂起任务 |
| close() / shutdown() | 完整关闭 |
| disable() / enable() | 禁用 / 启用 |
| setServerURL(url) | 切换 sink |
| use(plugin) | 注册插件 |
| getPresetProperties() | 当前 preset |
插件
13 个官方插件,按场景分组:
自动采集
pageLoad()— $WebPageLoad(性能 API)pageLeave()— $WebPageLeave(visibilitychange)exposure(opts)— $element_exposure(IntersectionObserver)sessionEvent(opts)— $session_start / $session_endautoTrack()— pageview / webclick / webstay
发送增强
debugSender(opts)— debug 模式(GET 到 ?debug URL + alert)indexedDBSender(opts)— 写到 IDB outboxwebviewBridge()— vapph5 协议
安全 / 隐私
encryptor({ encrypt, decrypt?, encryptEvent?, decryptEvent? })— 通用加密xorEncryptor(key)— XOR+Base64 便捷工厂aesGcmEncryptAsync(key)/aesGcmDecryptAsync(key)— AES-GCM
Plugin Authoring Guide
写自定义插件非常简单:
import type { Plugin, InstallContext } from '@macroui/event-tracker';
export function myPlugin(): Plugin {
return {
name: 'my-plugin',
install({ stages, config, sdk }: InstallContext) {
// 注册阶段拦截器
stages.sendDataStage.register({
name: 'my-modifier',
priority: 50, // 越大越晚
entry(ctx) {
// 修改 ctx.data(事件或属性)
if (ctx.data && ctx.data.properties) {
ctx.data.properties.$my_plugin = 'ran';
}
},
});
},
uninstall({ config }) {
// 清理工作:删除 config 字段、解除事件监听等
},
};
}
sdk.use(myPlugin());阶段(stage)约定
| 阶段 | 用途 | 推荐 priority |
|---|---|---|
| buildDataStage | 事件构造后,encrypt / send 之前 | 1-50(低) |
| businessStage | 业务校验 / 转换 / 风控 | 50-150 |
| sendDataStage | 实际发送 / 加密 / 落盘 | 100+(高) |
生命周期钩子
install({ sdk }) {
sdk.getRegistry().on('sdkReady', (cfg) => { /* cfg: TrackerConfig */ });
sdk.getRegistry().on('changeDistinctId', (id) => { /* 用户切换身份 */ });
}钩子列表:sdkReady / sdkInitPara / sdkAfterInitPara / sdkInitAPI / sdkAfterInitAPI / changeDistinctId / switch
加密 / 解密
import { encryptor, aesGcmEncryptAsync, aesGcmDecryptAsync } from '@macroui/event-tracker';
sdk.use(encryptor({
encrypt: aesGcmEncryptAsync('my-32-byte-secret-key-1234567890ab'),
decrypt: aesGcmDecryptAsync('my-32-byte-secret-key-1234567890ab'),
encryptEvent: (e) => {
// 脱敏:去掉 PII 字段
if (e.properties?.phone) delete e.properties.phone;
return e;
},
}));⚠️
aesGcmEncrypt同步版本仅做 API 完整性占位(Web Crypto 是异步的); 真同步场景请用 XOR(xorEncryptor)。
失败处理
SDK 包含三层失败保护:
- 重试:
max_retry_count× 指数退避(默认 3 次) - 失败队列:失败的事件入 localStorage,下次启动重发
flush():手动强制 flush batch + failed queue
sdk.track('important_event');
await sdk.waitUntilIdle(); // 等待所有 in-flight
// 出错捕获
sdk.track('foo', {}, (err) => {
if (err) console.error('send failed:', err);
});从神策 v1.27 / v2.0 迁移
| 神策 API | event-tracker | 状态 |
|---|---|---|
| init(config) | init(config) | ✅ 完全兼容字段名 |
| track(name, props) | track(name, props) | ✅ |
| trackSignup(userId) | trackSignup(userId) | ✅ |
| profileSet(props) | setProfile(props) | ✅ 别名 setProfile |
| profileSetOnce(props) | setOnceProfile(props) | ✅ |
| register(props) | register(props) | ✅ |
| initPara(config) | initPara(config) | ✅ |
| registerAppid(id) | registerAppid(id) | ✅ |
| alias(newId) | alias(newId) | ✅ |
| trackLinkage / trackLink / trackLinks | 同名 | ✅ |
| trackHeatMap / trackWebClick / trackWebStay | 同名 | ✅ |
| debug_mode 集成 | debugSender({ url }) 单独插件 | ⚠️ 需 use |
| 38 个官方插件 | 13 个核心插件 | ⚠️ 关键已覆盖 |
| 加密 encode | encryptor({ encrypt }) | ⚠️ 插件化,需 use |
开发
git clone https://github.com/macroui/event-tracker
cd event-tracker
npm install
# 开发
npm run typecheck # tsc --noEmit
npm run lint # eslint
npm test # Jest
npm run test:cov # 覆盖率
npm run test:bench # 性能基准
npm run test:types # 类型测试(tsd)
# 构建
npm run build # rollup + tsc-dts架构
src/
├── core/
│ ├── tracker.ts 主类(init + 公共 getter)
│ ├── track-api.ts track 家族方法
│ ├── identity-api.ts identify / login / logout / alias
│ ├── profile-api.ts profile set/append/...
│ ├── register-api.ts register / registerOnce
│ ├── send-pipeline.ts sendEvent + flush + waitUntilIdle
│ ├── identity.ts IdentityManager
│ ├── uuid.ts UUID v4
│ ├── logger.ts 日志
│ ├── env.ts 浏览器能力
│ ├── stage.ts StageChain(3 阶段)
│ ├── lifecycle.ts 7 生命周期钩子
│ ├── plugin.ts PluginRegistry
│ ├── instance-channel.ts 跨实例通信
│ ├── retry-policy.ts 指数退避
│ ├── failed-queue.ts 失败队列
│ ├── profile-store.ts 用户档案(v1.2)
│ ├── signed-identity.ts HMAC 签名
│ ├── errors.ts SDKError 体系
│ └── instance-factory.ts initPara / registerAppid
├── storage/ KVStore + IndexedDB
├── send/ ImageSender / BeaconSender / AjaxSender / BatchSender
├── presets/ 预置属性
├── plugins/ 13 个插件
└── types/ TrackerConfig(10 个子集)+ EventData + 常量路线图
- v1.0:基础 6 插件 ✅
- v1.1:完整神策 API + 3 阶段 + 7 钩子 + 多 sink + 加密 + 重试 + 跨实例 ✅
- v1.1.1:架构重构 + 类型分层 + 错误体系 + signedIdentity + 9 个测试套件 ✅
- v1.2:plugin marketplace / web worker / WebAssembly 加密
- v2.0:service worker / Source Map / 多端协议统一
许可
MIT
