reconexion basada en el UUID
This commit is contained in:
@@ -139,6 +139,8 @@ class ColyseusService {
|
|||||||
// Update current session id for correct role mapping
|
// Update current session id for correct role mapping
|
||||||
try { this.sessionId.value = (gameRoom as any).sessionId || this.sessionId.value; } catch {}
|
try { this.sessionId.value = (gameRoom as any).sessionId || this.sessionId.value; } catch {}
|
||||||
console.log('gameRoom.value is now:', this.gameRoom.value);
|
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
|
// 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 {}
|
try { this.sessionId.value = (gameRoom as any).sessionId || this.sessionId.value; } catch {}
|
||||||
this.gameRoom.value = gameRoom;
|
this.gameRoom.value = gameRoom;
|
||||||
this.currentRoom = 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
|
// 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 {
|
private getUuidFromPath(): string {
|
||||||
if (typeof window === 'undefined') return '';
|
if (typeof window === 'undefined') return '';
|
||||||
const path = window.location.pathname.replace(/^\/+/, '');
|
const path = window.location.pathname.replace(/^\/+/, '');
|
||||||
|
|||||||
@@ -115,6 +115,36 @@ onMounted(async () => {
|
|||||||
const room = await colyseusService.joinLobby();
|
const room = await colyseusService.joinLobby();
|
||||||
colorInput.value = colyseusService.playerColor.value || '#667eea';
|
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
|
// Keep color input synced with server-updated color
|
||||||
watch(() => colyseusService.playerColor.value, (c) => {
|
watch(() => colyseusService.playerColor.value, (c) => {
|
||||||
if (c && c !== colorInput.value) colorInput.value = c;
|
if (c && c !== colorInput.value) colorInput.value = c;
|
||||||
|
|||||||
@@ -226,6 +226,16 @@ export class GameRoom extends Room<GameState> {
|
|||||||
|
|
||||||
// Removed nextRound handler - rounds now auto-advance
|
// 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.onMessage("admin:kick", (client, playerId: string) => {
|
||||||
this.handleKick(playerId);
|
this.handleKick(playerId);
|
||||||
});
|
});
|
||||||
@@ -233,6 +243,44 @@ export class GameRoom extends Room<GameState> {
|
|||||||
|
|
||||||
onJoin(client: Client, options: any) {
|
onJoin(client: Client, options: any) {
|
||||||
console.log(`[GameRoom] ${client.sessionId} joined room ${this.roomId} with name: ${options.playerName}`);
|
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
|
// Special handling for shuffled players
|
||||||
if (this.isWaitingForShuffledPlayers && options.uuid) {
|
if (this.isWaitingForShuffledPlayers && options.uuid) {
|
||||||
@@ -246,8 +294,8 @@ export class GameRoom extends Room<GameState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Store UUID mapping if provided
|
// Store UUID mapping if provided
|
||||||
if (options.uuid) {
|
if (uuid) {
|
||||||
this.sessionToUuid.set(client.sessionId, options.uuid);
|
this.sessionToUuid.set(client.sessionId, uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the playerName passed from the lobby - don't generate a new one!
|
// 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);
|
const p = this.state.players.get(client.sessionId);
|
||||||
if (p) {
|
if (p) {
|
||||||
p.color = playerColor;
|
p.color = playerColor;
|
||||||
|
if (uuid) (p as any).uuid = uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
client.send("playerInfo", {
|
client.send("playerInfo", {
|
||||||
@@ -280,6 +329,14 @@ export class GameRoom extends Room<GameState> {
|
|||||||
if (this.state.players.size === 2 && this.state.gameStatus === GameStatus.WAITING) {
|
if (this.state.players.size === 2 && this.state.gameStatus === GameStatus.WAITING) {
|
||||||
this.startGame();
|
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) {
|
onLeave(client: Client, consented: boolean) {
|
||||||
@@ -400,6 +457,20 @@ export class GameRoom extends Room<GameState> {
|
|||||||
});
|
});
|
||||||
// Notify dashboard of game end
|
// Notify dashboard of game end
|
||||||
broadcastDashboardUpdate();
|
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() {
|
private resolveP2Action() {
|
||||||
|
|||||||
@@ -45,6 +45,32 @@ export class LobbyRoom extends Room<LobbyState> {
|
|||||||
if (options.uuid) {
|
if (options.uuid) {
|
||||||
this.sessionToUuid.set(client.sessionId, 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
|
// Check for shuffle redirect FIRST
|
||||||
if (NameManager.getInstance().isShuffleInProgress()) {
|
if (NameManager.getInstance().isShuffleInProgress()) {
|
||||||
const assignment = NameManager.getInstance().getPlayerRoomAssignment(options.uuid);
|
const assignment = NameManager.getInstance().getPlayerRoomAssignment(options.uuid);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Schema, type } from "@colyseus/schema";
|
|||||||
|
|
||||||
export class Player extends Schema {
|
export class Player extends Schema {
|
||||||
@type("string") sessionId: string = "";
|
@type("string") sessionId: string = "";
|
||||||
|
@type("string") uuid: string = "";
|
||||||
@type("string") name: string = "";
|
@type("string") name: string = "";
|
||||||
@type("number") clicks: number = 0;
|
@type("number") clicks: number = 0;
|
||||||
@type("boolean") connected: boolean = true;
|
@type("boolean") connected: boolean = true;
|
||||||
@@ -22,6 +23,7 @@ export class Player extends Schema {
|
|||||||
this.eloteTokens = 0;
|
this.eloteTokens = 0;
|
||||||
this.shameTokens = 0;
|
this.shameTokens = 0;
|
||||||
this.color = "#667eea";
|
this.color = "#667eea";
|
||||||
|
this.uuid = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
incrementClicks(): void {
|
incrementClicks(): void {
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ export class NameManager {
|
|||||||
// For shuffle functionality
|
// For shuffle functionality
|
||||||
private roomAssignments: Map<string, { roomId: string; role: 'P1' | 'P2' }> = new Map();
|
private roomAssignments: Map<string, { roomId: string; role: 'P1' | 'P2' }> = new Map();
|
||||||
private shuffleInProgress: boolean = false;
|
private shuffleInProgress: boolean = false;
|
||||||
|
private uuidToCurrentRoom: Map<string, string> = new Map();
|
||||||
|
private uuidToReconnectToken: Map<string, string> = new Map();
|
||||||
|
|
||||||
private constructor() {}
|
private constructor() {}
|
||||||
|
|
||||||
@@ -68,6 +70,34 @@ export class NameManager {
|
|||||||
return Array.from(this.uuidToName.values());
|
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
|
// Shuffle functionality methods
|
||||||
startShuffle(): void {
|
startShuffle(): void {
|
||||||
this.shuffleInProgress = true;
|
this.shuffleInProgress = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user