@clipto/reporter 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  declare class Reporter implements IReporter {
2
2
  private readonly opts;
3
3
  private readonly platform;
4
+ private readonly contextFn;
4
5
  private readonly flushInterval;
5
6
  private readonly maxBatchSize;
6
7
  private readonly maxBatchBytes;
@@ -11,9 +12,22 @@ declare class Reporter implements IReporter {
11
12
  private readonly flushOnExit;
12
13
  private readonly transportPromise;
13
14
  private readonly storagePromise;
14
- /** 会话 / 设备 id 兜底:enrich 未提供时按实例生成(对齐旧 SDK 格式) */
15
- private readonly sessionId;
16
- private readonly deviceId;
15
+ /** 匿名 id:首次生成后持久化(浏览器 localStorage / Node 进程内);对外只读访问见同名 getter */
16
+ private readonly _anonymousId;
17
+ /** 设备 / 安装实例 id:Web&PC 读取 google client id(_ga),否则生成并持久化;Node 进程内生成;对外只读访问见同名 getter */
18
+ private readonly _clientId;
19
+ /** 会话落盘时间戳(节流用);声明在 session 之前:session 初始化会触发强制落盘 */
20
+ private sessionSaveAt;
21
+ /** 会话共享读时间戳(节流用):多 tab 共享会话,活动时定期重读 localStorage 最新值 */
22
+ private sessionReadAt;
23
+ /** 会话:visitId 与最近活跃时间(30 分钟无活动轮换);localStorage 持久化,同源多 tab 共享 */
24
+ private session;
25
+ /** Node 端设备快照(init 时采集一次,避免每次入队动态加载 os) */
26
+ private nodeDevice;
27
+ /** 页面访问跟踪(browser + autoPageView 时启用) */
28
+ private pageTracker;
29
+ /** history 补丁的原始引用(dispose 时还原) */
30
+ private historyPatch;
17
31
  /** 待发队列(内存中的事实源,持久化是它的影子) */
18
32
  private readonly queue;
19
33
  /** init 完成后指向内置 / 注入的持久化实现 */
@@ -26,19 +40,39 @@ declare class Reporter implements IReporter {
26
40
  private disposed;
27
41
  private readonly onExit;
28
42
  private readonly onPageHide;
43
+ private readonly onPageShow;
29
44
  private readonly onVisibilityChange;
45
+ private readonly onPopState;
46
+ private readonly onStorage;
30
47
  constructor(options: IReporterOptions);
31
- track(name: string, properties?: Record<string, unknown>, options?: {
48
+ track(eventName: EventName, properties?: ReportNode, options?: {
32
49
  immediate?: boolean;
33
- timestamp?: number;
34
- category?: string;
35
50
  }): string;
36
51
  flush(): Promise<void>;
37
52
  get size(): number;
53
+ /** 匿名 id(只读):首次生成后持久化不变;供外部转化跟踪等场景读取 */
54
+ get anonymousId(): string;
55
+ /** 设备 / 安装实例 id(只读):Web&PC 与 google client id 一致;供外部转化跟踪等场景读取 */
56
+ get clientId(): string;
57
+ /** 当前会话 id(只读):会话轮换后返回新值;供外部转化跟踪等场景读取 */
58
+ get visitId(): string;
59
+ /** 当前登录账号 id(只读):实时调用 userId provider,未提供恒为 null */
60
+ get userId(): string | null;
38
61
  dispose(): Promise<void>;
39
- /** 异步初始化:创建内置持久化、恢复上次会话队列、挂载退出兜底 */
62
+ /** 异步初始化:创建内置持久化、采集 Node 设备快照、恢复上次会话队列、挂载退出兜底 */
40
63
  private init;
41
- /** 恢复队列入队:历史事件放队头保证先到先发,超限按溢出丢弃 */
64
+ /** 组装上报事件:身份字段由 SDK 填充,业务节点 = SDK 采集 + context 合并(接入方值优先) */
65
+ private buildEvent;
66
+ /** 进行中页面访问的 pageViewId(非访问期返回空) */
67
+ private pageSnapshot;
68
+ /** 会话活跃:track 调用与页面交互均触发;超时轮换 visitId 并结束旧页面访问 */
69
+ private touchSession;
70
+ /** 结束当前页面访问并上报 pageView 汇总事件(复用进入页面时的 visitId) */
71
+ private endPageVisit;
72
+ /** SPA 路由变化检测:pushState / replaceState 视为 routeChange,popstate 视为 back */
73
+ private patchHistory;
74
+ private restoreHistory;
75
+ /** 恢复队列入队:历史事件放队头保证先到先发,超限按溢出丢弃;过滤旧格式(无 eventId)事件 */
42
76
  private enqueueRestored;
43
77
  private enqueue;
44
78
  /** 攒批定时:延迟上报的实现,窗口内事件合并为一批 */
@@ -53,6 +87,22 @@ declare class Reporter implements IReporter {
53
87
  /** 持久化节流:窗口内多次变更只落盘一次,避免每次入队全量序列化 */
54
88
  private schedulePersist;
55
89
  private persistNow;
90
+ /** 匿名 id:localStorage 持久化,首次匿名访问生成后保留;Node 无持久化介质,进程内生成 */
91
+ private loadOrCreateAnonymousId;
92
+ /** clientId:Web&PC 与 google client id(_ga cookie)一致;否则本地生成并持久化 */
93
+ private loadOrCreateClientId;
94
+ /** 会话恢复:localStorage(同源多 tab 共享)中 30 分钟窗口内的会话复用,否则新建 */
95
+ private loadOrCreateSession;
96
+ /** 会话落盘(localStorage):节流 10s,避免滚动等高频活动频繁写;force 用于轮换 / 退出兜底 */
97
+ private saveSession;
98
+ /** 采纳共享会话:visitId 变化视为其他 tab 轮换了会话,结束本 tab 旧页面访问并开启新访问 */
99
+ private adoptSharedSession;
100
+ /** 活动时重读共享会话(兜底 storage 事件丢失):storage 事件只在其他 tab 触发 */
101
+ private refreshSessionFromShared;
102
+ private readPersistent;
103
+ private writePersistent;
104
+ /** 生成带前缀的实例 id(格式对齐旧 SDK session_ / device_) */
105
+ private newId;
56
106
  private log;
57
107
  /**
58
108
  * Node 进程退出事件句柄:Electron 渲染进程 typings 里 process 只声明了 'loaded'
@@ -64,9 +114,9 @@ declare class Reporter implements IReporter {
64
114
  }
65
115
 
66
116
  /** 浏览器发送:fetch(keepalive)走正常路径;sendBeacon 仅退出兜底(sendSync) */
67
- declare function createBrowserTransport(endpoint: string, headers?: Record<string, string>): IReportTransport;
117
+ declare function createBrowserTransport(endpoint: string, headers?: Record<string, string>, signer?: IReportSigner): IReportTransport;
68
118
  /** Node 发送:动态加载项目现有依赖 axios(仅在 Node 路径执行) */
69
- declare function createNodeTransport(endpoint: string, headers?: Record<string, string>): Promise<IReportTransport>;
119
+ declare function createNodeTransport(endpoint: string, headers?: Record<string, string>, signer?: IReportSigner): Promise<IReportTransport>;
70
120
  /** 降级实现:localStorage(容量有限,仅兜底) */
71
121
  declare function createLocalStorageStorage(): IReportStorage;
72
122
  /** 浏览器持久化:优先 Dexie(IndexedDB),不可用时降级 localStorage */
@@ -76,42 +126,70 @@ declare function createNodeStorage(persistDir: string): Promise<IReportStorage>;
76
126
 
77
127
  /** 事件被丢弃的原因 */
78
128
  type DropReason = 'max-attempts' | 'overflow' | 'invalid';
79
- /** 上报事件(track 入队后的形态;顶层字段对齐旧 SDK TrackedEvent) */
129
+ /** 签名函数:对序列化后的上报信封生成签名等附加键值( X-Clipto-Timestamp / X-Clipto-Signature),返回值作为请求头随包发送,服务端校验防止伪造数据污染;算法与密钥由接入方决定(如 WebCrypto HMAC);可同步或异步返回 */
130
+ type IReportSigner = (payload: string) => Record<string, string> | Promise<Record<string, string>>;
131
+ /** 业务节点:app / device / page / user / properties 的统一形态;具体字段与取值由接入方埋点方案约束,SDK 不感知语义、原样透传 */
132
+ type ReportNode = Record<string, unknown>;
133
+ /** 业务上下文:统一承载各业务节点;可传静态对象,或传函数在每次入队时实时计算(读取最新登录态 / 页面状态) */
134
+ interface IEventContext {
135
+ /** 应用信息:产品标识 / 环境 / 平台 / 版本 / 渠道归因等,由接入方配置 */
136
+ app?: ReportNode;
137
+ /** 设备信息:与 SDK 自动采集的硬件 / 系统 / 浏览器信息在同一节点内按字段合并,接入方提供的字段值优先;ip / country 等由服务端填充 */
138
+ device?: ReportNode;
139
+ /** 页面信息:与 SDK 自动采集的页面快照合并;pageId 等接入方定义字段在此提供 */
140
+ page?: ReportNode;
141
+ /** 用户信息:账号 / 订阅 / 权益 / 实验分组等,由接入方账号体系提供 */
142
+ user?: ReportNode;
143
+ /** 合并进每个事件 properties 的公共业务字段;track() 传入的 properties 优先级更高 */
144
+ properties?: ReportNode;
145
+ }
146
+ /** 事件名:接入方埋点方案定义的稳定事件名,同一行为跨 Web、iOS、Android、Mac、Windows 尽量同名;以下为 SDK 预置事件名,业务事件可继续扩展 */
147
+ type EventName = 'pageView' | 'elementExposure' | 'user_action' | (string & {});
148
+ /** 上报事件(网络信封结构:身份字段由 SDK 生成,业务节点由接入方 context 提供或 SDK 自动采集) */
80
149
  interface IReportEvent {
81
- /** 核心生成的唯一 id */
82
- id: string;
83
- /** 事件名(上报信封中序列化为 eventName,对齐旧 SDK) */
84
- name: string;
85
- /** 业务属性(baseProperties context 已合并) */
86
- properties: Record<string, unknown>;
87
- /** 事件时间(ms) */
150
+ /** SDK 自动生成:每条事件唯一 ID,用于网络重试与服务端幂等去重;同一事件重试时复用原值 */
151
+ eventId: string;
152
+ /** 埋点方案定义的稳定事件名,研发接入;同一行为跨端尽量同名 */
153
+ eventName: string;
154
+ /** 登录账号 id:账号系统提供、SDK 自动读取;未登录为 null,登录后携带,退出后恢复 null;接入方不手工填写 */
155
+ userId: string | null;
156
+ /** 匿名 id:SDK 自动生成,首次匿名访问 / 安装时生成,登录后继续保留;清浏览器数据、换浏览器或重装 App 后可能生成新值 */
157
+ anonymousId: string;
158
+ /** 会话 id:SDK 会话模块生成;页面切换保持不变,同源多 tab 共享一个会话(任一 tab 活动即刷新活跃时间);连续 30 分钟无活动、退出或切换账号后,下一次活动生成新值 */
159
+ visitId: string;
160
+ /** 设备 / 安装实例 id:SDK 自动生成并持久化;Web&PC 与 google client id 一致,iOS&Android 为 IDFV */
161
+ clientId: string;
162
+ /** 事件实际发生的毫秒时间戳,由客户端时钟产生(SDK 自动生成) */
88
163
  timestamp: number;
89
- /** 已尝试发送次数(SDK 内部字段,不上报) */
164
+ /** 应用信息(接入方 context 提供,节点始终存在,内容由埋点方案约束) */
165
+ app: ReportNode;
166
+ /** 设备信息(SDK 自动采集 + context 合并) */
167
+ device?: ReportNode;
168
+ /** 页面信息(SDK 自动采集 + context 合并) */
169
+ page?: ReportNode;
170
+ /** 用户信息(接入方 context 提供) */
171
+ user?: ReportNode;
172
+ /** 业务事件属性(context.properties 与 track properties 合并) */
173
+ properties?: ReportNode;
174
+ }
175
+ /** 队列内部事件:上报事件 + SDK 内部字段(序列化上报前剥离) */
176
+ interface IQueuedEvent extends IReportEvent {
177
+ /** 已尝试发送次数,达到 maxAttempts 即丢弃 */
90
178
  attempts: number;
91
- /** 会话 id(enrich 提供,否则核心按实例生成) */
92
- sessionId?: string;
93
- /** 用户 id(enrich 提供,否则 null) */
94
- userId?: string | null;
95
- /** 设备 id(enrich 提供,否则核心按实例生成) */
96
- deviceId?: string;
97
- /** 事件类别(enrich / track options 提供,默认 'custom') */
98
- category?: string;
99
- /** 采集上下文:页面 / 用户 / 浏览器 / 屏幕 / 视口(enrich 提供,否则空对象) */
100
- context?: Record<string, unknown>;
101
179
  }
102
180
  /** 发送适配器:如何把一批事件发出去(浏览器: sendBeacon → fetch;Node: https / axios) */
103
181
  interface IReportTransport {
104
- /** 发送一批事件;resolve 视为成功,reject 进入重试 */
182
+ /** 发送一批事件(纯上报结构,内部字段如 attempts 已由核心剥离);resolve 视为成功,reject 进入重试 */
105
183
  send(events: IReportEvent[]): Promise<void>;
106
184
  /** 退出兜底时的同步尽力发送(浏览器端为 navigator.sendBeacon);返回是否已受理 */
107
185
  sendSync?(events: IReportEvent[]): boolean;
108
186
  }
109
187
  /** 持久化适配器:待发队列跨页面刷新 / 进程重启存活(浏览器 IndexedDB / Node conf) */
110
188
  interface IReportStorage {
111
- /** 整体覆盖保存当前待发队列(实现需幂等;写入节流由核心负责) */
112
- save(events: IReportEvent[]): void | Promise<void>;
189
+ /** 整体覆盖保存当前待发队列(含重试计数,恢复后继续计次;实现需幂等;写入节流由核心负责) */
190
+ save(events: IQueuedEvent[]): void | Promise<void>;
113
191
  /** 读取上次保存的待发队列 */
114
- load(): IReportEvent[] | Promise<IReportEvent[]>;
192
+ load(): IQueuedEvent[] | Promise<IQueuedEvent[]>;
115
193
  /** 清空 */
116
194
  clear(): void | Promise<void>;
117
195
  }
@@ -119,6 +197,9 @@ interface IReportStorage {
119
197
  interface IReporterOptions {
120
198
  /** 上报端点,必填(浏览器与 Node 共用) */
121
199
  endpoint: string;
200
+ /** 业务上下文:统一提供 app / device / page / user / properties 业务节点;静态对象或实时函数(每次入队时调用);
201
+ * 与 SDK 自动采集的 device / page 在同一节点内按字段合并,接入方提供的字段值优先 */
202
+ context?: IEventContext | (() => IEventContext);
122
203
  /** 运行平台:显式声明,驱动内置发送 / 持久化实现;缺省自动探测(typeof window) */
123
204
  platform?: 'browser' | 'node';
124
205
  /** 攒批窗口(ms),即延迟上报的固定间隔;0 表示关闭批量,事件即时发送 */
@@ -135,22 +216,23 @@ interface IReporterOptions {
135
216
  maxAttempts?: number;
136
217
  /** 失败重发退避基础延迟(ms),按 2^n 指数增长 */
137
218
  backoffMs?: number;
138
- /** 合并进每个事件 properties 的公共业务字段(替代旧 SDK base_properties) */
139
- baseProperties?: Record<string, unknown>;
140
- /** 上下文采集函数,每次入队时实时调用并合并进 properties(替代旧 SDK 硬编码的页面/浏览器/设备信息) */
141
- context?: () => Record<string, unknown>;
142
- /** 逐事件顶层字段补充函数(sessionId/userId/deviceId/context),每次入队时实时调用,缺省值由核心兜底 */
143
- enrich?: () => Partial<Pick<IReportEvent, 'sessionId' | 'userId' | 'deviceId' | 'context'>>;
219
+ /** 登录账号 id 提供函数:每次入队时实时读取(由接入方账号体系提供);未提供时恒为 null */
220
+ userId?: () => string | null;
144
221
  /** 页面卸载 / 进程退出时兜底 flush(浏览器端走 sendBeacon) */
145
222
  flushOnExit?: boolean;
146
223
  /** 浏览器端:队列是否持久化到本地(优先 IndexedDB,不支持时降级 localStorage) */
147
224
  persist?: boolean;
148
225
  /** Node 端:持久化目录,设置后待发队列落盘(跨进程重启存活);不传仅内存 */
149
226
  persistDir?: string;
150
- /** 浏览器端:自动上报 page_view(初始化 + 页面重新可见) */
227
+ /** 浏览器端:自动管理页面访问并上报 pageView(进入页面建立访问上下文,离开页面时汇总上报) */
151
228
  autoPageView?: boolean;
152
229
  /** 自定义请求头(内置传输实现使用) */
153
230
  headers?: Record<string, string>;
231
+ /** 可选签名:每个批次发送前对序列化信封调用 signer,返回的键值对(通常为 X-Clipto-Timestamp / X-Clipto-Signature 等签名头)作为请求头随包发送,服务端校验防伪造/污染;
232
+ * payload 为信封 JSON 序列化(含 metadata.timestamp,可作签名时间戳);
233
+ * 仅内置 transport 生效(自定义 transport 自行处理);sendBeacon 不支持自定义请求头,
234
+ * 配置 signer 后退出兜底自动回退为带签名头的异步发送(keepalive fetch) */
235
+ signer?: IReportSigner;
154
236
  /** 打开调试日志 */
155
237
  debug?: boolean;
156
238
  /** 可选注入:发送适配器;不传按运行环境内置 */
@@ -166,17 +248,22 @@ interface IReporterOptions {
166
248
  }
167
249
  /** 公共接口 */
168
250
  interface IReporter {
169
- /** 上报一条事件并返回事件 id(dispose 后或非法事件返回空串);immediate 事件跳过攒批立即发送 */
170
- track(name: string, properties?: Record<string, unknown>, options?: {
251
+ /** 上报一条事件并返回 eventId(dispose 后或非法事件返回空串);immediate 事件跳过攒批立即发送;事件名见 EventName */
252
+ track(eventName: EventName, properties?: ReportNode, options?: {
171
253
  immediate?: boolean;
172
- timestamp?: number;
173
- /** 事件类别(顶层字段,默认 'custom') */
174
- category?: string;
175
254
  }): string;
176
255
  /** 立即冲刷队列(跳过退避等待),并等待在途批次完成 */
177
256
  flush(): Promise<void>;
178
257
  /** 队列中待发条数 */
179
258
  readonly size: number;
259
+ /** 当前登录账号 id:实时调用 userId provider;未提供时恒为 null */
260
+ readonly userId: string | null;
261
+ /** 匿名 id:首次生成后持久化不变;供外部转化跟踪等场景读取 */
262
+ readonly anonymousId: string;
263
+ /** 设备 / 安装实例 id:Web&PC 与 google client id 一致;供外部转化跟踪等场景读取 */
264
+ readonly clientId: string;
265
+ /** 当前会话 id:会话轮换后返回新值(同源多 tab 共享);供外部转化跟踪等场景读取 */
266
+ readonly visitId: string;
180
267
  /** 停止并兜底 flush(flushOnExit),之后拒绝新事件 */
181
268
  dispose(): Promise<void>;
182
269
  }
@@ -185,4 +272,4 @@ interface IReporterConstructor {
185
272
  new (options: IReporterOptions): IReporter;
186
273
  }
187
274
 
188
- export { type DropReason, type IReportEvent, type IReportStorage, type IReportTransport, type IReporter, type IReporterConstructor, type IReporterOptions, Reporter, createBrowserStorage, createBrowserTransport, createLocalStorageStorage, createNodeStorage, createNodeTransport };
275
+ export { type DropReason, type EventName, type IEventContext, type IQueuedEvent, type IReportEvent, type IReportSigner, type IReportStorage, type IReportTransport, type IReporter, type IReporterConstructor, type IReporterOptions, type ReportNode, Reporter, createBrowserStorage, createBrowserTransport, createLocalStorageStorage, createNodeStorage, createNodeTransport };