From 96765e483d6f39abd15c9cef89fd9c5a51795bef Mon Sep 17 00:00:00 2001 From: shaun0927 <70629228+shaun0927@users.noreply.github.com> Date: Tue, 21 Apr 2026 18:12:28 +0900 Subject: [PATCH] docs: correct cx/cy units and sanitize buffer option limits Two follow-up fixes for CodeRabbit feedback on the docs commit: - CursorTelemetryPoint JSDoc previously described cx/cy as 'device-pixel positions'. The producer sampleCursorPoint() in electron/ipc/handlers.ts clamps them to the [0, 1] range after dividing by the source display's width/height, so they are normalised ratios, not pixel values. Correct the doc comment accordingly. - createCursorTelemetryBuffer now sanitizes maxActiveSamples and maxPendingBatches: non-finite, zero, or negative values fall back to safe positive-integer defaults. Without this, a caller passing Infinity or NaN would hang the trim loops. New test covers the sanitisation path for both options. --- src/lib/cursorTelemetryBuffer.test.ts | 23 +++++++++++++++++++++++ src/lib/cursorTelemetryBuffer.ts | 22 ++++++++++++++++++---- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/lib/cursorTelemetryBuffer.test.ts b/src/lib/cursorTelemetryBuffer.test.ts index 309df7e..567a1eb 100644 --- a/src/lib/cursorTelemetryBuffer.test.ts +++ b/src/lib/cursorTelemetryBuffer.test.ts @@ -190,6 +190,29 @@ describe("createCursorTelemetryBuffer", () => { warn.mockRestore(); }); + it("sanitizes non-finite or non-positive option values to safe defaults", () => { + // Infinity / NaN / negative would otherwise turn the trim loops + // into infinite loops. The buffer must fall back to defaults. + const buf = createCursorTelemetryBuffer({ + maxActiveSamples: Number.POSITIVE_INFINITY, + maxPendingBatches: Number.NaN, + }); + + buf.startSession(); + buf.push(sample(1)); + expect(() => buf.endSession()).not.toThrow(); + expect(buf.pendingCount).toBe(1); + + const buf2 = createCursorTelemetryBuffer({ + maxActiveSamples: -5, + maxPendingBatches: 0, + }); + buf2.startSession(); + buf2.push(sample(2)); + expect(() => buf2.endSession()).not.toThrow(); + expect(buf2.pendingCount).toBe(1); + }); + it("reset() clears both active and pending state", () => { const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 }); buf.startSession(); diff --git a/src/lib/cursorTelemetryBuffer.ts b/src/lib/cursorTelemetryBuffer.ts index 57db2ed..e97bab8 100644 --- a/src/lib/cursorTelemetryBuffer.ts +++ b/src/lib/cursorTelemetryBuffer.ts @@ -1,8 +1,10 @@ /** * A single cursor telemetry sample captured during a recording session. * - * Coordinates (`cx`, `cy`) are device-pixel positions relative to the - * captured surface; `timeMs` is the offset from the recording's start. + * Coordinates (`cx`, `cy`) are clamped ratios in the `[0, 1]` range, + * normalised against the captured surface's width and height by the + * main-process `sampleCursorPoint()` before being pushed. `timeMs` is the + * offset (in milliseconds) from the recording's start. */ export interface CursorTelemetryPoint { timeMs: number; @@ -94,17 +96,29 @@ export interface CursorTelemetryBufferOptions { } const DEFAULT_MAX_PENDING_BATCHES = 8; +const DEFAULT_MAX_ACTIVE_SAMPLES = 10_000; + +/** Coerce a numeric option into a safe, finite, positive integer. */ +function sanitizeLimit(value: number | undefined, fallback: number): number { + if (typeof value !== "number" || !Number.isFinite(value)) return fallback; + const floored = Math.floor(value); + return floored >= 1 ? floored : fallback; +} /** * Create a cursor telemetry buffer. * + * Numeric options are sanitized: non-finite, negative, or zero values fall + * back to safe defaults so a bad caller cannot disable the memory bounds + * (which would turn the trim loops into infinite loops). + * * @see CursorTelemetryBuffer for the full lifecycle contract. */ export function createCursorTelemetryBuffer( options: CursorTelemetryBufferOptions, ): CursorTelemetryBuffer { - const maxActive = options.maxActiveSamples; - const maxPending = options.maxPendingBatches ?? DEFAULT_MAX_PENDING_BATCHES; + const maxActive = sanitizeLimit(options.maxActiveSamples, DEFAULT_MAX_ACTIVE_SAMPLES); + const maxPending = sanitizeLimit(options.maxPendingBatches, DEFAULT_MAX_PENDING_BATCHES); let active: CursorTelemetryPoint[] = []; let pending: CursorTelemetryPoint[][] = [];