mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-02 06:22:42 -08:00
150 lines
3.9 KiB
TypeScript
150 lines
3.9 KiB
TypeScript
import { OverlayContentMeasurement, OverlayContentRect, OverlayLayer } from "../../types";
|
|
const MAX_VIEWPORT = 10000;
|
|
const MAX_RECT_DIMENSION = 10000;
|
|
const MAX_RECT_OFFSET = 50000;
|
|
const MAX_FUTURE_TIMESTAMP_MS = 60_000;
|
|
const INVALID_LOG_THROTTLE_MS = 10_000;
|
|
|
|
type OverlayMeasurementStore = Record<OverlayLayer, OverlayContentMeasurement | null>;
|
|
|
|
export function sanitizeOverlayContentMeasurement(
|
|
payload: unknown,
|
|
nowMs: number,
|
|
): OverlayContentMeasurement | null {
|
|
if (!payload || typeof payload !== "object") return null;
|
|
|
|
const candidate = payload as {
|
|
layer?: unknown;
|
|
measuredAtMs?: unknown;
|
|
viewport?: { width?: unknown; height?: unknown };
|
|
contentRect?: { x?: unknown; y?: unknown; width?: unknown; height?: unknown } | null;
|
|
};
|
|
|
|
if (candidate.layer !== "visible" && candidate.layer !== "invisible") {
|
|
return null;
|
|
}
|
|
|
|
const viewportWidth = readFiniteInRange(candidate.viewport?.width, 1, MAX_VIEWPORT);
|
|
const viewportHeight = readFiniteInRange(candidate.viewport?.height, 1, MAX_VIEWPORT);
|
|
|
|
if (!Number.isFinite(viewportWidth) || !Number.isFinite(viewportHeight)) {
|
|
return null;
|
|
}
|
|
|
|
const measuredAtMs = readFiniteInRange(
|
|
candidate.measuredAtMs,
|
|
1,
|
|
nowMs + MAX_FUTURE_TIMESTAMP_MS,
|
|
);
|
|
if (!Number.isFinite(measuredAtMs)) {
|
|
return null;
|
|
}
|
|
|
|
const contentRect = sanitizeOverlayContentRect(candidate.contentRect);
|
|
if (candidate.contentRect !== null && !contentRect) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
layer: candidate.layer,
|
|
measuredAtMs,
|
|
viewport: { width: viewportWidth, height: viewportHeight },
|
|
contentRect,
|
|
};
|
|
}
|
|
|
|
function sanitizeOverlayContentRect(
|
|
rect: unknown,
|
|
): OverlayContentRect | null {
|
|
if (rect === null || rect === undefined) {
|
|
return null;
|
|
}
|
|
|
|
if (!rect || typeof rect !== "object") {
|
|
return null;
|
|
}
|
|
|
|
const candidate = rect as {
|
|
x?: unknown;
|
|
y?: unknown;
|
|
width?: unknown;
|
|
height?: unknown;
|
|
};
|
|
|
|
const width = readFiniteInRange(candidate.width, 0, MAX_RECT_DIMENSION);
|
|
const height = readFiniteInRange(candidate.height, 0, MAX_RECT_DIMENSION);
|
|
const x = readFiniteInRange(candidate.x, -MAX_RECT_OFFSET, MAX_RECT_OFFSET);
|
|
const y = readFiniteInRange(candidate.y, -MAX_RECT_OFFSET, MAX_RECT_OFFSET);
|
|
|
|
if (
|
|
!Number.isFinite(width) ||
|
|
!Number.isFinite(height) ||
|
|
!Number.isFinite(x) ||
|
|
!Number.isFinite(y)
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
return { x, y, width, height };
|
|
}
|
|
|
|
function readFiniteInRange(
|
|
value: unknown,
|
|
min: number,
|
|
max: number,
|
|
): number {
|
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
return Number.NaN;
|
|
}
|
|
if (value < min || value > max) {
|
|
return Number.NaN;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
export function createOverlayContentMeasurementStoreService(options?: {
|
|
now?: () => number;
|
|
warn?: (message: string) => void;
|
|
}) {
|
|
const now = options?.now ?? (() => Date.now());
|
|
const warn = options?.warn ?? ((message: string) => console.warn(message));
|
|
const latestByLayer: OverlayMeasurementStore = {
|
|
visible: null,
|
|
invisible: null,
|
|
};
|
|
|
|
let droppedInvalid = 0;
|
|
let lastInvalidLogAtMs = 0;
|
|
|
|
function report(payload: unknown): OverlayContentMeasurement | null {
|
|
const nowMs = now();
|
|
const measurement = sanitizeOverlayContentMeasurement(payload, nowMs);
|
|
if (!measurement) {
|
|
droppedInvalid += 1;
|
|
if (
|
|
droppedInvalid > 0 &&
|
|
nowMs - lastInvalidLogAtMs >= INVALID_LOG_THROTTLE_MS
|
|
) {
|
|
warn(
|
|
`[overlay-content-bounds] Dropped ${droppedInvalid} invalid measurement payload(s) in the last ${INVALID_LOG_THROTTLE_MS}ms.`,
|
|
);
|
|
droppedInvalid = 0;
|
|
lastInvalidLogAtMs = nowMs;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
latestByLayer[measurement.layer] = measurement;
|
|
return measurement;
|
|
}
|
|
|
|
function getLatestByLayer(layer: OverlayLayer): OverlayContentMeasurement | null {
|
|
return latestByLayer[layer];
|
|
}
|
|
|
|
return {
|
|
getLatestByLayer,
|
|
report,
|
|
};
|
|
}
|