From b754ec043a4e933649c1f4e14899e54799efc109 Mon Sep 17 00:00:00 2001 From: josedario87 Date: Tue, 12 Aug 2025 17:35:57 -0600 Subject: [PATCH] =?UTF-8?q?Implementar=20sistema=20UUID=20para=20nombres?= =?UTF-8?q?=20=C3=BAnicos=20globales?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Agregado UUID persistente en base de datos local (LokiJS) - Nombres únicos por UUID en lugar de sessionId - Nombres persisten entre reconexiones mientras el servidor esté activo - Migración automática de perfiles existentes - Registrados handlers para evitar warnings de mensajes no encontrados --- client/src/services/colyseus.ts | 46 +++++++++++++++---------- client/src/services/db.ts | 30 +++++++++++++++-- client/src/views/DemoGame.vue | 9 +++++ server/src/rooms/LobbyRoom.ts | 59 +++++++++++++++++++++------------ server/src/utils/nameManager.ts | 33 +++++++++--------- 5 files changed, 122 insertions(+), 55 deletions(-) diff --git a/client/src/services/colyseus.ts b/client/src/services/colyseus.ts index 10aedd4..299c899 100644 --- a/client/src/services/colyseus.ts +++ b/client/src/services/colyseus.ts @@ -49,7 +49,11 @@ class ColyseusService { async joinLobby(): Promise { try { - const room = await this.client.joinOrCreate("lobby"); + // Initialize DB first to get UUID + await localDB.init(); + const uuid = localDB.getUUID(); + + const room = await this.client.joinOrCreate("lobby", { uuid }); this.lobbyRoom.value = room; this.currentRoom = room; // Require explicit confirmation each time we join the lobby (auto-confirm if saved name exists) @@ -58,22 +62,29 @@ class ColyseusService { room.onMessage("welcome", async (data) => { this.sessionId.value = data.sessionId; if (data.color) this.playerColor.value = data.color; - // Initialize local DB and optionally auto-apply saved profile - try { - await localDB.init(); - const profile = localDB.getLocalPlayer(); - // Apply saved color silently - if (profile?.color && profile.color !== this.playerColor.value) { - this.setPlayerColor(profile.color); + + // If server already has a name for us, use it + if (data.name) { + this.playerName.value = data.name; + this.nameConfirmed.value = true; + } else { + // Initialize local DB and optionally auto-apply saved profile + try { + await localDB.init(); + const profile = localDB.getLocalPlayer(); + // Apply saved color silently + if (profile?.color && profile.color !== this.playerColor.value) { + this.setPlayerColor(profile.color); + } + if (profile?.name) { + this.playerName.value = profile.name; + try { localDB.setName(profile.name); } catch {} + this.setPlayerName(profile.name); + this.nameConfirmed.value = true; + } + } catch (e) { + console.warn("Local DB init failed", e); } - if (profile?.name) { - this.playerName.value = profile.name; - try { localDB.setName(profile.name); } catch {} - this.setPlayerName(profile.name); - this.nameConfirmed.value = true; - } - } catch (e) { - console.warn("Local DB init failed", e); } }); @@ -119,7 +130,8 @@ class ColyseusService { async setPlayerName(name: string): Promise { if (this.lobbyRoom.value) { - this.lobbyRoom.value.send("setName", name); + const uuid = localDB.getUUID(); + this.lobbyRoom.value.send("setName", { name, uuid }); } } diff --git a/client/src/services/db.ts b/client/src/services/db.ts index 6b11ef4..026d5c4 100644 --- a/client/src/services/db.ts +++ b/client/src/services/db.ts @@ -2,6 +2,7 @@ import Loki from "lokijs"; export interface LocalPlayerDoc { id: string; // fixed id for local profile + uuid: string; // persistent unique identifier name: string; color: string; stats: { @@ -29,13 +30,19 @@ class LocalDBService { this.players = this.db!.addCollection("players", { unique: ["id"] }); } // Ensure local profile exists - if (!this.players.by("id", "local")) { + let localPlayer = this.players.by("id", "local"); + if (!localPlayer) { this.players.insert({ id: "local", + uuid: this.generateUUID(), name: "", color: "#667eea", stats: { totalClicks: 0, gamesPlayed: 0, wins: 0, losses: 0 } }); + } else if (!localPlayer.uuid) { + // Migrate existing profiles to have a UUID + localPlayer.uuid = this.generateUUID(); + this.players.update(localPlayer); } this.initialized = true; this.db!.saveDatabase(() => resolve()); @@ -85,6 +92,25 @@ class LocalDBService { this.col.update(doc); this.db?.saveDatabase(); } + + getUUID(): string { + const doc = this.getLocalPlayer(); + if (!doc.uuid) { + doc.uuid = this.generateUUID(); + this.col.update(doc); + this.db?.saveDatabase(); + } + return doc.uuid; + } + + private generateUUID(): string { + // Simple UUID v4 generator + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + const r = Math.random() * 16 | 0; + const v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + } } -export const localDB = new LocalDBService(); +export const localDB = new LocalDBService(); \ No newline at end of file diff --git a/client/src/views/DemoGame.vue b/client/src/views/DemoGame.vue index 2cbb559..0764787 100644 --- a/client/src/views/DemoGame.vue +++ b/client/src/views/DemoGame.vue @@ -194,6 +194,15 @@ onMounted(() => { room.onMessage("gameEnd", () => { try { if (typeof window !== 'undefined') { window.localStorage.removeItem('snatch.game.rtoken'); } } catch {} }); + + // Register additional message handlers to avoid warnings + room.onMessage("gamePaused", () => { + // Game paused, could update UI state if needed + }); + + room.onMessage("variantChanged", (data: { variant: string }) => { + currentVariant.value = data.variant as any; + }); } }); diff --git a/server/src/rooms/LobbyRoom.ts b/server/src/rooms/LobbyRoom.ts index cb45b00..3cae710 100644 --- a/server/src/rooms/LobbyRoom.ts +++ b/server/src/rooms/LobbyRoom.ts @@ -4,13 +4,14 @@ import { NameManager } from "../utils/nameManager"; export class LobbyRoom extends Room { private updateInterval?: NodeJS.Timeout; + private sessionToUuid: Map = new Map(); onCreate(options: any) { this.setState(new LobbyState()); this.setPrivate(false); - this.onMessage("setName", (client, playerName: string) => { - this.handleSetName(client, playerName); + this.onMessage("setName", (client, data: { name: string; uuid: string }) => { + this.handleSetName(client, data); }); this.onMessage("setColor", (client, color: string) => { @@ -31,14 +32,29 @@ export class LobbyRoom extends Room { } onJoin(client: Client, options: any) { - console.log(`[LobbyRoom] ${client.sessionId} joined lobby`); - // Do NOT assign a default name on join. Wait until client presses "Set Name". - this.state.addPlayer(client.sessionId, ""); - - client.send("welcome", { - sessionId: client.sessionId, - color: this.state.players.get(client.sessionId)?.color || "#667eea" - }); + console.log(`[LobbyRoom] ${client.sessionId} joined lobby with UUID: ${options.uuid}`); + + // Store UUID mapping if provided + if (options.uuid) { + this.sessionToUuid.set(client.sessionId, options.uuid); + + // Check if this UUID already has a name + const existingName = NameManager.getInstance().getPlayerName(options.uuid); + this.state.addPlayer(client.sessionId, existingName || ""); + + client.send("welcome", { + sessionId: client.sessionId, + name: existingName || "", + color: this.state.players.get(client.sessionId)?.color || "#667eea" + }); + } else { + // Fallback for clients without UUID (shouldn't happen in normal flow) + this.state.addPlayer(client.sessionId, ""); + client.send("welcome", { + sessionId: client.sessionId, + color: this.state.players.get(client.sessionId)?.color || "#667eea" + }); + } this.updateAvailableRooms(); } @@ -46,10 +62,8 @@ export class LobbyRoom extends Room { onLeave(client: Client, consented: boolean) { console.log(`[LobbyRoom] ${client.sessionId} left lobby`); - const player = this.state.players.get(client.sessionId); - if (player) { - NameManager.getInstance().releasePlayerName(client.sessionId); - } + // Clean up UUID mapping + this.sessionToUuid.delete(client.sessionId); this.state.removePlayer(client.sessionId); } @@ -61,18 +75,21 @@ export class LobbyRoom extends Room { clearInterval(this.updateInterval); } - this.state.players.forEach(player => { - NameManager.getInstance().releasePlayerName(player.sessionId); - }); + // Clear UUID mappings + this.sessionToUuid.clear(); } - private handleSetName(client: Client, playerName: string) { + private handleSetName(client: Client, data: { name: string; uuid: string }) { const currentPlayer = this.state.players.get(client.sessionId); if (!currentPlayer) return; - NameManager.getInstance().releasePlayerName(client.sessionId); + // Update UUID mapping if provided + if (data.uuid) { + this.sessionToUuid.set(client.sessionId, data.uuid); + } - const uniqueName = NameManager.getInstance().generateUniquePlayerName(playerName, client.sessionId); + const uuid = this.sessionToUuid.get(client.sessionId) || client.sessionId; + const uniqueName = NameManager.getInstance().generateUniquePlayerName(data.name, uuid); currentPlayer.name = uniqueName; @@ -189,4 +206,4 @@ export class LobbyRoom extends Room { console.error("[LobbyRoom] Error updating available rooms:", error); } } -} +} \ No newline at end of file diff --git a/server/src/utils/nameManager.ts b/server/src/utils/nameManager.ts index 94e6541..a450abd 100644 --- a/server/src/utils/nameManager.ts +++ b/server/src/utils/nameManager.ts @@ -1,7 +1,6 @@ export class NameManager { private static instance: NameManager; - private nameCounters: Map = new Map(); - private sessionToName: Map = new Map(); + private uuidToName: Map = new Map(); private constructor() {} @@ -12,16 +11,22 @@ export class NameManager { return NameManager.instance; } - generateUniquePlayerName(baseName: string, sessionId: string): string { + generateUniquePlayerName(baseName: string, uuid: string): string { + // If UUID already has a name, return it + const existingName = this.uuidToName.get(uuid); + if (existingName) { + return existingName; + } + const normalizedName = baseName.trim().toLowerCase(); if (!normalizedName) { // Default base name when none is provided - return this.generateUniquePlayerName('guest', sessionId); + return this.generateUniquePlayerName('guest', uuid); } // Try exact name if not in use; otherwise, append incremental suffixes const isInUse = (name: string) => { - for (const val of this.sessionToName.values()) { + for (const val of this.uuidToName.values()) { if (val === name) return true; } return false; @@ -34,22 +39,20 @@ export class NameManager { uniqueName = `${normalizedName}-${n}`; } - this.sessionToName.set(sessionId, uniqueName); + this.uuidToName.set(uuid, uniqueName); return uniqueName; } - releasePlayerName(sessionId: string): void { - const name = this.sessionToName.get(sessionId); - if (name) { - this.sessionToName.delete(sessionId); - } + releasePlayerName(uuid: string): void { + // Names are now persistent per UUID, so we don't release them + // They only get cleared when the server restarts } - getPlayerName(sessionId: string): string | undefined { - return this.sessionToName.get(sessionId); + getPlayerName(uuid: string): string | undefined { + return this.uuidToName.get(uuid); } getAllActivePlayers(): string[] { - return Array.from(this.sessionToName.values()); + return Array.from(this.uuidToName.values()); } -} +} \ No newline at end of file