From 188e6f39eacfd2126404e5c4a249d857542e41f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Tue, 15 Sep 2026 16:13:37 +0800 Subject: [PATCH] =?UTF-8?q?fix(sdk):=20transport=20=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=E4=BF=AE=E6=AD=A3=EF=BC=88sendBeacon=20?= =?UTF-8?q?=E7=9C=9F=E5=AE=9E=E7=BD=91=E7=BB=9C=E8=AD=A6=E7=A4=BA/?= =?UTF-8?q?=E9=80=80=E9=81=BF=E5=8C=BA=E9=97=B4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Code --- sdk/src/transport.ts | 2 ++ sdk/test/transport.test.ts | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/sdk/src/transport.ts b/sdk/src/transport.ts index 8c4084a..4683a8f 100644 --- a/sdk/src/transport.ts +++ b/sdk/src/transport.ts @@ -7,6 +7,8 @@ export type TransportConfig = Required; // 批量传输:满 batchSize 或 flushMs 到期即发;4xx(除 429)不重试; // 指数退避 + 抖动重试;重试耗尽按本批事件数计 dropped;queueMax 溢出丢最旧。 // 红线:任何异常不得冒泡到宿主页(定时器/事件回调内的异常无人捕获)。 +// 审查留档:events 目前均为 SDK 自产可序列化对象;若未来引入受控外的 events +// 来源,需处理 stringify 抛错时已换出批次的计数(当前静默丢弃不计 dropped)。 export class Transport { private q: object[] = []; private timer: ReturnType | null = null; diff --git a/sdk/test/transport.test.ts b/sdk/test/transport.test.ts index e32c396..7a0887b 100644 --- a/sdk/test/transport.test.ts +++ b/sdk/test/transport.test.ts @@ -88,7 +88,7 @@ describe("transport", () => { t.push({ event_id: "e2" }); expect(fetchMock).toHaveBeenCalledTimes(1); - await vi.advanceTimersByTimeAsync(2000); // 第 1 次退避 500*2^1*jitter ∈ [1000,1400] + await vi.advanceTimersByTimeAsync(2000); // 第 1 次退避 500*2^1*jitter ∈ [800,1200] expect(fetchMock).toHaveBeenCalledTimes(2); expect(fetchMock.mock.calls[1][1].body).toBe(fetchMock.mock.calls[0][1].body); expect((t as unknown as { retry: number }).retry).toBe(0); @@ -127,7 +127,7 @@ describe("transport", () => { t.push({ event_id: "e2" }); expect(fetchMock).toHaveBeenCalledTimes(1); - await vi.advanceTimersByTimeAsync(2000); // 第 1 次重试(∈[1000,1400])后再失败 + await vi.advanceTimersByTimeAsync(2000); // 第 1 次重试(∈[800,1200])后再失败 expect(fetchMock).toHaveBeenCalledTimes(2); // 初发 1 + 重试 1,共 2 次 expect(t.dropped).toBe(2); // 按入批事件数计,而非按批计 expect((t as unknown as { retry: number }).retry).toBe(0); // 归零供下批复用 @@ -148,7 +148,7 @@ describe("transport", () => { }); it("flush(true) 走 sendBeacon(Blob/text/plain),不调 fetch", async () => { - // happy-dom 无 navigator.sendBeacon——整对象替换并测后还原(unstubAllGlobals) + // happy-dom 的 sendBeacon 会发真实网络请求,必须 stubGlobal 替换 navigator(不能只 spy) const beacon = vi.fn().mockReturnValue(true); vi.stubGlobal("navigator", { sendBeacon: beacon }); const t = new Transport(mkCfg({ batchSize: 10 }));