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

@@ -139,6 +139,8 @@ class ColyseusService {
// Update current session id for correct role mapping
try { this.sessionId.value = (gameRoom as any).sessionId || this.sessionId.value; } catch {}
console.log('gameRoom.value is now:', this.gameRoom.value);
// Register reconnection token on server for this UUID
try { (gameRoom as any).send("registerReconnection", (gameRoom as any).reconnectionToken || ""); } catch {}
// Don't register message handlers here - let the Game component handle them
@@ -172,6 +174,8 @@ class ColyseusService {
try { this.sessionId.value = (gameRoom as any).sessionId || this.sessionId.value; } catch {}
this.gameRoom.value = gameRoom;
this.currentRoom = gameRoom;
// Register reconnection token on server for this UUID
try { (gameRoom as any).send("registerReconnection", (gameRoom as any).reconnectionToken || ""); } catch {}
// Don't register message handlers here - let the Game component handle them
@@ -316,6 +320,17 @@ class ColyseusService {
}
}
// Used when lobby provides a reconnection token to resume a locked room
async reconnectWithToken(token: string): Promise<Room> {
const room = await (this.client as any).reconnect(token);
this.gameRoom.value = room;
this.currentRoom = room;
try { this.sessionId.value = (room as any).sessionId || this.sessionId.value; } catch {}
// Refresh reconnection token mapping on server
try { (room as any).send("registerReconnection", (room as any).reconnectionToken || ""); } catch {}
return room;
}
private getUuidFromPath(): string {
if (typeof window === 'undefined') return '';
const path = window.location.pathname.replace(/^\/+/, '');

View File

@@ -115,6 +115,36 @@ onMounted(async () => {
const room = await colyseusService.joinLobby();
colorInput.value = colyseusService.playerColor.value || '#667eea';
// Prefer reconnection token path to bypass locked rooms
room.onMessage("resumeReconnection", async (data: any) => {
try {
await colyseusService.reconnectWithToken(data.token);
// Leave lobby before navigating
if (colyseusService.lobbyRoom.value) {
colyseusService.lobbyRoom.value.leave();
colyseusService.lobbyRoom.value = null;
}
await router.push(`/${routeUuid.value}/demo`);
} catch (error) {
console.error('Reconnection failed:', error);
}
});
// Listen for server-initiated resume to existing game (fallback joinById)
room.onMessage("resumeGame", async (data: any) => {
try {
const gameRoom = await colyseusService.joinGameRoom(data.roomId);
// Leave lobby before navigating
if (colyseusService.lobbyRoom.value) {
colyseusService.lobbyRoom.value.leave();
colyseusService.lobbyRoom.value = null;
}
await router.push(`/${routeUuid.value}/demo`);
} catch (error) {
console.error('Auto-join failed:', error);
}
});
// Keep color input synced with server-updated color
watch(() => colyseusService.playerColor.value, (c) => {
if (c && c !== colorInput.value) colorInput.value = c;

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;