Skip to content

身份与会话 ​

标识「设备 / 用户 / 会话」三种主体,并把 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 → FingerprintJScookie(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-Age 1 年,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 为空串。

下一步 ​

基于 MIT 协议发布(内部使用)