Add discreet anticheat mechanism for the games
Build and deploy / deploy (push) Failing after 32s

This commit is contained in:
2026-08-24 12:07:10 +02:00
parent 35704e50fc
commit b7f3f8b0fa
6 changed files with 230 additions and 53 deletions
+5 -29
View File
@@ -1,12 +1,12 @@
"use client";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { Avatar } from "@/components/avatar";
import { Podium } from "@/components/podium";
import { IconClose, IconTrophy } from "@/components/icons";
import { computeRanksBy } from "@/lib/ranking";
import { createClient, waitForRealtimeAuth } from "@/lib/supabase/client";
import { IcarusGame } from "./icarus-game";
import { IcarusGame, type IcarusRunTelemetry } from "./icarus-game";
import type { IcarusScoreEntry } from "./page";
export function IcareView({
@@ -21,13 +21,6 @@ export function IcareView({
const [leaderboard, setLeaderboard] = useState(initialLeaderboard);
const [ownBestScore, setOwnBestScore] = useState(initialOwnBestScore);
const [showRanking, setShowRanking] = useState(false);
// Promesse plutôt qu'une simple valeur : évite une course si la partie se
// termine avant que start_icarus_run() n'ait eu le temps de répondre —
// handleFinish attend cette même promesse (déjà résolue dans l'immense
// majorité des cas, une partie dure toujours largement plus longtemps que
// cet aller-retour réseau).
const runIdPromiseRef = useRef<Promise<string | null>>(Promise.resolve(null));
const refetch = useCallback(async () => {
const supabase = createClient();
const { data: scores } = await supabase
@@ -73,26 +66,9 @@ export function IcareView({
};
}, [refetch]);
// Démarre le suivi serveur du temps de jeu (icarus_runs) dès le premier
// battement d'aile — soumis avec le score à la fin pour rejeter un score
// incohérent avec le temps réellement écoulé (voir submit_icarus_score,
// supabase/schema.sql).
function handleStart() {
async function handleFinish(score: number, run: IcarusRunTelemetry) {
const supabase = createClient();
runIdPromiseRef.current = (async () => {
try {
const { data } = await supabase.rpc("start_icarus_run");
return (data as string | null) ?? null;
} catch {
return null;
}
})();
}
async function handleFinish(score: number) {
const supabase = createClient();
const runId = await runIdPromiseRef.current;
const { data, error } = await supabase.rpc("submit_icarus_score", { p_score: score, p_run_id: runId });
const { data, error } = await supabase.rpc("submit_icarus_score", { p_score: score, p_run: run });
if (!error && data) {
setOwnBestScore(data.best_score);
refetch();
@@ -121,7 +97,7 @@ export function IcareView({
</button>
</div>
<IcarusGame onStart={handleStart} onFinish={handleFinish} />
<IcarusGame onFinish={handleFinish} />
{showRanking && (
<div className="fixed inset-0 z-30 flex items-center justify-center bg-ink/80 p-4 backdrop-blur-sm">