feat(sdk): 页面浏览/有效停留/事件入口,打包≤15KB

- events.ts:page_view/page_stay(有效停留=可见+30s 内有活动),
  close 双触发(hidden+pagehide)靠 activeMs 清零不重发
- index.ts:IIFE 入口,队列重放前先替换 g.ctk=dispatch;
  首触归因——__ctk_ctx 除设备环境外带 referrer_domain/utm/path
- transport.ts:stringify 抛错(track 宿主可控对象)按已换出
  批次事件数计 dropped(Task 14 审查加固)
- demo 双页互跳;gzip 2209B ≤ 15360B;测试 35→44

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-15 16:28:14 +08:00
parent 188e6f39ea
commit dc13ae6e0e
8 changed files with 474 additions and 3 deletions
+7 -3
View File
@@ -7,8 +7,8 @@ export type TransportConfig = Required<CtkConfig>;
// 批量传输:满 batchSize 或 flushMs 到期即发;4xx(除 429)不重试;
// 指数退避 + 抖动重试;重试耗尽按本批事件数计 dropped;queueMax 溢出丢最旧。
// 红线:任何异常不得冒泡到宿主页(定时器/事件回调内的异常无人捕获)。
// 审查留档:events 目前均为 SDK 自产可序列化对象;若未来引入受控外的 events
// 来源,需处理 stringify 抛错时已换出批次的计数(当前静默丢弃不计 dropped)。
// 审查留档(Task 14 结论):index.ts 的 track 分支已引入宿主可控对象——
// stringify 抛错(如循环引用)时已换出的批次按事件数计 dropped。
export class Transport {
private q: object[] = [];
private timer: ReturnType<typeof setTimeout> | null = null;
@@ -33,9 +33,11 @@ export class Transport {
this.timer = null;
}
if (!this.q.length) return;
let n = 0;
try {
const events = this.q;
this.q = [];
n = events.length; // 换出后先记数:stringify 抛错时本批已丢,须计 dropped
const body = JSON.stringify({
batch_id: uuid(),
site_id: this.cfg.siteId,
@@ -55,7 +57,9 @@ export class Transport {
}
this.send(body, events.length);
} catch {
// 定时器/事件回调内的任何异常不得冒泡到宿主页
// 定时器/事件回调内的任何异常不得冒泡到宿主页;
// stringify 抛错时批次已换出(宿主可控对象),按事件数计丢弃
this.dropped += n;
}
}