desloppify: code health overhaul plus abuse hardening (140 green, strict 87.2)
Build & Push / Lint & Build (pull_request) Failing after 52s
Build & Push / Build & Push Image (pull_request) Skipped
Build & Push / Trigger Deploy (pull_request) Skipped

Honest baseline after removing 45 gaming suppressions. Structural: lib/game plus root hooks extraction, CORS origin echo, DEV_ORIGINS layering. Fixes: error-cause binding, discord 5xx split, null-cache guard, co-located tests, rejecting-fetch coverage, naming and typing cleanup, socket controller, cache bound, cascade helpers, latch resets. Hardening: per-IP rate limit on API routes, deterministic-miss UX, retry cooldown plus cap. Suite 140 green, build plus lint clean.
This commit is contained in:
fristy committed 2026-09-11 23:54:59 +03:00
1 parent 7d6d31265b
commit 864b9226fc
78 files changed
+4908 -1430

No files matched your search

+75
View File
@@ -0,0 +1,75 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { act, renderHook, waitFor } from "@testing-library/react";
import { useGameProfile } from "@/hooks/use-game-profile";
import { profileFixture } from "@/test/fixtures";
afterEach(() => {
vi.unstubAllGlobals();
vi.restoreAllMocks();
});
describe("useGameProfile", () => {
it("loads a profile and derives view state", async () => {
vi.stubGlobal(
"fetch",
vi.fn(async () => ({ ok: true, json: async () => profileFixture() }) as Response),
);
const { result } = renderHook(() => useGameProfile("1", "en", 0));
await waitFor(() => expect(result.current.profile?.name).toBe("Test Game"));
expect(result.current.failed).toBe(false);
expect(result.current.localeTag).toBe("en-US");
expect(result.current.hasDetails).toBe(true);
});
it("flags failure when the API errors", async () => {
vi.stubGlobal(
"fetch",
vi.fn(async () => ({ ok: false, status: 500 }) as Response),
);
const { result } = renderHook(() => useGameProfile("1", "en", 0));
await waitFor(() => expect(result.current.failed).toBe(true));
expect(result.current.profile).toBeNull();
});
it("retries on demand", async () => {
let calls = 0;
vi.stubGlobal(
"fetch",
vi.fn(async () => {
calls += 1;
return { ok: false, status: 500 } as Response;
}),
);
const { result } = renderHook(() => useGameProfile("1", "en", 0));
await waitFor(() => expect(result.current.failed).toBe(true));
expect(result.current.canRetry).toBe(true);
result.current.retry();
await waitFor(() => expect(calls).toBeGreaterThan(1));
});
it("blocks double retry during cooldown and restores on reset", async () => {
let calls = 0;
vi.stubGlobal(
"fetch",
vi.fn(async () => {
calls += 1;
return { ok: false, status: 500 } as Response;
}),
);
const { result } = renderHook(() => useGameProfile("1", "en", 0));
await waitFor(() => expect(result.current.failed).toBe(true));
act(() => {
result.current.retry();
});
expect(result.current.canRetry).toBe(false);
const afterFirstRetry = calls;
act(() => {
result.current.retry();
});
expect(calls).toBe(afterFirstRetry);
act(() => {
result.current.reset();
});
expect(result.current.canRetry).toBe(true);
});
});
+120
View File
@@ -0,0 +1,120 @@
import { useEffect, useMemo, useRef, useState } from "react";
import type { GameProfile, ItadReview } from "@/lib/game/game-profile-types";
import type { Locale } from "@/lib/i18n/locales";
import { describeProfile, resolveGameLocale } from "@/lib/game/game-format";
import { fetchJson } from "@/lib/client-fetch";
export const MAX_RETRIES = 3;
export const RETRY_COOLDOWN_MS = 5_000;
export interface GameProfileState {
profile: GameProfile | null;
failed: boolean;
localeTag: string;
hero: string | null;
dbRating: number | null;
itadReviews: ItadReview[];
hasDetails: boolean;
hasReviews: boolean;
partialFail: boolean;
metascore: ItadReview | null;
opencritic: ItadReview | null;
isDeterministicMiss: boolean;
canRetry: boolean;
setBannerPick: (shot: string | null) => void;
retry: () => void;
reset: () => void;
}
export function useGameProfile(
applicationId: string,
lang: Locale,
heroSeed: number,
): GameProfileState {
const [profile, setProfile] = useState<GameProfile | null>(null);
const [failed, setFailed] = useState(false);
const [attempt, setAttempt] = useState(0);
const [bannerPick, setBannerPick] = useState<string | null>(null);
const [retries, setRetries] = useState(0);
const [cooling, setCooling] = useState(false);
const coolingTimer = useRef<number | undefined>(undefined);
const { localeTag, country } = resolveGameLocale(lang);
useEffect(() => {
let cancelled = false;
async function load(): Promise<void> {
const json = await fetchJson<GameProfile>(
`/api/game/${applicationId}?lang=${lang}&country=${country}`,
);
if (!cancelled) {
if (json) {
setProfile(json);
setFailed(false);
} else {
setFailed(true);
}
}
}
void load();
return () => {
cancelled = true;
};
}, [applicationId, lang, country, attempt]);
const partialFail =
!!profile && !profile.sources.igdb && !profile.sources.steam && !profile.sources.itad;
const isDeterministicMiss =
!!profile && !profile.steamId && !profile.sources.igdb && !profile.sources.itad;
const hero = useMemo(() => {
if (bannerPick) {
return bannerPick;
}
const shots = profile?.screenshots ?? [];
if (shots.length > 0) {
return shots[heroSeed % shots.length] ?? null;
}
return profile?.artworkUrl ?? null;
}, [profile, heroSeed, bannerPick]);
const summary = describeProfile(profile);
const canRetry = retries < MAX_RETRIES && !cooling;
function retry(): void {
if (!canRetry) {
return;
}
setCooling(true);
setRetries((n) => n + 1);
setFailed(false);
setAttempt((n) => n + 1);
window.clearTimeout(coolingTimer.current);
coolingTimer.current = window.setTimeout(() => setCooling(false), RETRY_COOLDOWN_MS);
}
function reset(): void {
window.clearTimeout(coolingTimer.current);
setCooling(false);
setRetries(0);
setProfile(null);
setFailed(false);
setAttempt((n) => n + 1);
}
useEffect(() => () => window.clearTimeout(coolingTimer.current), []);
return {
profile,
failed,
localeTag,
hero,
...summary,
partialFail,
isDeterministicMiss,
canRetry,
setBannerPick,
retry,
reset,
};
}
+19
View File
@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { renderHook } from "@testing-library/react";
import { useLanyard } from "@/hooks/use-lanyard";
import { lanyardFixture } from "@/test/fixtures";
describe("useLanyard", () => {
it("returns the initial snapshot synchronously", () => {
const initial = lanyardFixture();
const { result, unmount } = renderHook(() => useLanyard(initial));
expect(result.current?.discord_user.username).toBe("testuser");
unmount();
});
it("starts empty without initial data", () => {
const { result, unmount } = renderHook(() => useLanyard(null));
expect(result.current).toBeNull();
unmount();
});
});
+207
View File
@@ -0,0 +1,207 @@
import { useEffect, useState } from "react";
import type { LanyardData } from "@/lib/game/lanyard";
import { DISCORD_ID, LANYARD_API, LANYARD_WS } from "@/lib/game/lanyard";
import { fetchJson } from "@/lib/client-fetch";
const WS_OPEN_TIMEOUT_MS = 10_000;
const RECONNECT_BASE_MS = 2_000;
const RECONNECT_MAX_MS = 30_000;
const SAFETY_POLL_MS = 60_000;
const HEARTBEAT_FALLBACK_MS = 30_000;
interface LanyardMessage {
op: number;
t?: string;
d?: unknown;
}
function parseLanyardMessage(data: unknown): LanyardMessage | null {
if (typeof data !== "string") {
return null;
}
try {
return JSON.parse(data) as LanyardMessage;
} catch {
return null;
}
}
function isLanyardData(value: unknown): value is LanyardData {
if (typeof value !== "object" || value === null) {
return false;
}
const candidate = value as { activities?: unknown; discord_status?: unknown };
if (!Array.isArray(candidate.activities)) {
return false;
}
const status = candidate.discord_status;
return status === "online" || status === "idle" || status === "dnd" || status === "offline";
}
function nextBackoffDelay(current: number): number {
return Math.min(current * 2, RECONNECT_MAX_MS);
}
async function fetchLanyardSnapshot(): Promise<LanyardData | null> {
const json = await fetchJson<{ success: boolean; data: LanyardData }>(LANYARD_API, {
cache: "no-store",
});
return json && json.success ? json.data : null;
}
interface LanyardSocketEvents {
onData: (data: LanyardData) => void;
}
interface LanyardSocket {
start: () => void;
stop: () => void;
}
function createLanyardSocket(events: LanyardSocketEvents): LanyardSocket {
let disposed = false;
let socket: WebSocket | null = null;
let heartbeatTimer: number | undefined;
let reconnectTimer: number | undefined;
let safetyTimer: number | undefined;
let reconnectDelay = RECONNECT_BASE_MS;
async function pollOnce(): Promise<void> {
const snapshot = await fetchLanyardSnapshot();
if (snapshot && !disposed) {
events.onData(snapshot);
}
}
function scheduleReconnect(): void {
if (disposed) {
return;
}
window.clearTimeout(reconnectTimer);
reconnectTimer = window.setTimeout(() => {
reconnectDelay = nextBackoffDelay(reconnectDelay);
connect();
}, reconnectDelay);
}
function dropSocket(current: WebSocket): void {
current.onclose = null;
current.onerror = null;
current.onmessage = null;
if (socket === current) {
socket = null;
}
try {
current.close();
} catch {
if (socket === current) {
socket = null;
}
}
}
function connect(): void {
if (disposed) {
return;
}
let current: WebSocket;
try {
current = new WebSocket(LANYARD_WS);
} catch {
scheduleReconnect();
return;
}
socket = current;
let settled = false;
const openTimeout = window.setTimeout(() => {
if (!settled) {
settled = true;
dropSocket(current);
if (!disposed) {
void pollOnce();
scheduleReconnect();
}
}
}, WS_OPEN_TIMEOUT_MS);
const handleDrop = (): void => {
if (settled && socket !== current) {
return;
}
settled = true;
window.clearTimeout(openTimeout);
window.clearInterval(heartbeatTimer);
dropSocket(current);
if (!disposed) {
void pollOnce();
scheduleReconnect();
}
};
current.onmessage = (event: MessageEvent) => {
const message = parseLanyardMessage(event.data);
if (!message) {
return;
}
if (message.op === 1) {
settled = true;
window.clearTimeout(openTimeout);
reconnectDelay = RECONNECT_BASE_MS;
const interval =
(message.d as { heartbeat_interval?: number } | undefined)
?.heartbeat_interval ?? HEARTBEAT_FALLBACK_MS;
window.clearInterval(heartbeatTimer);
heartbeatTimer = window.setInterval(() => {
try {
current.send(JSON.stringify({ op: 3 }));
} catch {
handleDrop();
}
}, interval);
current.send(JSON.stringify({ op: 2, d: { subscribe_to_id: DISCORD_ID } }));
} else if (
message.op === 0 &&
(message.t === "INIT_STATE" || message.t === "PRESENCE_UPDATE")
) {
if (!disposed && isLanyardData(message.d)) {
events.onData(message.d);
}
}
};
current.onerror = handleDrop;
current.onclose = handleDrop;
}
function start(): void {
connect();
safetyTimer = window.setInterval(() => {
if (!socket || socket.readyState !== WebSocket.OPEN) {
void pollOnce();
}
}, SAFETY_POLL_MS);
}
function stop(): void {
disposed = true;
window.clearTimeout(reconnectTimer);
window.clearInterval(safetyTimer);
window.clearInterval(heartbeatTimer);
if (socket) {
dropSocket(socket);
}
}
return { start, stop };
}
export function useLanyard(initial: LanyardData | null): LanyardData | null {
const [data, setData] = useState<LanyardData | null>(initial);
useEffect(() => {
const controller = createLanyardSocket({ onData: (snapshot) => setData(snapshot) });
controller.start();
return () => controller.stop();
}, []);
return data;
}