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.
This commit is contained in:
shaun0927
2026-04-21 18:12:28 +09:00
parent adc610544c
commit 96765e483d
2 changed files with 41 additions and 4 deletions
+23
View File
@@ -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();
+18 -4
View File
@@ -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[][] = [];