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
+16
View File
@@ -176,6 +176,22 @@ describe("transport", () => {
await vi.advanceTimersByTimeAsync(100);
expect(t.dropped).toBe(1); // maxRetry=0 耗尽 → 按事件数丢弃
});
it("stringify 抛错(track 引入宿主可控对象):已换出批次按事件数计 dropped", () => {
const t = new Transport(mkCfg({ batchSize: 10, flushMs: 60000 }));
const bad: { event_id: string; self?: unknown } = { event_id: "bad" };
bad.self = bad; // 循环引用 → JSON.stringify 抛 TypeError
t.push(bad);
t.push({ event_id: "ok" });
expect(() => t.flush()).not.toThrow();
expect(t.dropped).toBe(2); // 换出的 2 条全部落丢,不能静默不计
expect(fetchMock).not.toHaveBeenCalled();
t.push({ event_id: "e3" }); // 队列已换出,后续事件照常
t.flush();
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(JSON.parse(fetchMock.mock.calls[0][1].body).events).toEqual([{ event_id: "e3" }]);
});
});
describe("mergeConfig", () => {