// Events Socket.IO typés, partagés par le serveur et le client. // Convention : `CLIENT → SERVER` et `SERVER → CLIENT`. Tout timestamp est serveur. import type { Answer, BlindtestMode, GameType, MixMode, PlayerScore, RoomSnapshot, RoundConfig, RoundRecap, RoundType, } from "./domain" import type { BlindtestTrackInfo } from "./blindtest" // --- Payloads ------------------------------------------------------------ export interface RoomCreatePayload { playerName?: string } export interface RoomCreatedPayload { roomCode: string playerId: string /** Jeton secret pour se reconnecter après un refresh/coupure. */ token: string } export interface RejoinPayload { roomCode: string playerId: string token: string } export interface RoomJoinPayload { roomCode: string playerName?: string } export interface SetNamePayload { name: string } export interface UpdateSettingsPayload { gameType: GameType mixedModes: MixMode[] blindtestMode: BlindtestMode roundDuration: number tracksPerPlayer: number categories: string[] rounds: RoundConfig[] } export interface PlayerRefPayload { playerId: string } export interface SubmitTrackPayload { youtubeUrl: string } export interface SubmitOkPayload { accepted: boolean reason?: string /** Id du titre créé (pour pouvoir le supprimer). */ trackId?: string youtubeId?: string /** Titre récupéré (oEmbed) si accepté — pour confirmation côté client. */ title?: string /** Nombre total de titres soumis par ce joueur après cet ajout. */ count?: number } export interface RemoveTrackPayload { trackId: string } export interface RoundStartPayload { type: RoundType djId?: string /** Timestamp serveur de début des réponses (après le délai de préparation / transition). */ startsAt: number /** Timestamp serveur de fin de manche (startsAt + roundDuration). */ endsAt: number /** Envoyé uniquement au contributeur du titre (blindtest) : "c'est le tien". */ mine?: boolean /** Payload spécifique au type d'épreuve, SANS la réponse. */ payload: unknown } export type MediaAction = "play" | "pause" | "seek" /** Contrôle média émis par le DJ uniquement. */ export interface MediaControlPayload { action: MediaAction positionSec: number } /** Diffusion de recalage à tous les clients. */ export interface MediaSyncPayload { action: MediaAction positionSec: number atServerTs: number } export interface VotePayload { answer: Answer } /** Progression des votes. `voted` = playerIds ayant validé (jamais leur réponse). */ export interface VoteAckPayload { count: number total: number voted: string[] } export interface RevealPayload { truth: unknown perPlayerResult: unknown } export interface ScoreUpdatePayload { scores: PlayerScore[] } export interface GameEndPayload { finalScores: PlayerScore[] /** Titres joués (si la partie comportait du blindtest) — pour récupérer les liens. */ tracks?: BlindtestTrackInfo[] /** Récapitulatif de toutes les manches (question + réponse + qui a marqué). */ recap?: RoundRecap[] spotifyExport?: unknown } export interface ErrorPayload { code: string message: string } // --- Maps d'events Socket.IO --------------------------------------------- /** Ack standard renvoyé par le serveur sur les commandes critiques. */ export type Ack = (result: { ok: true; data: T } | { ok: false; error: ErrorPayload }) => void /** CLIENT → SERVER */ export interface ClientToServerEvents { "room:create": (payload: RoomCreatePayload, ack: Ack) => void "room:join": (payload: RoomJoinPayload, ack: Ack) => void "room:rejoin": (payload: RejoinPayload, ack: Ack) => void "player:setName": (payload: SetNamePayload) => void "lobby:updateSettings": (payload: UpdateSettingsPayload) => void "lobby:return": () => void "game:start": (ack: Ack) => void "blindtest:submitTrack": ( payload: SubmitTrackPayload, ack: (res: SubmitOkPayload) => void ) => void "blindtest:removeTrack": ( payload: RemoveTrackPayload, ack: (res: { ok: boolean }) => void ) => void "media:control": (payload: MediaControlPayload) => void "round:vote": (payload: VotePayload) => void } /** SERVER → CLIENT */ export interface ServerToClientEvents { "room:state": (payload: RoomSnapshot) => void "player:left": (payload: PlayerRefPayload) => void "player:rejoined": (payload: PlayerRefPayload) => void "blindtest:submitOk": (payload: SubmitOkPayload) => void "round:start": (payload: RoundStartPayload) => void "media:sync": (payload: MediaSyncPayload) => void "round:voteAck": (payload: VoteAckPayload) => void "round:reveal": (payload: RevealPayload) => void "score:update": (payload: ScoreUpdatePayload) => void "game:end": (payload: GameEndPayload) => void "error": (payload: ErrorPayload) => void } /** Events serveur ↔ serveur (inutilisés en V1, pas de Redis adapter). */ export type InterServerEvents = Record /** Données attachées à chaque socket côté serveur. */ export interface SocketData { playerId?: string roomCode?: string }