身份与会话
标识「设备 / 用户 / 会话」三种主体,并把 device id 注入业务请求做跨请求追踪。
身份能力由 IdentityPlugin 承载,和归因一样默认装配、不受 Consent 管控。
如何使用
身份的日常操作就是跟着登录态走:登录后绑定用户,登出时清除。
ts
// 登录成功后:绑定业务用户,可附带贯穿后续事件的业务维度
trace.identify('user_8821', { plan: 'pro' });
// 登出:清除内存里的用户身份
trace.clearUser();绑定后,后续所有事件都会自动带上 user_id,无需在每次上报时手动传。device_id、session_id 由 SDK 自动维护,通常不用你操心;只有在「同一个页面里要切换成新会话」这种少见场景,才显式调用 renewSession():
ts
trace.renewSession(); // 不刷新页面,开启一个新 session三 ID 身份模型
SDK 用三个 ID 标识不同主体,语义和落地各不相同——这是理解后面 API 的基础:
| ID | 含义 | 来源 | 持久化 | 进请求头 |
|---|---|---|---|---|
device_id | 设备级永久标识 | deviceIdOverride → cookie → localStorage → FingerprintJS | cookie(1 年,锚根域)+ localStorage 兜底 | ✅ X-DeviceId |
user_id | 业务登录用户 | identify() 写入 | 纯内存 | ❌ 永不进 header |
session_id | 页面会话 | 每次页面加载新生成 | 纯内存(不落盘) | ❌ 不进 header,随事件公参 context.session_id 上报 |
API 说明
trace.deviceId / trace.headers
- 含义与作用:当前设备 id,与可直接合并进请求的 header 对象。
- 类型:
get deviceId: string;get headers: Record<string, string>。 - 注意:
init()完成前 / SSR 下deviceId为空串、headers为{}(空对象,可安全Object.assign合并)。headers只含 device id(X-DeviceId);session_id不进请求头,随每条事件公参context.session_id上报。
trace.identify(userId, traits?)
- 含义与作用:登录后绑定业务用户,写入内存身份并触发一条
identity绑定事件上报。 - 类型:
(userId: string, traits?: Record<string, unknown>) => void。 - 注意:SSR 下整体 no-op。空
userId时不触发绑定事件与广播,但仍会覆写内存身份(相当于清掉已有user_id)——清登录态请显式用clearUser()。fire-and-forget,前端不做去重——每次identify()都如实上报,重复由后端幂等收敛。user_id仅进事件公参,永不进请求头。
trace.clearUser()
- 含义与作用:登出,清除内存里的用户身份。
- 类型:
() => void。 - 注意:不发 unbind 事件(绑定关系在后端已是稳定事实)。
trace.sessionId / trace.renewSession()
- 含义与作用:当前会话 id;
renewSession()不刷新页面就开启新会话。 - 类型:
get sessionId: string;renewSession(): string(返回新 id)。 - 注意:
init()前 / SSR 下sessionId为空串;renewSession()在 SSR 下返回空串。
trace.setGlobalParams(params)
- 含义与作用:设置全局业务参数,后续所有事件自动携带于
payload.globalParams私参命名空间。 - 类型:
(params: Record<string, unknown>) => void。 - 注意:某字段传
null即删除它。适合放 AB 分组、租户等贯穿全程的维度。
ts
trace.setGlobalParams({ ab_group: 'B', tenant: 'acme' });
trace.setGlobalParams({ ab_group: null }); // 删除 ab_group实现原理
device id:一次解析,整会话冻结
init() 内按固定链路解析一次,之后整个会话不变:
deviceIdOverride 显式传入 → 用它,并写入缓存
↓ 无
cookie 命中缓存 → 用缓存,并同值回写刷新有效期(滑动过期,避免活跃用户满一年断裂)
↓ 无
localStorage 命中缓存 → 用缓存,并回写 cookie(历史值迁移,设备身份不断裂)
↓ 无
FingerprintJS 现算 → 用它,并写回缓存(下次直接命中 cookie)写入时 cookie 为主、localStorage 双写兜底:
- cookie 名
tripo_device_id,Max-Age1 年,Path=/、SameSite=Lax,非 http 页面带Secure。 Domain自动探测当前 host 可写入的最宽可注册根域(如.example.com),同根域下各子域共享同一个device_id;localhost/ IP / 单段域没有根域概念,自动降级为 host-only。- 同时写一份 localStorage(key 仍是
tripo:device_id),用户禁用 cookie 时仍能复用同一 ID。 - 读写同一白名单:device id 须为可打印 ASCII(不含空格与控制字符)、1-128 位。cookie / localStorage 读到的值不匹配则当没读到、回落下一级;
deviceIdOverride不匹配则忽略并控制台 warn 一次、走默认解析链——读写口径一致,不会出现「写得进、读不回」的身份漂移。根域 cookie 同根域任意子域与服务端都能写,此校验防止脏值(如含换行的注入串)进入X-DeviceId请求头导致请求构造失败;带./:/@的业务 override 风格 id 均合法。
device id 是唯一会注入业务请求的标识——通过 trace.headers 合并到请求头(X-DeviceId),实现跨请求追踪;session_id 不进请求头,随每条事件公参 context.session_id 上报(仅页面卸载兜底发送路径会额外把 session_id 写进批量壳 body——网关 header 契约里没有 session 头,卸载批靠壳补上会话身份)。
session id:仅存内存、不落盘
每次页面加载(刷新 / 重开 tab)都新生成,仅存内存、不落盘——跨加载天然不复用,没有 30 分钟失效、没有日切。renewSession() 可在不刷新页面时显式切到新会话。
为什么这样设计
- device / user / session 三者正交:分别回答「哪台设备、哪个登录用户、哪一次访问」,后端可独立分析与关联。
- ID 合并归后端:前端不做 device ↔ user 的关联,只如实上报三个标识,关联是后端的事——这呼应整体的前后端职责边界。
注意事项
user_id永不进请求头:只进事件公参context.user_id,避免把用户标识泄露到所有业务请求里。session是页面会话,不是登录会话——别用它判断登录态。- SSR 安全:以上 API 在非浏览器环境均 no-op,
deviceId/sessionId为空串。
下一步
- 事件的一生 —— 三 ID 在信封
context里的位置 - Consent 授权 —— 采集授权管理