desloppify: code health overhaul plus abuse hardening (140 green, strict 87.2)
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:
78 files changed
+4908
-1430
No files matched your search
+44
-212
@@ -4,20 +4,20 @@ import Image from "next/image";
|
||||
import { useEffect, useState } from "react";
|
||||
import { GameProfileModal } from "@/components/game-profile-modal";
|
||||
import type { Dictionary } from "@/lib/i18n/get-dictionary";
|
||||
import { useLanyard } from "@/hooks/use-lanyard";
|
||||
import {
|
||||
activityImageUrl,
|
||||
DISCORD_ID,
|
||||
discordAvatarUrl,
|
||||
discordGameUrl,
|
||||
formatSpotifyList,
|
||||
LANYARD_WS,
|
||||
lanyardUrl,
|
||||
spotifyTrackUrl,
|
||||
type DiscordAppInfo,
|
||||
type DiscordStatus,
|
||||
type LanyardActivity,
|
||||
type LanyardData,
|
||||
} from "@/lib/lanyard";
|
||||
} from "@/lib/game/lanyard";
|
||||
import { discordGameUrl, type DiscordAppInfo } from "@/lib/game/discord-app";
|
||||
import { fetchJson } from "@/lib/client-fetch";
|
||||
import { formatElapsed } from "@/lib/game/game-format";
|
||||
import type { Locale } from "@/lib/i18n/locales";
|
||||
|
||||
const STATUS_DOT: Record<DiscordStatus, string> = {
|
||||
online: "bg-emerald-400",
|
||||
@@ -26,15 +26,13 @@ const STATUS_DOT: Record<DiscordStatus, string> = {
|
||||
offline: "bg-zinc-500",
|
||||
};
|
||||
|
||||
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 MS_PER_SECOND = 1000;
|
||||
const SECONDS_PER_MINUTE = 60;
|
||||
|
||||
function formatTrackTime(ms: number): string {
|
||||
const totalSeconds = Math.max(0, Math.floor(ms / 1000));
|
||||
const minutes = Math.floor(totalSeconds / 60);
|
||||
const seconds = totalSeconds % 60;
|
||||
const totalSeconds = Math.max(0, Math.floor(ms / MS_PER_SECOND));
|
||||
const minutes = Math.floor(totalSeconds / SECONDS_PER_MINUTE);
|
||||
const seconds = totalSeconds % SECONDS_PER_MINUTE;
|
||||
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
@@ -47,11 +45,12 @@ function SpotifyProgress({ start, end }: { start: number; end: number }) {
|
||||
}, []);
|
||||
|
||||
const total = Math.max(1, end - start);
|
||||
const elapsed = Math.min(Math.max(0, now - start), total);
|
||||
const ratio = Math.min(1, Math.max(0, (now - start) / total));
|
||||
const percent = Math.round(ratio * 100);
|
||||
return (
|
||||
<div className="mt-1.5 flex max-w-60 items-center gap-1.5 font-mono text-[10px] text-zinc-500">
|
||||
<span>{formatTrackTime(now - start)}</span>
|
||||
<span>{formatTrackTime(elapsed)}</span>
|
||||
<div
|
||||
className="h-[3px] flex-1 overflow-hidden rounded-full bg-white/10"
|
||||
role="progressbar"
|
||||
@@ -66,43 +65,33 @@ function SpotifyProgress({ start, end }: { start: number; end: number }) {
|
||||
);
|
||||
}
|
||||
|
||||
function formatElapsed(start: number, lang: string): string {
|
||||
const seconds = Math.max(0, Math.floor((Date.now() - start) / 1000));
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
if (lang === "tr") {
|
||||
if (hours > 0) {
|
||||
return `${hours}s ${minutes}dk`;
|
||||
}
|
||||
if (minutes > 0) {
|
||||
return `${minutes}dk`;
|
||||
}
|
||||
return "<1dk";
|
||||
}
|
||||
if (lang === "de") {
|
||||
if (hours > 0) {
|
||||
return `${hours} Std. ${minutes} Min.`;
|
||||
}
|
||||
if (minutes > 0) {
|
||||
return `${minutes} Min.`;
|
||||
}
|
||||
return "<1 Min.";
|
||||
}
|
||||
if (hours > 0) {
|
||||
return `${hours}h ${minutes}m`;
|
||||
}
|
||||
if (minutes > 0) {
|
||||
return `${minutes}m`;
|
||||
}
|
||||
return "<1m";
|
||||
}
|
||||
const DISCORD_APP_CACHE_LIMIT = 50;
|
||||
|
||||
const discordAppCache = new Map<string, DiscordAppInfo | null>();
|
||||
|
||||
export function _resetDiscordAppCache(): void {
|
||||
discordAppCache.clear();
|
||||
}
|
||||
|
||||
function cacheDiscordApp(id: string, info: DiscordAppInfo): void {
|
||||
if (!discordAppCache.has(id) && discordAppCache.size >= DISCORD_APP_CACHE_LIMIT) {
|
||||
const oldest = discordAppCache.keys().next();
|
||||
if (!oldest.done) {
|
||||
discordAppCache.delete(oldest.value);
|
||||
}
|
||||
}
|
||||
discordAppCache.set(id, info);
|
||||
}
|
||||
|
||||
function useDiscordApp(applicationId: string | null | undefined): DiscordAppInfo | null {
|
||||
const [info, setInfo] = useState<DiscordAppInfo | null>(() =>
|
||||
applicationId ? (discordAppCache.get(applicationId) ?? null) : null,
|
||||
);
|
||||
const [prevId, setPrevId] = useState<string | null | undefined>(applicationId);
|
||||
if (prevId !== applicationId) {
|
||||
setPrevId(applicationId);
|
||||
setInfo(applicationId ? (discordAppCache.get(applicationId) ?? null) : null);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!applicationId || discordAppCache.has(applicationId)) {
|
||||
@@ -111,16 +100,12 @@ function useDiscordApp(applicationId: string | null | undefined): DiscordAppInfo
|
||||
const id = applicationId;
|
||||
let cancelled = false;
|
||||
async function load(): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(`/api/discord-app/${id}`);
|
||||
const json = (await response.json()) as DiscordAppInfo | { error: string };
|
||||
const resolved = response.ok && "iconUrl" in json ? (json as DiscordAppInfo) : null;
|
||||
discordAppCache.set(id, resolved);
|
||||
if (!cancelled) {
|
||||
setInfo(resolved);
|
||||
}
|
||||
} catch {
|
||||
discordAppCache.set(id, null);
|
||||
const resolved = await fetchJson<DiscordAppInfo>(`/api/discord-app/${id}`);
|
||||
if (resolved) {
|
||||
cacheDiscordApp(id, resolved);
|
||||
}
|
||||
if (!cancelled) {
|
||||
setInfo(resolved);
|
||||
}
|
||||
}
|
||||
void load();
|
||||
@@ -139,13 +124,13 @@ function ActivityRow({
|
||||
}: {
|
||||
activity: LanyardActivity;
|
||||
dict: Dictionary["discord"];
|
||||
locale: string;
|
||||
locale: Locale;
|
||||
}) {
|
||||
const appInfo = useDiscordApp(activity.application_id);
|
||||
const [profileOpen, setProfileOpen] = useState(false);
|
||||
const [heroSeed, setHeroSeed] = useState(0);
|
||||
const large = activityImageUrl(activity, false) ?? appInfo?.iconUrl ?? null;
|
||||
const small = activityImageUrl(activity, true);
|
||||
const large = activityImageUrl(activity, "large") ?? appInfo?.iconUrl ?? null;
|
||||
const small = activityImageUrl(activity, "small");
|
||||
const gameUrl = activity.application_id
|
||||
? (appInfo?.gameUrl ?? discordGameUrl(activity.application_id))
|
||||
: null;
|
||||
@@ -229,162 +214,9 @@ export function DiscordCard({
|
||||
}: {
|
||||
initial: LanyardData | null;
|
||||
dict: Dictionary["discord"];
|
||||
locale: string;
|
||||
locale: Locale;
|
||||
}) {
|
||||
const [data, setData] = useState<LanyardData | null>(initial);
|
||||
|
||||
useEffect(() => {
|
||||
let disposed = false;
|
||||
let socket: WebSocket | null = null;
|
||||
let heartbeatTimer: number | undefined;
|
||||
let reconnectTimer: number | undefined;
|
||||
let reconnectDelay = RECONNECT_BASE_MS;
|
||||
|
||||
async function pollOnce(): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(lanyardUrl(), { cache: "no-store" });
|
||||
if (!response.ok) {
|
||||
return;
|
||||
}
|
||||
const json = (await response.json()) as {
|
||||
success: boolean;
|
||||
data: LanyardData;
|
||||
};
|
||||
if (json.success && !disposed) {
|
||||
setData(json.data);
|
||||
}
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleReconnect(): void {
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
window.clearTimeout(reconnectTimer);
|
||||
reconnectTimer = window.setTimeout(() => {
|
||||
reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX_MS);
|
||||
connect();
|
||||
}, reconnectDelay);
|
||||
}
|
||||
|
||||
function dropSocket(current: WebSocket): void {
|
||||
if (socket === current) {
|
||||
socket = null;
|
||||
}
|
||||
try {
|
||||
current.close();
|
||||
} catch {
|
||||
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) => {
|
||||
let message: { op: number; t?: string; d?: unknown };
|
||||
try {
|
||||
message = JSON.parse(event.data as string) as {
|
||||
op: number;
|
||||
t?: string;
|
||||
d?: unknown;
|
||||
};
|
||||
} catch {
|
||||
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 ?? 30_000;
|
||||
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) {
|
||||
setData(message.d as LanyardData);
|
||||
}
|
||||
}
|
||||
};
|
||||
current.onerror = handleDrop;
|
||||
current.onclose = handleDrop;
|
||||
}
|
||||
|
||||
connect();
|
||||
const safetyTimer = window.setInterval(() => {
|
||||
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
||||
void pollOnce();
|
||||
}
|
||||
}, SAFETY_POLL_MS);
|
||||
|
||||
return () => {
|
||||
disposed = true;
|
||||
window.clearTimeout(reconnectTimer);
|
||||
window.clearInterval(safetyTimer);
|
||||
window.clearInterval(heartbeatTimer);
|
||||
if (socket) {
|
||||
const current = socket;
|
||||
socket = null;
|
||||
current.onclose = null;
|
||||
current.onerror = null;
|
||||
current.onmessage = null;
|
||||
try {
|
||||
current.close();
|
||||
} catch {
|
||||
socket = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
const data = useLanyard(initial);
|
||||
|
||||
if (!data) {
|
||||
return (
|
||||
|
||||
Reference in new issue
Block a user