reconexion basada en el UUID

This commit is contained in:
2025-08-15 18:51:46 -06:00
parent 811f569391
commit 310fb3455a
6 changed files with 176 additions and 2 deletions

View File

@@ -226,6 +226,16 @@ export class GameRoom extends Room<GameState> {
// Removed nextRound handler - rounds now auto-advance
// Persist reconnection token per UUID (sent by client after join)
this.onMessage("registerReconnection", (client, token: string) => {
const uuid = this.sessionToUuid.get(client.sessionId);
if (!uuid) return;
try {
const { NameManager } = require("../utils/nameManager");
NameManager.getInstance().setReconnectToken(uuid, (token || '').toString());
} catch {}
});
this.onMessage("admin:kick", (client, playerId: string) => {
this.handleKick(playerId);
});
@@ -233,6 +243,44 @@ export class GameRoom extends Room<GameState> {
onJoin(client: Client, options: any) {
console.log(`[GameRoom] ${client.sessionId} joined room ${this.roomId} with name: ${options.playerName}`);
const uuid = options?.uuid as string | undefined;
// UUID-based reconnection: if game already started or room is full, allow join if UUID matches a participant
if ((this.state.gameStatus !== GameStatus.WAITING || this.state.players.size >= 2) && uuid) {
// Try to find a matching player by UUID
let foundKey: string | null = null;
this.state.players.forEach((p, key) => {
if ((p as any).uuid && (p as any).uuid === uuid) {
foundKey = key;
}
});
if (foundKey) {
// Rebind player to new sessionId
const player = this.state.players.get(foundKey!);
if (player) {
this.state.players.delete(foundKey!);
player.sessionId = client.sessionId;
player.connected = true;
this.state.players.set(client.sessionId, player);
if (this.state.p1Id === foundKey) this.state.p1Id = client.sessionId;
if (this.state.p2Id === foundKey) this.state.p2Id = client.sessionId;
this.sessionToUuid.set(client.sessionId, uuid);
// Let client know identity
client.send("playerInfo", { sessionId: client.sessionId, name: player.name, roomId: this.roomId });
// If paused and both connected, resume
if (this.state.gameStatus === GameStatus.PAUSED && this.getConnectedPlayersCount() === 2) {
this.state.resumeGame();
this.setMetadata({
gameStatus: 'playing',
currentRound: this.state.currentRound,
currentVariant: this.state.currentVariant
});
broadcastDashboardUpdate();
}
return; // Do not proceed with normal join flow
}
}
}
// Special handling for shuffled players
if (this.isWaitingForShuffledPlayers && options.uuid) {
@@ -246,8 +294,8 @@ export class GameRoom extends Room<GameState> {
}
// Store UUID mapping if provided
if (options.uuid) {
this.sessionToUuid.set(client.sessionId, options.uuid);
if (uuid) {
this.sessionToUuid.set(client.sessionId, uuid);
}
// Use the playerName passed from the lobby - don't generate a new one!
@@ -259,6 +307,7 @@ export class GameRoom extends Room<GameState> {
const p = this.state.players.get(client.sessionId);
if (p) {
p.color = playerColor;
if (uuid) (p as any).uuid = uuid;
}
client.send("playerInfo", {
@@ -280,6 +329,14 @@ export class GameRoom extends Room<GameState> {
if (this.state.players.size === 2 && this.state.gameStatus === GameStatus.WAITING) {
this.startGame();
}
// Persist current room mapping by UUID
if (uuid) {
try {
const { NameManager } = require("../utils/nameManager");
NameManager.getInstance().setCurrentRoom(uuid, this.roomId);
} catch {}
}
}
onLeave(client: Client, consented: boolean) {
@@ -400,6 +457,20 @@ export class GameRoom extends Room<GameState> {
});
// Notify dashboard of game end
broadcastDashboardUpdate();
// Clear UUID -> current room mapping and reconnection tokens for participants
try {
const { NameManager } = require("../utils/nameManager");
const toClear: string[] = [];
this.state.players.forEach((p) => {
const u = (p as any)?.uuid;
if (u) toClear.push(u);
});
toClear.forEach(u => {
NameManager.getInstance().clearCurrentRoom(u);
NameManager.getInstance().clearReconnectToken(u);
});
} catch {}
}
private resolveP2Action() {

View File

@@ -44,6 +44,32 @@ export class LobbyRoom extends Room<LobbyState> {
// Store UUID mapping if provided
if (options.uuid) {
this.sessionToUuid.set(client.sessionId, options.uuid);
// If this UUID has a current active game room assignment, redirect there
try {
const currentRoomId = NameManager.getInstance().getCurrentRoom(options.uuid);
if (currentRoomId) {
// Verify room exists and is not finished
matchMaker.query({ roomId: currentRoomId }).then((rooms) => {
const room = rooms[0];
const status = room?.metadata?.gameStatus || "waiting";
if (room && status !== "finished") {
const token = NameManager.getInstance().getReconnectToken(options.uuid);
if (token) {
// Prefer reconnection token to bypass room lock
client.send("resumeReconnection", { token });
} else {
// Fallback to joinById path
client.send("resumeGame", { roomId: currentRoomId });
}
// Mark as in game on lobby state to avoid duplicate quickPlay
const p = this.state.players.get(client.sessionId);
if (p) p.inGame = true;
return;
}
}).catch(() => {});
}
} catch {}
// Check for shuffle redirect FIRST
if (NameManager.getInstance().isShuffleInProgress()) {

View File

@@ -2,6 +2,7 @@ import { Schema, type } from "@colyseus/schema";
export class Player extends Schema {
@type("string") sessionId: string = "";
@type("string") uuid: string = "";
@type("string") name: string = "";
@type("number") clicks: number = 0;
@type("boolean") connected: boolean = true;
@@ -22,6 +23,7 @@ export class Player extends Schema {
this.eloteTokens = 0;
this.shameTokens = 0;
this.color = "#667eea";
this.uuid = "";
}
incrementClicks(): void {

View File

@@ -6,6 +6,8 @@ export class NameManager {
// For shuffle functionality
private roomAssignments: Map<string, { roomId: string; role: 'P1' | 'P2' }> = new Map();
private shuffleInProgress: boolean = false;
private uuidToCurrentRoom: Map<string, string> = new Map();
private uuidToReconnectToken: Map<string, string> = new Map();
private constructor() {}
@@ -68,6 +70,34 @@ export class NameManager {
return Array.from(this.uuidToName.values());
}
// Current game room assignment (for reconnection by UUID)
setCurrentRoom(uuid: string, roomId: string): void {
if (!uuid || !roomId) return;
this.uuidToCurrentRoom.set(uuid, roomId);
}
getCurrentRoom(uuid: string): string | undefined {
return this.uuidToCurrentRoom.get(uuid);
}
clearCurrentRoom(uuid: string): void {
this.uuidToCurrentRoom.delete(uuid);
}
// Reconnection token per UUID (to join locked rooms)
setReconnectToken(uuid: string, token: string): void {
if (!uuid || !token) return;
this.uuidToReconnectToken.set(uuid, token);
}
getReconnectToken(uuid: string): string | undefined {
return this.uuidToReconnectToken.get(uuid);
}
clearReconnectToken(uuid: string): void {
this.uuidToReconnectToken.delete(uuid);
}
// Shuffle functionality methods
startShuffle(): void {
this.shuffleInProgress = true;