@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/README.md +86 -72
- package/dist/index.cjs +495 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +133 -46
- package/dist/index.d.ts +133 -46
- package/dist/index.js +495 -76
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/id.ts","../src/builtins.ts","../src/reporter.ts"],"sourcesContent":["/**\n * 事件 / 批次 id:对齐旧 SDK generateEventId 格式(evt_时间戳_随机串)\n */\nexport function generateEventId(): string {\n return `evt_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;\n}\n","// ============================================================\n// 内置 transport / storage —— 复用现有组件,不新造轮子:\n// 浏览器发送: 原生 fetch(keepalive)+ navigator.sendBeacon(退出兜底)\n// Node 发送: axios(项目现有依赖,动态加载,不进 renderer 包)\n// 浏览器持久化: Dexie(项目现有依赖,与 @renderer/common/idb 同款;\n// IndexedDB 不可用时降级 localStorage)\n// Node 持久化: conf(electron-store 的纯 Node 内核,Electron main 与\n// 纯 Node 服务端通吃,JSON 文件 + 内部原子写),整体覆盖保存\n// payload 沿用旧 SDK 的 { events, metadata } 信封,便于后端平滑过渡\n// ============================================================\nimport type { IReportEvent, IReportStorage, IReportTransport } from './index';\nimport { generateEventId } from './id';\n\nconst SDK_VERSION = '1.0.0'; // 对齐旧 SDK sdkVersion,便于后端平滑过渡\nconst DB_NAME = 'CliptoReportDB';\nconst STORE_NAME = 'clipto-reporting';\nconst QUEUE_KEY = 'pending';\nconst LS_KEY = 'clipto_report_pending_queue';\n\n/** 上报信封事件:顶层字段与旧 SDK TrackedEvent 完全对齐(name 序列化为 eventName) */\ninterface IEventPayloadEvent {\n id: string;\n eventName: string;\n timestamp: number;\n sessionId?: string;\n userId?: string | null;\n deviceId?: string;\n properties: Record<string, unknown>;\n category?: string;\n context?: Record<string, unknown>;\n}\n\n/** 上报信封:对齐旧 SDK { events, metadata } 结构 */\ninterface IEventPayload {\n events: IEventPayloadEvent[];\n metadata: {\n sdkVersion: string;\n timestamp: number;\n batchId: string;\n };\n}\n\nfunction buildPayload(events: IReportEvent[]): IEventPayload {\n return {\n // attempts 为 SDK 内部字段,不上报;name 输出为 eventName(对齐旧 SDK)\n events: events.map(\n ({\n id,\n name,\n timestamp,\n sessionId,\n userId,\n deviceId,\n properties,\n category,\n context,\n }) => ({\n id,\n eventName: name,\n timestamp,\n sessionId,\n userId,\n deviceId,\n properties,\n category,\n context,\n }),\n ),\n metadata: {\n sdkVersion: SDK_VERSION,\n timestamp: Date.now(),\n batchId: generateEventId(), // 与事件 id 同格式(对齐旧 SDK)\n },\n };\n}\n\n/** 浏览器发送:fetch(keepalive)走正常路径;sendBeacon 仅退出兜底(sendSync) */\nexport function createBrowserTransport(\n endpoint: string,\n headers?: Record<string, string>,\n): IReportTransport {\n const commonHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...(headers ?? {}),\n };\n return {\n async send(events) {\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: commonHeaders,\n body: JSON.stringify(buildPayload(events)),\n keepalive: true,\n });\n if (!response.ok) {\n throw new Error(`report send failed: HTTP ${response.status}`);\n }\n },\n sendSync(events) {\n if (\n typeof navigator === 'undefined' ||\n typeof navigator.sendBeacon !== 'function'\n ) {\n return false;\n }\n try {\n // sendBeacon 不支持自定义请求头,与旧 SDK 行为一致\n const blob = new Blob([JSON.stringify(buildPayload(events))], {\n type: 'application/json',\n });\n return navigator.sendBeacon(endpoint, blob);\n } catch {\n return false;\n }\n },\n };\n}\n\n/** Node 发送:动态加载项目现有依赖 axios(仅在 Node 路径执行) */\nexport async function createNodeTransport(\n endpoint: string,\n headers?: Record<string, string>,\n): Promise<IReportTransport> {\n const { default: axios } = await import('axios');\n return {\n async send(events) {\n await axios.post(endpoint, buildPayload(events), {\n headers,\n timeout: 10000,\n });\n },\n };\n}\n\n/** 降级实现:localStorage(容量有限,仅兜底) */\nexport function createLocalStorageStorage(): IReportStorage {\n return {\n save(events) {\n try {\n localStorage.setItem(LS_KEY, JSON.stringify(events));\n } catch {\n // 配额满或不可用:静默降级为不持久化\n }\n },\n load() {\n try {\n const text = localStorage.getItem(LS_KEY);\n return text ? (JSON.parse(text) as IReportEvent[]) : [];\n } catch {\n return [];\n }\n },\n clear() {\n try {\n localStorage.removeItem(LS_KEY);\n } catch {\n // ignore\n }\n },\n };\n}\n\n/** 浏览器持久化:优先 Dexie(IndexedDB),不可用时降级 localStorage */\nexport async function createBrowserStorage(): Promise<IReportStorage> {\n try {\n // 动态加载项目现有依赖 Dexie:仅在浏览器路径执行,不进 main 进程\n const { default: Dexie } = await import('dexie');\n const db = new Dexie(DB_NAME);\n db.version(1).stores({ queue: 'key' });\n await db.open(); // 提前探活,失败走 localStorage 降级\n\n const table = db.table<{ key: string; events: IReportEvent[] }, string>(\n 'queue',\n );\n return {\n async save(events) {\n await table.put({ key: QUEUE_KEY, events });\n },\n async load() {\n const row = await table.get(QUEUE_KEY);\n return row ? row.events : [];\n },\n async clear() {\n await table.delete(QUEUE_KEY);\n },\n };\n } catch {\n // IndexedDB 不可用(隐私模式 / 极老浏览器):降级 localStorage\n return createLocalStorageStorage();\n }\n}\n\n/** Node 持久化:conf(纯 Node 可用,JSON 文件 + 内部原子写),整体覆盖保存 */\nexport async function createNodeStorage(\n persistDir: string,\n): Promise<IReportStorage> {\n // 动态加载:仅在 Node 路径执行,renderer 永不进入该分支。\n // webpackIgnore:让打包器跳过 conf(依赖 Node 内置模块,web 目标无法打包),\n // 运行时由 Node 直接解析;SDK 作为 npm 包发布时 conf 随依赖安装。\n // 注意:打包后的 main 进程若启用该分支,需将 conf 加入\n // release/app/package.json dependencies(同 better-sqlite3 等原生依赖模式)。\n const { default: Conf } = await import(/* webpackIgnore: true */ 'conf');\n const store = new Conf<Record<string, IReportEvent[]>>({\n configName: STORE_NAME, // 落盘为 persistDir/clipto-reporting.json\n cwd: persistDir,\n });\n return {\n save(events) {\n store.set(QUEUE_KEY, events);\n },\n load() {\n return store.get(QUEUE_KEY) ?? [];\n },\n clear() {\n store.delete(QUEUE_KEY);\n },\n };\n}\n","// ============================================================\n// Reporter 核心实现 —— 平台无关,差异通过 transport / storage 注入\n// 复用现有组件:setTimeout(攒批 / 退避定时);事件 id 对齐旧 SDK\n// generateEventId 格式(见 id.ts);\n// 入队、攒批、重试、有界队列即 SDK 本身,项目内 better-queue / p-queue\n// 面向任务队列语义,与攒批上报不匹配,故核心手写保持精简。\n// ============================================================\nimport type {\n IReportEvent,\n IReporter,\n IReporterOptions,\n IReportStorage,\n IReportTransport,\n} from './index';\nimport {\n createBrowserStorage,\n createBrowserTransport,\n createNodeStorage,\n createNodeTransport,\n} from './builtins';\nimport { generateEventId } from './id';\n\nconst DEFAULT_FLUSH_INTERVAL = 2000; // 攒批窗口(ms),即延迟上报间隔\nconst DEFAULT_MAX_BATCH_SIZE = 10; // 单批最大条数,达到即刷\nconst DEFAULT_MAX_BATCH_BYTES = 64 * 1024; // 单批最大字节数,达到即刷\nconst DEFAULT_MAX_QUEUE_SIZE = 1000; // 队列最大条数\nconst DEFAULT_MAX_ATTEMPTS = 3; // 单事件最大发送尝试次数,超限丢弃\nconst DEFAULT_BACKOFF_MS = 1000; // 重试退避基数,按 2^n 指数增长\nconst STORAGE_WRITE_THROTTLE_MS = 500; // 持久化写入节流窗口\n\n/** Node 进程退出信号:兼容 Electron 渲染进程与 @types/node 对 process 的 typings 差异 */\ntype ExitSignal = 'beforeExit' | 'SIGINT' | 'SIGTERM';\n\ntype ExitProcessHandle = {\n once(event: ExitSignal, listener: () => void): unknown;\n off(event: ExitSignal, listener: () => void): unknown;\n};\n\nexport class Reporter implements IReporter {\n private readonly opts: IReporterOptions;\n\n private readonly platform: 'browser' | 'node';\n\n private readonly flushInterval: number;\n\n private readonly maxBatchSize: number;\n\n private readonly maxBatchBytes: number;\n\n private readonly maxQueueSize: number;\n\n private readonly overflowPolicy: NonNullable<\n IReporterOptions['overflowPolicy']\n >;\n\n private readonly maxAttempts: number;\n\n private readonly backoffMs: number;\n\n private readonly flushOnExit: boolean;\n\n private readonly transportPromise: Promise<IReportTransport>;\n\n private readonly storagePromise: Promise<IReportStorage | undefined>;\n\n /** 会话 / 设备 id 兜底:enrich 未提供时按实例生成(对齐旧 SDK 格式) */\n private readonly sessionId = `session_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;\n\n private readonly deviceId = `device_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;\n\n /** 待发队列(内存中的事实源,持久化是它的影子) */\n private readonly queue: IReportEvent[] = [];\n\n /** init 完成后指向内置 / 注入的持久化实现 */\n private storage: IReportStorage | undefined;\n\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n\n private retryTimer: ReturnType<typeof setTimeout> | null = null;\n\n private persistTimer: ReturnType<typeof setTimeout> | null = null;\n\n private persistDirty = false;\n\n private flushPromise: Promise<void> | null = null;\n\n private disposed = false;\n\n private readonly onExit = (): void => {\n this.fire(this.dispose());\n };\n\n private readonly onPageHide = (): void => {\n this.fire(this.dispose());\n };\n\n private readonly onVisibilityChange = (): void => {\n if (document.visibilityState === 'hidden') {\n // 页面切后台:冲刷一次(非卸载,保持接收新事件)\n this.fire(this.flush());\n } else if (this.opts.autoPageView) {\n this.track('page_view');\n }\n };\n\n constructor(options: IReporterOptions) {\n if (!options.endpoint) {\n throw new Error('Reporter: endpoint 为必填项');\n }\n this.opts = options;\n this.platform =\n options.platform ?? (typeof window !== 'undefined' ? 'browser' : 'node');\n this.flushInterval = options.flushInterval ?? DEFAULT_FLUSH_INTERVAL;\n this.maxBatchSize = options.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;\n this.maxBatchBytes = options.maxBatchBytes ?? DEFAULT_MAX_BATCH_BYTES;\n this.maxQueueSize = options.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;\n this.overflowPolicy = options.overflowPolicy ?? 'drop-oldest';\n this.maxAttempts = options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\n this.backoffMs = options.backoffMs ?? DEFAULT_BACKOFF_MS;\n this.flushOnExit = options.flushOnExit ?? true;\n\n // 注入优先;未注入按平台内置(复用现有组件,见 builtins.ts)\n if (options.transport) {\n this.transportPromise = Promise.resolve(options.transport);\n } else if (this.platform === 'browser') {\n this.transportPromise = Promise.resolve(\n createBrowserTransport(options.endpoint, options.headers),\n );\n } else {\n this.transportPromise = createNodeTransport(\n options.endpoint,\n options.headers,\n );\n }\n\n if (options.storage) {\n this.storagePromise = Promise.resolve(options.storage);\n } else if (options.persist === false) {\n this.storagePromise = Promise.resolve(undefined);\n } else if (this.platform === 'browser') {\n this.storagePromise = createBrowserStorage(); // Dexie → localStorage 降级\n } else if (options.persistDir) {\n this.storagePromise = createNodeStorage(options.persistDir);\n } else {\n this.storagePromise = Promise.resolve(undefined); // Node 未配置 persistDir:仅内存\n }\n\n this.fire(this.init());\n }\n\n track(\n name: string,\n properties?: Record<string, unknown>,\n options?: { immediate?: boolean; timestamp?: number; category?: string },\n ): string {\n if (this.disposed) {\n this.log('dispose 后忽略新事件:', name);\n return '';\n }\n\n // 顶层字段:enrich 实时补充,缺省由核心兜底(对齐旧 SDK TrackedEvent)\n const enriched = this.opts.enrich?.() ?? {};\n const event: IReportEvent = {\n id: generateEventId(),\n name,\n properties: {\n ...(this.opts.baseProperties ?? {}),\n ...(this.opts.context?.() ?? {}),\n ...(properties ?? {}),\n },\n timestamp: options?.timestamp ?? Date.now(),\n attempts: 0,\n sessionId: enriched.sessionId ?? this.sessionId,\n userId: enriched.userId ?? null,\n deviceId: enriched.deviceId ?? this.deviceId,\n category: options?.category ?? 'custom',\n context: enriched.context ?? {},\n };\n\n if (typeof name !== 'string' || name.length === 0) {\n this.opts.onDropped?.([event], 'invalid');\n return '';\n }\n\n if (options?.immediate) {\n // 立即发送:跳过攒批,失败仍走重试(重新入队)\n this.fire(this.sendBatch([event]));\n return event.id;\n }\n\n this.enqueue(event);\n if (this.flushInterval <= 0) {\n // 关闭攒批:事件即时发送\n this.fire(this.flush());\n } else {\n this.scheduleFlush();\n }\n return event.id;\n }\n\n flush(): Promise<void> {\n if (!this.flushPromise) {\n // 显式冲刷:跳过退避立即重试(退避只约束自动重试)\n if (this.retryTimer) {\n clearTimeout(this.retryTimer);\n this.retryTimer = null;\n }\n this.flushPromise = this.drain().finally(() => {\n this.flushPromise = null;\n });\n }\n return this.flushPromise;\n }\n\n get size(): number {\n return this.queue.length;\n }\n\n async dispose(): Promise<void> {\n if (this.disposed) return;\n this.disposed = true;\n\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n if (this.retryTimer) {\n clearTimeout(this.retryTimer);\n this.retryTimer = null;\n }\n if (this.persistTimer) {\n clearTimeout(this.persistTimer);\n this.persistTimer = null;\n }\n\n if (this.platform === 'browser') {\n window.removeEventListener('pagehide', this.onPageHide);\n document.removeEventListener('visibilitychange', this.onVisibilityChange);\n } else {\n const exitProcess = this.getExitProcess();\n exitProcess.off('beforeExit', this.onExit);\n exitProcess.off('SIGINT', this.onExit);\n exitProcess.off('SIGTERM', this.onExit);\n }\n\n const transport = await this.transportPromise;\n\n if (this.queue.length === 0) {\n // 队列已空:若持久化有脏标记,写空队列收敛,避免恢复已发送事件造成重复上报\n if (this.persistDirty) {\n await this.persistNow([]);\n }\n return;\n }\n\n if (this.flushOnExit) {\n const pending = this.queue.splice(0);\n // 优先同步尽力发送(sendBeacon,页面卸载场景 fetch 可能来不及)\n if (transport.sendSync?.(pending) ?? false) {\n this.opts.onFlushed?.(pending);\n await this.persistNow([]);\n return;\n }\n try {\n await transport.send(pending);\n this.opts.onFlushed?.(pending);\n await this.persistNow([]);\n } catch (error) {\n this.opts.onFailed?.(pending, error);\n this.queue.push(...pending); // 放回队列持久化,下次会话恢复重发\n await this.persistNow();\n }\n return;\n }\n\n // 退出兜底关闭:剩余队列仅持久化,下次启动恢复\n if (this.persistDirty) {\n await this.persistNow();\n }\n }\n\n /** 异步初始化:创建内置持久化、恢复上次会话队列、挂载退出兜底 */\n private async init(): Promise<void> {\n this.storage = await this.storagePromise;\n if (this.disposed) return;\n\n if (this.storage) {\n try {\n const restored = await this.storage.load();\n if (restored.length > 0) {\n this.enqueueRestored(restored);\n }\n } catch (error) {\n this.log('restore pending queue failed:', error);\n }\n }\n if (this.disposed) return;\n\n if (this.platform === 'browser') {\n if (this.flushOnExit) {\n window.addEventListener('pagehide', this.onPageHide);\n }\n if (this.flushOnExit || this.opts.autoPageView) {\n document.addEventListener('visibilitychange', this.onVisibilityChange);\n }\n if (this.opts.autoPageView) {\n this.track('page_view');\n }\n } else if (this.flushOnExit) {\n const exitProcess = this.getExitProcess();\n exitProcess.once('beforeExit', this.onExit);\n exitProcess.once('SIGINT', this.onExit);\n exitProcess.once('SIGTERM', this.onExit);\n }\n\n if (this.queue.length > 0) {\n this.scheduleFlush();\n this.schedulePersist();\n }\n }\n\n /** 恢复队列入队:历史事件放队头保证先到先发,超限按溢出丢弃 */\n private enqueueRestored(events: IReportEvent[]): void {\n let accepted = events;\n if (accepted.length > this.maxQueueSize) {\n this.opts.onDropped?.(accepted.slice(this.maxQueueSize), 'overflow');\n accepted = accepted.slice(0, this.maxQueueSize);\n }\n this.queue.unshift(...accepted);\n }\n\n private enqueue(event: IReportEvent): void {\n if (this.queue.length >= this.maxQueueSize) {\n if (this.overflowPolicy === 'drop-newest') {\n this.opts.onDropped?.([event], 'overflow');\n return;\n }\n if (this.overflowPolicy === 'drop-oldest') {\n this.opts.onDropped?.([this.queue.shift()!], 'overflow');\n } else {\n // flush-now:队列满时先冲刷腾位,不丢事件\n this.fire(this.flush());\n }\n }\n this.queue.push(event);\n this.schedulePersist();\n }\n\n /** 攒批定时:延迟上报的实现,窗口内事件合并为一批 */\n private scheduleFlush(): void {\n if (this.flushTimer || this.disposed || this.flushInterval <= 0) return;\n this.flushTimer = setTimeout(() => {\n this.flushTimer = null;\n if (this.queue.length > 0) {\n this.fire(this.flush());\n }\n }, this.flushInterval);\n }\n\n /** 串行清空队列;失败批次重新入队后由 retryTimer 再次触发 drain */\n private async drain(): Promise<void> {\n while (!this.disposed && !this.retryTimer && this.queue.length > 0) {\n // eslint-disable-next-line no-await-in-loop\n await this.sendBatch(this.takeBatch());\n }\n }\n\n /** 取一批:受条数 / 字节数限制;首条必取,避免单事件超 maxBatchBytes 时队列卡死 */\n private takeBatch(): IReportEvent[] {\n const batch: IReportEvent[] = [];\n let bytes = 0;\n while (this.queue.length > 0 && batch.length < this.maxBatchSize) {\n const size = JSON.stringify(this.queue[0]).length;\n if (batch.length > 0 && bytes + size > this.maxBatchBytes) break;\n batch.push(this.queue.shift()!);\n bytes += size;\n }\n return batch;\n }\n\n private async sendBatch(batch: IReportEvent[]): Promise<void> {\n const transport = await this.transportPromise;\n try {\n await transport.send(batch);\n this.opts.onFlushed?.(batch);\n this.schedulePersist(); // 该批已移出队列,持久化剩余\n } catch (error) {\n this.opts.onFailed?.(batch, error);\n this.log('send batch failed:', error);\n\n const updated = batch.map((event) => ({\n ...event,\n attempts: event.attempts + 1,\n }));\n const retryable = updated.filter(\n (event) => event.attempts < this.maxAttempts,\n );\n const dead = updated.filter(\n (event) => event.attempts >= this.maxAttempts,\n );\n if (dead.length > 0) {\n this.opts.onDropped?.(dead, 'max-attempts');\n }\n if (retryable.length > 0) {\n this.queue.unshift(...retryable); // 保持原顺序放回队头\n this.schedulePersist();\n this.scheduleRetry(retryable);\n }\n }\n }\n\n /** 失败退避:按批次内最大 attempts 计 2^n 指数增长,避免失败批次频繁重试 */\n private scheduleRetry(retryable: IReportEvent[]): void {\n if (this.retryTimer) return;\n const maxAttemptsInBatch = Math.max(\n ...retryable.map((event) => event.attempts),\n );\n const delay = this.backoffMs * 2 ** (maxAttemptsInBatch - 1);\n this.retryTimer = setTimeout(() => {\n this.retryTimer = null;\n this.fire(this.flush());\n }, delay);\n }\n\n /** 持久化节流:窗口内多次变更只落盘一次,避免每次入队全量序列化 */\n private schedulePersist(): void {\n if (!this.storage) return;\n this.persistDirty = true;\n if (this.persistTimer) return;\n this.persistTimer = setTimeout(() => {\n this.persistTimer = null;\n if (!this.persistDirty) return;\n this.persistDirty = false;\n this.fire(this.persistNow());\n }, STORAGE_WRITE_THROTTLE_MS);\n }\n\n private async persistNow(events: IReportEvent[] = this.queue): Promise<void> {\n if (!this.storage) return;\n try {\n await this.storage.save(events);\n } catch (error) {\n this.log('persist queue failed:', error);\n }\n }\n\n private log(...args: unknown[]): void {\n if (this.opts.debug) {\n // eslint-disable-next-line no-console\n console.log('[reporter]', ...args);\n }\n }\n\n /**\n * Node 进程退出事件句柄:Electron 渲染进程 typings 里 process 只声明了 'loaded'\n * 事件,与 @types/node 不一致,显式收窄到本 SDK 用到的信号,兼容两端编译器\n */\n private getExitProcess(): ExitProcessHandle {\n return process as unknown as ExitProcessHandle;\n }\n\n /** 安全触发异步任务:吞掉 rejection,避免未处理异常 */\n private fire(promise: Promise<unknown>): void {\n promise.catch((error) => {\n this.log('async task failed:', error);\n });\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/id.ts","../src/builtins.ts","../src/collect.ts","../src/page.ts","../src/reporter.ts"],"sourcesContent":["/**\n * 事件 / 批次 id:对齐旧 SDK generateEventId 格式(evt_时间戳_随机串)\n */\nexport function generateEventId(): string {\n return `evt_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;\n}\n","// ============================================================\n// 内置 transport / storage —— 复用现有组件,不新造轮子:\n// 浏览器发送: 原生 fetch(keepalive)+ navigator.sendBeacon(退出兜底)\n// Node 发送: axios(项目现有依赖,动态加载,不进 renderer 包)\n// 浏览器持久化: Dexie(项目现有依赖,与 @renderer/common/idb 同款;\n// IndexedDB 不可用时降级 localStorage)\n// Node 持久化: conf(electron-store 的纯 Node 内核,Electron main 与\n// 纯 Node 服务端通吃,JSON 文件 + 内部原子写),整体覆盖保存\n// payload 沿用旧 SDK 的 { events, metadata } 信封,便于后端平滑过渡;\n// events 为上报事件原样透传(SDK 内部字段已在核心剥离)。\n// ============================================================\nimport type {\n IQueuedEvent,\n IReportEvent,\n IReportSigner,\n IReportStorage,\n IReportTransport,\n} from './index';\nimport { generateEventId } from './id';\n\nconst SDK_VERSION = '1.0'; // 对齐旧 SDK sdkVersion,便于后端平滑过渡\nconst DB_NAME = 'CliptoReportDB';\nconst STORE_NAME = 'clipto-reporting';\nconst QUEUE_KEY = 'pending';\nconst LS_KEY = 'clipto_report_pending_queue';\n\n/** 上报信封:对齐旧 SDK { events, metadata } 结构;签名由 signer 生成后以请求头随包发送 */\ninterface IEventPayload {\n events: IReportEvent[];\n metadata: {\n type: string;\n sdkVersion: string;\n timestamp: number;\n batchId: string;\n };\n}\n\nfunction buildPayload(events: IReportEvent[]): IEventPayload {\n return {\n events,\n metadata: {\n type:\"tracker_event\" , \n sdkVersion: SDK_VERSION,\n timestamp: Date.now(),\n batchId: generateEventId(), // 与事件 id 同格式(对齐旧 SDK)\n },\n };\n}\n\n/** 生成签名头:signer 返回的键值对作为请求头随包发送(如 X-Clipto-Timestamp / X-Clipto-Signature);签名失败随发送失败进入重试 */\nasync function signHeaders(\n payload: IEventPayload,\n signer?: IReportSigner,\n): Promise<Record<string, string>> {\n if (!signer) return {};\n return signer(JSON.stringify(payload));\n}\n\n/** 浏览器发送:fetch(keepalive)走正常路径;sendBeacon 仅退出兜底(sendSync) */\nexport function createBrowserTransport(\n endpoint: string,\n headers?: Record<string, string>,\n signer?: IReportSigner,\n): IReportTransport {\n const commonHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...(headers ?? {}),\n };\n return {\n async send(events) {\n const payload = buildPayload(events);\n const requestHeaders = {\n ...commonHeaders,\n ...(await signHeaders(payload, signer)),\n };\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: requestHeaders,\n body: JSON.stringify(payload),\n keepalive: true,\n });\n if (!response.ok) {\n throw new Error(`report send failed: HTTP ${response.status}`);\n }\n },\n sendSync(events) {\n if (\n typeof navigator === 'undefined' ||\n typeof navigator.sendBeacon !== 'function' ||\n signer\n ) {\n // sendBeacon 不支持自定义请求头:配置 signer 时放弃 beacon,\n // 由调用方回退带签名头的异步发送(keepalive fetch)\n return false;\n }\n try {\n const blob = new Blob([JSON.stringify(buildPayload(events))], {\n type: 'application/json',\n });\n return navigator.sendBeacon(endpoint, blob);\n } catch {\n return false;\n }\n },\n };\n}\n\n/** Node 发送:动态加载项目现有依赖 axios(仅在 Node 路径执行) */\nexport async function createNodeTransport(\n endpoint: string,\n headers?: Record<string, string>,\n signer?: IReportSigner,\n): Promise<IReportTransport> {\n const { default: axios } = await import('axios');\n return {\n async send(events) {\n const payload = buildPayload(events);\n await axios.post(endpoint, payload, {\n headers: {\n ...(headers ?? {}),\n ...(await signHeaders(payload, signer)),\n },\n timeout: 10000,\n });\n },\n };\n}\n\n/** 降级实现:localStorage(容量有限,仅兜底) */\nexport function createLocalStorageStorage(): IReportStorage {\n return {\n save(events) {\n try {\n localStorage.setItem(LS_KEY, JSON.stringify(events));\n } catch {\n // 配额满或不可用:静默降级为不持久化\n }\n },\n load() {\n try {\n const text = localStorage.getItem(LS_KEY);\n return text ? (JSON.parse(text) as IQueuedEvent[]) : [];\n } catch {\n return [];\n }\n },\n clear() {\n try {\n localStorage.removeItem(LS_KEY);\n } catch {\n // ignore\n }\n },\n };\n}\n\n/** 浏览器持久化:优先 Dexie(IndexedDB),不可用时降级 localStorage */\nexport async function createBrowserStorage(): Promise<IReportStorage> {\n try {\n // 动态加载项目现有依赖 Dexie:仅在浏览器路径执行,不进 main 进程\n const { default: Dexie } = await import('dexie');\n const db = new Dexie(DB_NAME);\n db.version(1).stores({ queue: 'key' });\n await db.open(); // 提前探活,失败走 localStorage 降级\n\n const table = db.table<{ key: string; events: IQueuedEvent[] }, string>(\n 'queue',\n );\n return {\n async save(events) {\n await table.put({ key: QUEUE_KEY, events });\n },\n async load() {\n const row = await table.get(QUEUE_KEY);\n return row ? row.events : [];\n },\n async clear() {\n await table.delete(QUEUE_KEY);\n },\n };\n } catch {\n // IndexedDB 不可用(隐私模式 / 极老浏览器):降级 localStorage\n return createLocalStorageStorage();\n }\n}\n\n/** Node 持久化:conf(纯 Node 可用,JSON 文件 + 内部原子写),整体覆盖保存 */\nexport async function createNodeStorage(\n persistDir: string,\n): Promise<IReportStorage> {\n // 动态加载:仅在 Node 路径执行,renderer 永不进入该分支。\n // webpackIgnore:让打包器跳过 conf(依赖 Node 内置模块,web 目标无法打包),\n // 运行时由 Node 直接解析;SDK 作为 npm 包发布时 conf 随依赖安装。\n // 注意:打包后的 main 进程若启用该分支,需将 conf 加入\n // release/app/package.json dependencies(同 better-sqlite3 等原生依赖模式)。\n const { default: Conf } = await import(/* webpackIgnore: true */ 'conf');\n const store = new Conf<Record<string, IQueuedEvent[]>>({\n configName: STORE_NAME, // 落盘为 persistDir/clipto-reporting.json\n cwd: persistDir,\n });\n return {\n save(events) {\n store.set(QUEUE_KEY, events);\n },\n load() {\n return store.get(QUEUE_KEY) ?? [];\n },\n clear() {\n store.delete(QUEUE_KEY);\n },\n };\n}\n","// ============================================================\n// 内置采集:设备 / 页面快照。\n// 只采集通用、平台可得的字段,不做业务口径约束;采集结果与接入方\n// context 在同一节点内按字段合并,接入方提供的字段值优先。\n// ============================================================\nimport { UAParser } from 'ua-parser-js';\nimport type { ReportNode } from './index';\n\ninterface IUserAgentInfo {\n browserName?: string;\n browserVersion?: string;\n osName?: string;\n osVersion?: string;\n}\n\n/**\n * 浏览器 UA 解析:复用社区成熟的 ua-parser-js(活跃维护),不自行维护 UA 规则。\n * 仅做输出归一化,字段命名与取值口径对齐 SDK 既有格式:\n * iOS Safari 解出 \"Mobile Safari\" 归一为 \"Safari\",移动端 Chrome 解出\n * \"Mobile Chrome\" 归一为 \"Chrome\",\"Mac OS\" 归一为 \"macOS\"。\n * (库同时解出设备型号 / 厂商 / 类型,规范确认 device 字段口径后可直接使用)\n */\nfunction parseUserAgent(ua: string): IUserAgentInfo {\n const parsed = new UAParser(ua);\n const browser = parsed.getBrowser();\n const os = parsed.getOS();\n const info: IUserAgentInfo = {};\n if (browser.name) {\n info.browserName =\n browser.name === 'Mobile Safari'\n ? 'Safari'\n : browser.name === 'Mobile Chrome'\n ? 'Chrome'\n : browser.name;\n }\n if (browser.version) info.browserVersion = browser.version;\n if (os.name) info.osName = os.name === 'Mac OS' ? 'macOS' : os.name;\n if (os.version) info.osVersion = os.version;\n return info;\n}\n\n/** 浏览器端设备快照:同步读取,每次入队时合并(语言等字段保持实时) */\nexport function collectBrowserDevice(): ReportNode {\n if (typeof navigator === 'undefined') return {};\n const { browserName, browserVersion, osName, osVersion } = parseUserAgent(\n navigator.userAgent || '',\n );\n const device: ReportNode = {};\n if (browserName) device.browserName = browserName;\n if (browserVersion) device.browserVersion = browserVersion;\n if (osName) device.osName = osName;\n if (osVersion) device.osVersion = osVersion;\n // 平台:优先 UA Client Hints,退化 navigator.platform(已废弃但仍可读)\n const uaData = (navigator as Navigator & {\n userAgentData?: { platform?: string };\n }).userAgentData;\n if (uaData?.platform) device.platform = uaData.platform;\n else if (navigator.platform) device.platform = navigator.platform;\n if (navigator.language) device.language = navigator.language;\n return device;\n}\n\n/** Node 端设备快照:动态加载 os 模块(不进浏览器包),init 时采集一次缓存 */\nexport async function collectNodeDevice(): Promise<ReportNode> {\n try {\n const os = await import('os');\n const device: ReportNode = {\n platform: os.platform(),\n osVersion: os.release(),\n arch: os.arch(),\n memSize: Math.round(os.totalmem() / 1024 ** 3), // 统一 GB\n };\n const type = os.type();\n device.osName =\n type === 'Darwin' ? 'macOS' : type === 'Windows_NT' ? 'Windows' : type;\n const cpu = os.cpus()[0]?.model;\n if (cpu) device.cpu = cpu;\n return device;\n } catch {\n return {}; // os 模块不可用:跳过采集\n }\n}\n\n/** 页面快照:事件发生时的地址 / 标题 / 来源(browser 专用) */\nexport function collectPageSnapshot(): ReportNode {\n if (typeof document === 'undefined') return {};\n const page: ReportNode = {};\n if (location.href) page.url = location.href;\n if (document.title) page.title = document.title;\n if (document.referrer) page.referrer = document.referrer;\n return page;\n}\n","// ============================================================\n// 页面访问跟踪(browser 专用,autoPageView 开启时启用):\n// 进入页面建立访问上下文(生成 pageViewId,记录进入时快照),累计\n// durationMs / activeMs / scrollCount / maxScrollDepthPct;\n// 离开时返回本次访问的汇总 page 节点,供 pageView 事件使用。\n// 规则:连续 60s 无操作暂停累计活跃时长;连续滚动间隔 ≤500ms\n// 计为一次手势;深度按可见区底端占文档总高百分比计,范围 0–100。\n// ============================================================\nimport type { ReportNode } from './index';\nimport { collectPageSnapshot } from './collect';\nimport { generateEventId } from './id';\n\nexport type PageEndReason =\n | 'routeChange'\n | 'back'\n | 'close'\n | 'background'\n | 'sessionTimeout'\n | 'unknown';\n\nconst SCROLL_GESTURE_GAP_MS = 500; // 连续滚动间隔 ≤500ms 计为一次手势\nconst IDLE_PAUSE_MS = 60_000; // 连续无操作 60s 暂停累计活跃时长\nconst METRIC_TICK_MS = 1000; // 汇总指标采样周期\n\ninterface IPageVisit {\n pageViewId: string;\n enteredAt: number;\n /** 进入页面时的 url / title / referrer 快照(pageView 汇总事件复用) */\n snapshot: ReportNode;\n activeMs: number;\n scrollCount: number;\n maxScrollDepthPct: number;\n lastActivityAt: number;\n lastScrollAt: number;\n}\n\nexport interface IPageTracker {\n /** 是否有进行中的页面访问 */\n readonly active: boolean;\n /** 进入页面:生成新 pageViewId 并开始累计(进行中时忽略) */\n startVisit(): void;\n /** 离开页面:结束访问并返回汇总 page 节点(无进行中访问时返回 undefined) */\n endVisit(reason: PageEndReason): ReportNode | undefined;\n /** 进行中访问的 pageViewId */\n pageViewId(): string | undefined;\n /** 清理监听与定时器(不结束访问,调用方先 endVisit) */\n dispose(): void;\n}\n\n/** 创建页面访问跟踪器;onActivity 在检测到用户操作时回调(用于刷新会话活跃时间) */\nexport function createPageTracker(onActivity: () => void): IPageTracker {\n let visit: IPageVisit | null = null;\n let interval: ReturnType<typeof setInterval> | null = null;\n\n const touch = (): void => {\n if (!visit) return;\n visit.lastActivityAt = Date.now();\n onActivity();\n };\n\n const updateMaxDepth = (): void => {\n if (!visit) return;\n const doc = document.documentElement;\n if (!doc) return;\n const total = Math.max(doc.scrollHeight, 1);\n const depth = Math.round(\n ((window.scrollY + window.innerHeight) / total) * 100,\n );\n visit.maxScrollDepthPct = Math.max(\n visit.maxScrollDepthPct,\n Math.min(depth, 100),\n );\n };\n\n const onScroll = (): void => {\n if (!visit) return;\n const now = Date.now();\n if (now - visit.lastScrollAt > SCROLL_GESTURE_GAP_MS) {\n visit.scrollCount += 1;\n }\n visit.lastScrollAt = now;\n touch();\n updateMaxDepth();\n };\n\n const onInput = (): void => touch();\n\n const tick = (): void => {\n if (!visit) return;\n if (\n document.visibilityState === 'visible' &&\n Date.now() - visit.lastActivityAt <= IDLE_PAUSE_MS\n ) {\n visit.activeMs += METRIC_TICK_MS;\n }\n updateMaxDepth();\n };\n\n const removeListeners = (): void => {\n window.removeEventListener('scroll', onScroll);\n window.removeEventListener('pointerdown', onInput, { capture: true });\n window.removeEventListener('keydown', onInput, { capture: true });\n if (interval) {\n clearInterval(interval);\n interval = null;\n }\n };\n\n const startVisit = (): void => {\n if (visit) return;\n const now = Date.now();\n visit = {\n pageViewId: `pv_${generateEventId()}`,\n enteredAt: now,\n snapshot: collectPageSnapshot(),\n activeMs: 0,\n scrollCount: 0,\n maxScrollDepthPct: 0,\n lastActivityAt: now,\n lastScrollAt: 0,\n };\n window.addEventListener('scroll', onScroll, { passive: true });\n window.addEventListener('pointerdown', onInput, {\n capture: true,\n passive: true,\n });\n window.addEventListener('keydown', onInput, { capture: true, passive: true });\n interval = setInterval(tick, METRIC_TICK_MS);\n };\n\n const endVisit = (reason: PageEndReason): ReportNode | undefined => {\n if (!visit) return undefined;\n removeListeners();\n const finished = visit;\n visit = null;\n // SPA 常在 effect 中异步设置标题,进入时快照可能滞后:离开时用当前\n // document.title 刷新(url / referrer 保持进入时快照——routeChange /\n // back 的离开瞬间地址已是新路由,而标题仍是旧页面稳定后的值,语义正确)\n if (document.title) {\n finished.snapshot.title = document.title;\n }\n return {\n ...finished.snapshot,\n pageViewId: finished.pageViewId,\n durationMs: Date.now() - finished.enteredAt,\n activeMs: finished.activeMs,\n scrollCount: finished.scrollCount,\n maxScrollDepthPct: finished.maxScrollDepthPct,\n endReason: reason,\n };\n };\n\n return {\n get active(): boolean {\n return visit !== null;\n },\n startVisit,\n endVisit,\n pageViewId: (): string | undefined => visit?.pageViewId,\n dispose: removeListeners,\n };\n}\n","// ============================================================\n// Reporter 核心实现 —— 平台无关,差异通过 transport / storage 注入;\n// 业务数据(app / device / page / user / properties)通过统一 context\n// 注入,SDK 只负责自身行为:身份字段生成与持久化、会话轮换、\n// 页面访问跟踪(pageView 汇总)、入队、攒批、重试退避、有界队列。\n// 复用现有组件:setTimeout(攒批 / 退避定时);事件 id 对齐旧 SDK\n// generateEventId 格式(见 id.ts)。\n// ============================================================\nimport type {\n EventName,\n IEventContext,\n IReportEvent,\n IReporter,\n IReporterOptions,\n IReportStorage,\n IReportTransport,\n IQueuedEvent,\n ReportNode,\n} from './index';\nimport {\n createBrowserStorage,\n createBrowserTransport,\n createNodeStorage,\n createNodeTransport,\n} from './builtins';\nimport {\n collectBrowserDevice,\n collectNodeDevice,\n collectPageSnapshot,\n} from './collect';\nimport {\n createPageTracker,\n type IPageTracker,\n type PageEndReason,\n} from './page';\nimport { generateEventId } from './id';\n\nconst DEFAULT_FLUSH_INTERVAL = 2000; // 攒批窗口(ms),即延迟上报间隔\nconst DEFAULT_MAX_BATCH_SIZE = 10; // 单批最大条数,达到即刷\nconst DEFAULT_MAX_BATCH_BYTES = 64 * 1024; // 单批最大字节数,达到即刷\nconst DEFAULT_MAX_QUEUE_SIZE = 1000; // 队列最大条数\nconst DEFAULT_MAX_ATTEMPTS = 3; // 单事件最大发送尝试次数,超限丢弃\nconst DEFAULT_BACKOFF_MS = 1000; // 重试退避基数,按 2^n 指数增长\nconst STORAGE_WRITE_THROTTLE_MS = 500; // 持久化写入节流窗口\nconst VISIT_IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 会话有效期:连续无活动 30 分钟\nconst SESSION_SAVE_THROTTLE_MS = 10_000; // 会话活跃时间落盘节流,避免滚动等高频活动频繁写\n\n/** 浏览器端身份持久化键 */\nconst ANONYMOUS_ID_KEY = 'clipto_report_anonymous_id';\nconst CLIENT_ID_KEY = 'clipto_report_client_id';\nconst SESSION_KEY = 'clipto_report_session';\n\n/** Node 进程退出信号:兼容 Electron 渲染进程与 @types/node 对 process 的 typings 差异 */\ntype ExitSignal = 'beforeExit' | 'SIGINT' | 'SIGTERM';\n\ntype ExitProcessHandle = {\n once(event: ExitSignal, listener: () => void): unknown;\n off(event: ExitSignal, listener: () => void): unknown;\n};\n\n/** 会话状态:visitId + 最近活跃时间 */\ninterface ISession {\n visitId: string;\n lastActivityMs: number;\n}\n\n/** context 归一化:静态对象包装为函数,每次入队时实时读取 */\nfunction normalizeContext(\n context: IReporterOptions['context'],\n): () => IEventContext {\n if (typeof context === 'function') return context;\n return () => context ?? {};\n}\n\n/** 发送前剥离 SDK 内部字段,transport 收到纯上报结构 */\nfunction stripAttempts(events: IQueuedEvent[]): IReportEvent[] {\n return events.map((event) => {\n const { attempts: _attempts, ...rest } = event;\n return rest;\n });\n}\n\nexport class Reporter implements IReporter {\n private readonly opts: IReporterOptions;\n\n private readonly platform: 'browser' | 'node';\n\n private readonly contextFn: () => IEventContext;\n\n private readonly flushInterval: number;\n\n private readonly maxBatchSize: number;\n\n private readonly maxBatchBytes: number;\n\n private readonly maxQueueSize: number;\n\n private readonly overflowPolicy: NonNullable<\n IReporterOptions['overflowPolicy']\n >;\n\n private readonly maxAttempts: number;\n\n private readonly backoffMs: number;\n\n private readonly flushOnExit: boolean;\n\n private readonly transportPromise: Promise<IReportTransport>;\n\n private readonly storagePromise: Promise<IReportStorage | undefined>;\n\n /** 匿名 id:首次生成后持久化(浏览器 localStorage / Node 进程内);对外只读访问见同名 getter */\n private readonly _anonymousId: string;\n\n /** 设备 / 安装实例 id:Web&PC 读取 google client id(_ga),否则生成并持久化;Node 进程内生成;对外只读访问见同名 getter */\n private readonly _clientId: string;\n\n /** 会话落盘时间戳(节流用);声明在 session 之前:session 初始化会触发强制落盘 */\n private sessionSaveAt = 0;\n\n /** 会话共享读时间戳(节流用):多 tab 共享会话,活动时定期重读 localStorage 最新值 */\n private sessionReadAt = 0;\n\n /** 会话:visitId 与最近活跃时间(30 分钟无活动轮换);localStorage 持久化,同源多 tab 共享 */\n private session: ISession;\n\n /** Node 端设备快照(init 时采集一次,避免每次入队动态加载 os) */\n private nodeDevice: ReportNode = {};\n\n /** 页面访问跟踪(browser + autoPageView 时启用) */\n private pageTracker: IPageTracker | null = null;\n\n /** history 补丁的原始引用(dispose 时还原) */\n private historyPatch: {\n pushState: typeof history.pushState;\n replaceState: typeof history.replaceState;\n } | null = null;\n\n /** 待发队列(内存中的事实源,持久化是它的影子) */\n private readonly queue: IQueuedEvent[] = [];\n\n /** init 完成后指向内置 / 注入的持久化实现 */\n private storage: IReportStorage | undefined;\n\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n\n private retryTimer: ReturnType<typeof setTimeout> | null = null;\n\n private persistTimer: ReturnType<typeof setTimeout> | null = null;\n\n private persistDirty = false;\n\n private flushPromise: Promise<void> | null = null;\n\n private disposed = false;\n\n private readonly onExit = (): void => {\n this.fire(this.dispose());\n };\n\n private readonly onPageHide = (): void => {\n // 关闭 / 刷新页面:结束本次访问(close),随后 dispose 兜底 flush(sendBeacon)\n this.endPageVisit(this.session.visitId, 'close');\n this.fire(this.dispose());\n };\n\n private readonly onPageShow = (): void => {\n // bfcache 恢复:重新可见时开启新的页面访问\n if (document.visibilityState === 'visible') {\n this.pageTracker?.startVisit();\n }\n };\n\n private readonly onVisibilityChange = (): void => {\n if (document.visibilityState === 'hidden') {\n // 页面切后台:结束本次访问(background),冲刷一次(非卸载,保持接收新事件)\n this.endPageVisit(this.session.visitId, 'background');\n this.fire(this.flush());\n } else {\n this.pageTracker?.startVisit();\n }\n };\n\n private readonly onPopState = (): void => {\n this.endPageVisit(this.session.visitId, 'back');\n this.pageTracker?.startVisit();\n };\n\n private readonly onStorage = (event: StorageEvent): void => {\n if (event.key !== SESSION_KEY || !event.newValue) return;\n // 多 tab 共享会话:其他 tab 的活动 / 轮换实时同步到本 tab\n try {\n this.adoptSharedSession(JSON.parse(event.newValue) as ISession);\n } catch {\n // 解析失败:忽略,下次活动时重读兜底\n }\n };\n\n constructor(options: IReporterOptions) {\n if (!options.endpoint) {\n throw new Error('Reporter: endpoint 为必填项');\n }\n this.opts = options;\n this.platform =\n options.platform ?? (typeof window !== 'undefined' ? 'browser' : 'node');\n this.contextFn = normalizeContext(options.context);\n this.flushInterval = options.flushInterval ?? DEFAULT_FLUSH_INTERVAL;\n this.maxBatchSize = options.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;\n this.maxBatchBytes = options.maxBatchBytes ?? DEFAULT_MAX_BATCH_BYTES;\n this.maxQueueSize = options.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;\n this.overflowPolicy = options.overflowPolicy ?? 'drop-oldest';\n this.maxAttempts = options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\n this.backoffMs = options.backoffMs ?? DEFAULT_BACKOFF_MS;\n this.flushOnExit = options.flushOnExit ?? true;\n\n this._anonymousId = this.loadOrCreateAnonymousId();\n this._clientId = this.loadOrCreateClientId();\n this.session = this.loadOrCreateSession();\n\n // 注入优先;未注入按平台内置(复用现有组件,见 builtins.ts)\n if (options.transport) {\n this.transportPromise = Promise.resolve(options.transport);\n } else if (this.platform === 'browser') {\n this.transportPromise = Promise.resolve(\n createBrowserTransport(options.endpoint, options.headers, options.signer),\n );\n } else {\n this.transportPromise = createNodeTransport(\n options.endpoint,\n options.headers,\n options.signer,\n );\n }\n\n if (options.storage) {\n this.storagePromise = Promise.resolve(options.storage);\n } else if (options.persist === false) {\n this.storagePromise = Promise.resolve(undefined);\n } else if (this.platform === 'browser') {\n this.storagePromise = createBrowserStorage(); // Dexie → localStorage 降级\n } else if (options.persistDir) {\n this.storagePromise = createNodeStorage(options.persistDir);\n } else {\n this.storagePromise = Promise.resolve(undefined); // Node 未配置 persistDir:仅内存\n }\n\n this.fire(this.init());\n }\n\n track(\n eventName: EventName,\n properties?: ReportNode,\n options?: { immediate?: boolean },\n ): string {\n if (this.disposed) {\n this.log('dispose 后忽略新事件:', eventName);\n return '';\n }\n\n if (typeof eventName !== 'string' || eventName.length === 0) {\n this.opts.onDropped?.([this.buildEvent('', properties)], 'invalid');\n return '';\n }\n\n // 活动刷新:会话超时则轮换 visitId,并结束旧页面访问(sessionTimeout)\n this.touchSession();\n\n const event = this.buildEvent(eventName, properties);\n\n if (options?.immediate) {\n // 立即发送:跳过攒批,失败仍走重试(重新入队)\n this.fire(this.sendBatch([{ ...event, attempts: 0 }]));\n return event.eventId;\n }\n\n this.enqueue(event);\n if (this.flushInterval <= 0) {\n // 关闭攒批:事件即时发送\n this.fire(this.flush());\n } else {\n this.scheduleFlush();\n }\n return event.eventId;\n }\n\n flush(): Promise<void> {\n if (!this.flushPromise) {\n // 显式冲刷:跳过退避立即重试(退避只约束自动重试)\n if (this.retryTimer) {\n clearTimeout(this.retryTimer);\n this.retryTimer = null;\n }\n this.flushPromise = this.drain().finally(() => {\n this.flushPromise = null;\n });\n }\n return this.flushPromise;\n }\n\n get size(): number {\n return this.queue.length;\n }\n\n /** 匿名 id(只读):首次生成后持久化不变;供外部转化跟踪等场景读取 */\n get anonymousId(): string {\n return this._anonymousId;\n }\n\n /** 设备 / 安装实例 id(只读):Web&PC 与 google client id 一致;供外部转化跟踪等场景读取 */\n get clientId(): string {\n return this._clientId;\n }\n\n /** 当前会话 id(只读):会话轮换后返回新值;供外部转化跟踪等场景读取 */\n get visitId(): string {\n return this.session.visitId;\n }\n\n /** 当前登录账号 id(只读):实时调用 userId provider,未提供恒为 null */\n get userId(): string | null {\n return this.opts.userId?.() ?? null;\n }\n\n async dispose(): Promise<void> {\n if (this.disposed) return;\n this.disposed = true;\n\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n if (this.retryTimer) {\n clearTimeout(this.retryTimer);\n this.retryTimer = null;\n }\n if (this.persistTimer) {\n clearTimeout(this.persistTimer);\n this.persistTimer = null;\n }\n\n if (this.platform === 'browser') {\n window.removeEventListener('pagehide', this.onPageHide);\n window.removeEventListener('pageshow', this.onPageShow);\n window.removeEventListener('storage', this.onStorage);\n document.removeEventListener('visibilitychange', this.onVisibilityChange);\n this.restoreHistory();\n if (this.pageTracker) {\n // 兜底:仍有进行中的访问(应用主动销毁)按 unknown 结束\n this.endPageVisit(this.session.visitId, 'unknown');\n this.pageTracker.dispose();\n this.pageTracker = null;\n }\n this.saveSession(true);\n } else {\n const exitProcess = this.getExitProcess();\n exitProcess.off('beforeExit', this.onExit);\n exitProcess.off('SIGINT', this.onExit);\n exitProcess.off('SIGTERM', this.onExit);\n }\n\n const transport = await this.transportPromise;\n\n if (this.queue.length === 0) {\n // 队列已空:若持久化有脏标记,写空队列收敛,避免恢复已发送事件造成重复上报\n if (this.persistDirty) {\n await this.persistNow([]);\n }\n return;\n }\n\n if (this.flushOnExit) {\n const pending = this.queue.splice(0);\n const wire = stripAttempts(pending);\n // 优先同步尽力发送(sendBeacon,页面卸载场景 fetch 可能来不及)\n if (transport.sendSync?.(wire) ?? false) {\n this.opts.onFlushed?.(wire);\n await this.persistNow([]);\n return;\n }\n try {\n await transport.send(wire);\n this.opts.onFlushed?.(wire);\n await this.persistNow([]);\n } catch (error) {\n this.opts.onFailed?.(wire, error);\n this.queue.push(...pending); // 放回队列持久化,下次会话恢复重发\n await this.persistNow();\n }\n return;\n }\n\n // 退出兜底关闭:剩余队列仅持久化,下次启动恢复\n if (this.persistDirty) {\n await this.persistNow();\n }\n }\n\n /** 异步初始化:创建内置持久化、采集 Node 设备快照、恢复上次会话队列、挂载退出兜底 */\n private async init(): Promise<void> {\n this.storage = await this.storagePromise;\n if (this.platform === 'node') {\n this.nodeDevice = await collectNodeDevice();\n }\n if (this.disposed) return;\n\n if (this.storage) {\n try {\n const restored = await this.storage.load();\n if (restored.length > 0) {\n this.enqueueRestored(restored);\n }\n } catch (error) {\n this.log('restore pending queue failed:', error);\n }\n }\n if (this.disposed) return;\n\n if (this.platform === 'browser') {\n window.addEventListener('storage', this.onStorage);\n if (this.flushOnExit) {\n window.addEventListener('pagehide', this.onPageHide);\n }\n if (this.flushOnExit || this.opts.autoPageView) {\n document.addEventListener('visibilitychange', this.onVisibilityChange);\n }\n if (this.opts.autoPageView) {\n this.pageTracker = createPageTracker(() => this.touchSession());\n this.pageTracker.startVisit(); // SDK 初始化视为进入页面\n this.patchHistory();\n window.addEventListener('pageshow', this.onPageShow);\n }\n } else if (this.flushOnExit) {\n const exitProcess = this.getExitProcess();\n exitProcess.once('beforeExit', this.onExit);\n exitProcess.once('SIGINT', this.onExit);\n exitProcess.once('SIGTERM', this.onExit);\n }\n\n if (this.queue.length > 0) {\n this.scheduleFlush();\n this.schedulePersist();\n }\n }\n\n /** 组装上报事件:身份字段由 SDK 填充,业务节点 = SDK 采集 + context 合并(接入方值优先) */\n private buildEvent(\n eventName: EventName,\n properties?: ReportNode,\n overrides?: Partial<IReportEvent>,\n ): IReportEvent {\n const context = this.contextFn();\n const device = {\n ...this.nodeDevice,\n ...collectBrowserDevice(),\n ...(context.device ?? {}),\n };\n const page = {\n ...collectPageSnapshot(),\n ...this.pageSnapshot(),\n ...(context.page ?? {}),\n };\n const mergedProperties = { ...(context.properties ?? {}), ...(properties ?? {}) };\n\n if (!context.app) {\n this.log('context 未提供 app 节点,以空对象上报');\n }\n\n return {\n eventId: generateEventId(),\n eventName,\n userId: this.opts.userId?.() ?? null,\n anonymousId: this._anonymousId,\n visitId: this.session.visitId,\n clientId: this._clientId,\n timestamp: Date.now(),\n app: context.app ?? {},\n ...(Object.keys(device).length > 0 ? { device } : {}),\n ...(Object.keys(page).length > 0 ? { page } : {}),\n ...(context.user ? { user: context.user } : {}),\n ...(Object.keys(mergedProperties).length > 0\n ? { properties: mergedProperties }\n : {}),\n ...overrides,\n };\n }\n\n /** 进行中页面访问的 pageViewId(非访问期返回空) */\n private pageSnapshot(): ReportNode {\n if (!this.pageTracker?.active) return {};\n const pageViewId = this.pageTracker.pageViewId();\n return pageViewId ? { pageViewId } : {};\n }\n\n /** 会话活跃:track 调用与页面交互均触发;超时轮换 visitId 并结束旧页面访问 */\n private touchSession(): void {\n const now = Date.now();\n // 多 tab 共享会话:活动时定期重读共享值,避免本 tab 长期后台后误判超时\n if (now - this.sessionReadAt >= SESSION_SAVE_THROTTLE_MS) {\n this.sessionReadAt = now;\n this.refreshSessionFromShared();\n }\n if (now - this.session.lastActivityMs > VISIT_IDLE_TIMEOUT_MS) {\n // 会话超时:结束旧页面访问(复用进入页面时的旧 visitId),轮换新会话并开启新访问\n const oldVisitId = this.session.visitId;\n this.endPageVisit(oldVisitId, 'sessionTimeout');\n this.session = { visitId: this.newId('visit'), lastActivityMs: now };\n this.saveSession(true);\n this.pageTracker?.startVisit();\n } else {\n this.session.lastActivityMs = now;\n this.saveSession();\n }\n }\n\n /** 结束当前页面访问并上报 pageView 汇总事件(复用进入页面时的 visitId) */\n private endPageVisit(visitId: string, reason: PageEndReason): void {\n if (!this.pageTracker?.active) return;\n const summary = this.pageTracker.endVisit(reason);\n if (!summary) return;\n const context = this.contextFn();\n this.enqueue(\n this.buildEvent('pageView', undefined, {\n visitId,\n page: { ...summary, ...(context.page ?? {}) },\n }),\n );\n this.scheduleFlush();\n }\n\n /** SPA 路由变化检测:pushState / replaceState 视为 routeChange,popstate 视为 back */\n private patchHistory(): void {\n if (this.historyPatch) return;\n const endVisit = (reason: PageEndReason): void => {\n this.endPageVisit(this.session.visitId, reason);\n this.pageTracker?.startVisit();\n };\n const pushState = history.pushState.bind(history);\n const replaceState = history.replaceState.bind(history);\n history.pushState = function (\n ...args: Parameters<typeof history.pushState>\n ): void {\n const result = pushState(...args);\n endVisit('routeChange');\n return result;\n };\n history.replaceState = function (\n ...args: Parameters<typeof history.replaceState>\n ): void {\n const result = replaceState(...args);\n endVisit('routeChange');\n return result;\n };\n window.addEventListener('popstate', this.onPopState);\n this.historyPatch = { pushState, replaceState };\n }\n\n private restoreHistory(): void {\n if (!this.historyPatch) return;\n history.pushState = this.historyPatch.pushState;\n history.replaceState = this.historyPatch.replaceState;\n window.removeEventListener('popstate', this.onPopState);\n this.historyPatch = null;\n }\n\n /** 恢复队列入队:历史事件放队头保证先到先发,超限按溢出丢弃;过滤旧格式(无 eventId)事件 */\n private enqueueRestored(events: IQueuedEvent[]): void {\n let accepted = events.filter(\n (event) =>\n typeof event.eventId === 'string' &&\n typeof event.eventName === 'string',\n );\n if (accepted.length < events.length) {\n this.log(`跳过 ${events.length - accepted.length} 条旧格式持久化事件`);\n }\n accepted = accepted.map((event) => ({\n ...event,\n attempts: typeof event.attempts === 'number' ? event.attempts : 0,\n }));\n if (accepted.length > this.maxQueueSize) {\n this.opts.onDropped?.(accepted.slice(this.maxQueueSize), 'overflow');\n accepted = accepted.slice(0, this.maxQueueSize);\n }\n this.queue.unshift(...accepted);\n }\n\n private enqueue(event: IReportEvent): void {\n const queued: IQueuedEvent = { ...event, attempts: 0 };\n if (this.queue.length >= this.maxQueueSize) {\n if (this.overflowPolicy === 'drop-newest') {\n this.opts.onDropped?.([event], 'overflow');\n return;\n }\n if (this.overflowPolicy === 'drop-oldest') {\n this.opts.onDropped?.([this.queue.shift()!], 'overflow');\n } else {\n // flush-now:队列满时先冲刷腾位,不丢事件\n this.fire(this.flush());\n }\n }\n this.queue.push(queued);\n this.schedulePersist();\n }\n\n /** 攒批定时:延迟上报的实现,窗口内事件合并为一批 */\n private scheduleFlush(): void {\n if (this.flushTimer || this.disposed || this.flushInterval <= 0) return;\n this.flushTimer = setTimeout(() => {\n this.flushTimer = null;\n if (this.queue.length > 0) {\n this.fire(this.flush());\n }\n }, this.flushInterval);\n }\n\n /** 串行清空队列;失败批次重新入队后由 retryTimer 再次触发 drain */\n private async drain(): Promise<void> {\n while (!this.disposed && !this.retryTimer && this.queue.length > 0) {\n // eslint-disable-next-line no-await-in-loop\n await this.sendBatch(this.takeBatch());\n }\n }\n\n /** 取一批:受条数 / 字节数限制;首条必取,避免单事件超 maxBatchBytes 时队列卡死 */\n private takeBatch(): IQueuedEvent[] {\n const batch: IQueuedEvent[] = [];\n let bytes = 0;\n while (this.queue.length > 0 && batch.length < this.maxBatchSize) {\n const size = JSON.stringify(this.queue[0]).length;\n if (batch.length > 0 && bytes + size > this.maxBatchBytes) break;\n batch.push(this.queue.shift()!);\n bytes += size;\n }\n return batch;\n }\n\n private async sendBatch(batch: IQueuedEvent[]): Promise<void> {\n const transport = await this.transportPromise;\n const wire = stripAttempts(batch);\n try {\n await transport.send(wire);\n this.opts.onFlushed?.(wire);\n this.schedulePersist(); // 该批已移出队列,持久化剩余\n } catch (error) {\n this.opts.onFailed?.(wire, error);\n this.log('send batch failed:', error);\n\n const updated = batch.map((event) => ({\n ...event,\n attempts: event.attempts + 1,\n }));\n const retryable = updated.filter(\n (event) => event.attempts < this.maxAttempts,\n );\n const dead = updated.filter(\n (event) => event.attempts >= this.maxAttempts,\n );\n if (dead.length > 0) {\n this.opts.onDropped?.(dead, 'max-attempts');\n }\n if (retryable.length > 0) {\n this.queue.unshift(...retryable); // 保持原顺序放回队头\n this.schedulePersist();\n this.scheduleRetry(retryable);\n }\n }\n }\n\n /** 失败退避:按批次内最大 attempts 计 2^n 指数增长,避免失败批次频繁重试 */\n private scheduleRetry(retryable: IQueuedEvent[]): void {\n if (this.retryTimer) return;\n const maxAttemptsInBatch = Math.max(\n ...retryable.map((event) => event.attempts),\n );\n const delay = this.backoffMs * 2 ** (maxAttemptsInBatch - 1);\n this.retryTimer = setTimeout(() => {\n this.retryTimer = null;\n this.fire(this.flush());\n }, delay);\n }\n\n /** 持久化节流:窗口内多次变更只落盘一次,避免每次入队全量序列化 */\n private schedulePersist(): void {\n if (!this.storage) return;\n this.persistDirty = true;\n if (this.persistTimer) return;\n this.persistTimer = setTimeout(() => {\n this.persistTimer = null;\n if (!this.persistDirty) return;\n this.persistDirty = false;\n this.fire(this.persistNow());\n }, STORAGE_WRITE_THROTTLE_MS);\n }\n\n private async persistNow(\n events: IQueuedEvent[] = this.queue,\n ): Promise<void> {\n if (!this.storage) return;\n try {\n await this.storage.save(events);\n } catch (error) {\n this.log('persist queue failed:', error);\n }\n }\n\n // ============================================================\n // 身份与会话持久化(browser: localStorage;Node: 进程内)。\n // 会话存 localStorage 而非 sessionStorage:同源多 tab 共享一个会话,\n // \"连续 30 分钟无活动\"是用户级语义,不应按 tab 拆分;跨 tab 同步靠\n // storage 事件(实时)+ 活动时重读(兜底)。\n // ============================================================\n\n /** 匿名 id:localStorage 持久化,首次匿名访问生成后保留;Node 无持久化介质,进程内生成 */\n private loadOrCreateAnonymousId(): string {\n const stored = this.readPersistent(ANONYMOUS_ID_KEY);\n if (stored) return stored;\n const fresh = this.newId('anonymous');\n this.writePersistent(ANONYMOUS_ID_KEY, fresh);\n return fresh;\n }\n\n /** clientId:Web&PC 与 google client id(_ga cookie)一致;否则本地生成并持久化 */\n private loadOrCreateClientId(): string {\n if (typeof document !== 'undefined') {\n const ga = document.cookie.match(/(?:^|;\\s*)_ga=GA1\\.\\d\\.([^;]+)/);\n if (ga?.[1]) return ga[1];\n const stored = this.readPersistent(CLIENT_ID_KEY);\n if (stored) return stored;\n }\n // TODO: Node 端跨重启持久化(如 persistDir 落盘),当前进程内生成\n const fresh = this.newId('client');\n this.writePersistent(CLIENT_ID_KEY, fresh);\n return fresh;\n }\n\n /** 会话恢复:localStorage(同源多 tab 共享)中 30 分钟窗口内的会话复用,否则新建 */\n private loadOrCreateSession(): ISession {\n const now = Date.now();\n try {\n const text = this.readPersistent(SESSION_KEY);\n if (text) {\n const stored = JSON.parse(text) as ISession;\n if (\n stored.visitId &&\n now - stored.lastActivityMs <= VISIT_IDLE_TIMEOUT_MS\n ) {\n return stored;\n }\n }\n } catch {\n // 解析失败:新建会话\n }\n const fresh = { visitId: this.newId('visit'), lastActivityMs: now };\n this.session = fresh;\n this.saveSession(true);\n return fresh;\n }\n\n /** 会话落盘(localStorage):节流 10s,避免滚动等高频活动频繁写;force 用于轮换 / 退出兜底 */\n private saveSession(force = false): void {\n const now = Date.now();\n if (\n !force &&\n this.sessionSaveAt > 0 &&\n now - this.sessionSaveAt < SESSION_SAVE_THROTTLE_MS\n ) {\n return;\n }\n this.sessionSaveAt = now;\n this.writePersistent(SESSION_KEY, JSON.stringify(this.session));\n }\n\n /** 采纳共享会话:visitId 变化视为其他 tab 轮换了会话,结束本 tab 旧页面访问并开启新访问 */\n private adoptSharedSession(stored: ISession): void {\n if (!stored.visitId || stored.visitId === this.session.visitId) {\n if (stored.visitId) this.session = stored; // 仅活跃时间更新\n return;\n }\n const oldVisitId = this.session.visitId;\n this.endPageVisit(oldVisitId, 'sessionTimeout');\n this.pageTracker?.startVisit();\n this.session = stored;\n }\n\n /** 活动时重读共享会话(兜底 storage 事件丢失):storage 事件只在其他 tab 触发 */\n private refreshSessionFromShared(): void {\n try {\n const text = this.readPersistent(SESSION_KEY);\n if (text) {\n this.adoptSharedSession(JSON.parse(text) as ISession);\n }\n } catch {\n // 解析失败:忽略,保持内存会话\n }\n }\n\n private readPersistent(key: string): string | null {\n // 本地持久化仅浏览器生效,Node 端进程内生成;按平台短路,\n // 避免命中 Node ≥22 实验性 localStorage 全局(引用即触发告警)\n if (this.platform !== 'browser') return null;\n try {\n return localStorage.getItem(key);\n } catch {\n return null; // 隐私模式 / 配额限制:退化为进程内生成\n }\n }\n\n private writePersistent(key: string, value: string): void {\n if (this.platform !== 'browser') return;\n try {\n localStorage.setItem(key, value);\n } catch {\n // 不可写:静默退化\n }\n }\n\n /** 生成带前缀的实例 id(格式对齐旧 SDK session_ / device_) */\n private newId(prefix: string): string {\n return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;\n }\n\n private log(...args: unknown[]): void {\n if (this.opts.debug) {\n // eslint-disable-next-line no-console\n console.log('[reporter]', ...args);\n }\n }\n\n /**\n * Node 进程退出事件句柄:Electron 渲染进程 typings 里 process 只声明了 'loaded'\n * 事件,与 @types/node 不一致,显式收窄到本 SDK 用到的信号,兼容两端编译器\n */\n private getExitProcess(): ExitProcessHandle {\n return process as unknown as ExitProcessHandle;\n }\n\n /** 安全触发异步任务:吞掉 rejection,避免未处理异常 */\n private fire(promise: Promise<unknown>): void {\n promise.catch((error) => {\n this.log('async task failed:', error);\n });\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clipto/reporter",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "通用数据上报 SDK:攒批 / 重试 / 有界队列 / 崩溃恢复,浏览器(sendBeacon+fetch+Dexie)与 Node(axios+conf)双端通用",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Clipto",
|
|
@@ -31,12 +31,14 @@
|
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "tsup",
|
|
33
33
|
"dev": "tsup --watch",
|
|
34
|
+
"test": "npm run build && node --test test/smoke.mjs",
|
|
34
35
|
"prepublishOnly": "npm run build"
|
|
35
36
|
},
|
|
36
37
|
"dependencies": {
|
|
37
38
|
"axios": "^1.7.9",
|
|
38
39
|
"conf": "^10.2.0",
|
|
39
|
-
"dexie": "^4.0.10"
|
|
40
|
+
"dexie": "^4.0.10",
|
|
41
|
+
"ua-parser-js": "^2.0.10"
|
|
40
42
|
},
|
|
41
43
|
"devDependencies": {
|
|
42
44
|
"@types/node": "^26.3.0",
|