usuarios pre configurados

This commit is contained in:
2025-08-15 18:35:09 -06:00
parent 1c0d777699
commit 811f569391
12 changed files with 508 additions and 172 deletions

View File

@@ -8,24 +8,35 @@ const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
name: 'Lobby',
path: '/:uuid',
name: 'LobbyWithUuid',
component: Lobby
},
{
path: '/game',
name: 'Game',
path: '/:uuid/game',
name: 'GameWithUuid',
component: Game
},
{
path: '/demo',
name: 'DemoGame',
path: '/:uuid/demo',
name: 'DemoGameWithUuid',
component: DemoGame
},
{
path: '/dashboard',
name: 'Dashboard',
component: Dashboard
},
{
path: '/',
redirect: '/missing-uuid'
},
{
// simple fallback for users hitting root without UUID
path: '/missing-uuid',
component: {
template: `<div style="padding:20px;font-family:sans-serif"><h2>Falta UUID</h2><p>Abre el juego escaneando tu código QR: snatchgame.nucleoriofrio.com/{uuid}</p></div>`
}
}
]
});

View File

@@ -1,6 +1,5 @@
import { Client, Room } from "colyseus.js";
import { ref, Ref } from "vue";
import { localDB } from "./db";
export interface PlayerData {
sessionId: string;
@@ -25,8 +24,8 @@ class ColyseusService {
private client: Client;
private currentRoom: Room | null = null;
private apiBase: string;
private readonly LS_KEY_RECONNECT = "snatch.game.rtoken";
// No local storage tokens: UUID in URL identifies the user
public lobbyRoom: Ref<Room | null> = ref(null);
public gameRoom: Ref<Room | null> = ref(null);
public playerName: Ref<string> = ref("");
@@ -50,10 +49,8 @@ class ColyseusService {
async joinLobby(): Promise<Room> {
try {
// Initialize DB first to get UUID
await localDB.init();
const uuid = localDB.getUUID();
const uuid = this.getUuidFromPath();
const room = await this.client.joinOrCreate("lobby", { uuid });
this.lobbyRoom.value = room;
this.currentRoom = room;
@@ -69,36 +66,18 @@ class ColyseusService {
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);
}
// No client-side persistence; user must confirm name once per session
}
});
room.onMessage("nameUpdated", (data) => {
this.playerName.value = data.name;
try { localDB.setName(data.name); } catch {}
this.nameConfirmed.value = true;
});
room.onMessage("colorUpdated", (data) => {
if (data?.color) {
this.playerColor.value = data.color;
try { localDB.setColor(data.color); } catch {}
}
});
@@ -110,28 +89,13 @@ class ColyseusService {
}
async tryReconnectToOngoingGame(): Promise<Room | null> {
try {
const token = typeof window !== 'undefined' ? (window.localStorage.getItem(this.LS_KEY_RECONNECT) || "") : "";
if (!token) return null;
const room = await this.client.reconnect(token);
this.gameRoom.value = room;
this.currentRoom = room;
// Ensure local session id reflects the active room session
try { this.sessionId.value = (room as any).sessionId || this.sessionId.value; } catch {}
try { if (typeof window !== 'undefined') window.localStorage.setItem(this.LS_KEY_RECONNECT, (room as any).reconnectionToken || token); } catch {}
return room;
} catch (e) {
console.warn('Reconnection failed, clearing tokens');
try {
if (typeof window !== 'undefined') { window.localStorage.removeItem(this.LS_KEY_RECONNECT); }
} catch {}
return null;
}
// No stored reconnection tokens; rely on UUID flow and fresh joins
return Promise.resolve(null);
}
async setPlayerName(name: string): Promise<void> {
if (this.lobbyRoom.value) {
const uuid = localDB.getUUID();
const uuid = this.getUuidFromPath();
this.lobbyRoom.value.send("setName", { name, uuid });
}
}
@@ -156,9 +120,11 @@ class ColyseusService {
try {
// Join the game room directly using the roomId
console.log('Joining game room with name:', this.playerName.value);
const uuid = this.getUuidFromPath();
const gameRoom = await this.client.joinById(data.roomId, {
playerName: this.playerName.value,
playerColor: this.playerColor.value
playerColor: this.playerColor.value,
uuid
});
// Ensure the room id is set
@@ -172,7 +138,6 @@ class ColyseusService {
this.currentRoom = gameRoom;
// Update current session id for correct role mapping
try { this.sessionId.value = (gameRoom as any).sessionId || this.sessionId.value; } catch {}
try { if (typeof window !== 'undefined') window.localStorage.setItem(this.LS_KEY_RECONNECT, (gameRoom as any).reconnectionToken || ""); } catch {}
console.log('gameRoom.value is now:', this.gameRoom.value);
// Don't register message handlers here - let the Game component handle them
@@ -198,12 +163,13 @@ class ColyseusService {
async joinGameRoom(roomId: string): Promise<Room> {
try {
const uuid = this.getUuidFromPath();
const gameRoom = await this.client.joinById(roomId, {
playerName: this.playerName.value,
playerColor: this.playerColor.value
playerName: this.playerName.value,
playerColor: this.playerColor.value,
uuid
});
try { this.sessionId.value = (gameRoom as any).sessionId || this.sessionId.value; } catch {}
try { if (typeof window !== 'undefined') window.localStorage.setItem(this.LS_KEY_RECONNECT, (gameRoom as any).reconnectionToken || ""); } catch {}
this.gameRoom.value = gameRoom;
this.currentRoom = gameRoom;
@@ -291,7 +257,6 @@ class ColyseusService {
this.currentRoom = null;
}
}
try { if (typeof window !== 'undefined') { window.localStorage.removeItem(this.LS_KEY_RECONNECT); } } catch {}
}
leaveCurrentRoom(): void {
@@ -350,6 +315,13 @@ class ColyseusService {
return null;
}
}
private getUuidFromPath(): string {
if (typeof window === 'undefined') return '';
const path = window.location.pathname.replace(/^\/+/, '');
const seg = path.split('/')[0] || '';
return seg;
}
}
export const colyseusService = new ColyseusService();

View File

@@ -68,7 +68,7 @@
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import { useRouter } from 'vue-router';
import { useRouter, useRoute } from 'vue-router';
import { colyseusService } from '../services/colyseus';
import { getStateCallbacks } from 'colyseus.js';
@@ -81,6 +81,8 @@ import PlayerStats from './games/PlayerStats.vue';
import ChatWidget from './games/ChatWidget.vue';
const router = useRouter();
const route = useRoute();
const routeUuid = computed(() => (route.params as any)?.uuid as string || '');
const players = ref<any[]>([]);
const gameStatus = ref('waiting');
@@ -126,14 +128,7 @@ const currentComponent = computed(() => componentMap[currentVariant.value]);
onMounted(() => {
let room = colyseusService.gameRoom.value;
if (!room) {
// Try reconnection first
colyseusService.tryReconnectToOngoingGame().then(r => {
if (!r) {
router.push('/');
return;
}
setupRoom(r);
});
router.push(`/${routeUuid.value}`);
return;
}
@@ -181,7 +176,7 @@ onMounted(() => {
$(player).listen("shameTokens", (v: number) => { const p = players.value.find(x => x.sessionId === key); if (p) p.shameTokens = v; });
$(player).listen("color", (v: string) => { const p = players.value.find(x => x.sessionId === key); if (p) p.color = v; });
});
$(room.state).players.onRemove((player: any, key: string) => {
$(room.state).players.onRemove((_: any, key: string) => {
const i = players.value.findIndex(p => p.sessionId === key);
if (i !== -1) players.value.splice(i, 1);
});
@@ -192,7 +187,7 @@ onMounted(() => {
});
room.onMessage("gameEnd", () => {
try { if (typeof window !== 'undefined') { window.localStorage.removeItem('snatch.game.rtoken'); } } catch {}
// no-op for local storage
});
// Register additional message handlers to avoid warnings
@@ -209,7 +204,7 @@ onMounted(() => {
});
// Handle room closure/disconnection
room.onLeave((code) => {
room.onLeave((code: number) => {
console.log('[DemoGame] Room disconnected with code:', code);
// Handle shuffle disconnection specially
@@ -223,36 +218,25 @@ onMounted(() => {
} catch {}
// Redirect to lobby and let it handle the shuffle redirect
router.push('/');
router.push(`/${routeUuid.value}`);
return;
}
// Normal disconnection handling
// Always clean up local storage when room closes
try {
if (typeof window !== 'undefined') {
window.localStorage.removeItem('snatch.game.roomId');
window.localStorage.removeItem('snatch.game.sessionId');
}
} catch {}
// no-op for local storage cleanup
// If not on lobby page, redirect there
if (router.currentRoute.value.path !== '/') {
if (router.currentRoute.value.path !== `/${routeUuid.value}`) {
console.log('[DemoGame] Room closed, redirecting to lobby');
router.push('/');
router.push(`/${routeUuid.value}`);
}
});
room.onError((code, message) => {
room.onError((code: number, message: any) => {
console.error('[DemoGame] Room error:', code, message);
// On error, also redirect to lobby
try {
if (typeof window !== 'undefined') {
window.localStorage.removeItem('snatch.game.roomId');
window.localStorage.removeItem('snatch.game.sessionId');
}
} catch {}
router.push('/');
// On error, redirect to lobby
router.push(`/${routeUuid.value}`);
});
}
});
@@ -276,8 +260,7 @@ function onAssignShame(val: boolean) { colyseusService.assignShame(val); }
function leaveGame() {
console.log('[DemoGame] User manually leaving game');
colyseusService.leaveGame();
try { if (typeof window !== 'undefined') { window.localStorage.removeItem('snatch.game.roomId'); window.localStorage.removeItem('snatch.game.sessionId'); } } catch {}
router.push('/');
router.push(`/${routeUuid.value}`);
}
</script>

View File

@@ -75,12 +75,13 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue';
import { useRouter } from 'vue-router';
import { useRouter, useRoute } from 'vue-router';
import { colyseusService } from '../services/colyseus';
import { localDB } from '../services/db';
import { getStateCallbacks } from 'colyseus.js';
const router = useRouter();
const route = useRoute();
const routeUuid = computed(() => (route.params as any)?.uuid as string || '');
const players = ref<any[]>([]);
const gameStatus = ref('waiting');
const timeRemaining = ref(600);
@@ -99,16 +100,8 @@ onMounted(() => {
console.log('Current game room:', room);
if (!room) {
console.warn('No game room found, trying reconnection...');
// Attempt reconnection if tokens are available
colyseusService.tryReconnectToOngoingGame().then((r) => {
if (!r) {
console.error('Reconnection failed, redirecting to lobby...');
router.push('/');
return;
}
setupRoom(r);
});
console.warn('No game room found, redirecting to lobby...');
router.push(`/${routeUuid.value}`);
return;
}
@@ -120,7 +113,7 @@ onMounted(() => {
const $ = getStateCallbacks(room);
// Wait for the initial state sync (do not manually push players here to avoid duplicates)
room.onStateChange.once((state) => {
room.onStateChange.once((state: any) => {
console.log('Initial state received:', state);
gameStatus.value = state.gameStatus || 'waiting';
timeRemaining.value = state.timeRemaining || 600;
@@ -164,14 +157,14 @@ onMounted(() => {
});
});
$(room.state).players.onRemove((player: any, key: string) => {
$(room.state).players.onRemove((_: any, key: string) => {
const index = players.value.findIndex(p => p.sessionId === key);
if (index !== -1) {
players.value.splice(index, 1);
}
});
room.onMessage("playerInfo", (info) => {
room.onMessage("playerInfo", (info: any) => {
console.log('Received playerInfo:', info);
colyseusService.sessionId.value = info.sessionId;
colyseusService.playerName.value = info.name;
@@ -182,10 +175,9 @@ onMounted(() => {
gameStatus.value = 'playing';
});
room.onMessage("gameEnd", (data) => {
console.log("Game ended!", data);
room.onMessage("gameEnd", () => {
console.log("Game ended!");
gameStatus.value = 'finished';
try { if (typeof window !== 'undefined') { window.localStorage.removeItem('snatch.game.rtoken'); } } catch {}
});
room.onMessage("gamePaused", () => {
@@ -207,18 +199,17 @@ onUnmounted(() => {
}
});
function handleClick() {
if (gameStatus.value !== 'playing') return;
colyseusService.sendClick();
try { localDB.incClicks(1); } catch {}
isClicking.value = true;
clearTimeout(clickTimeout);
clickTimeout = setTimeout(() => {
isClicking.value = false;
}, 100);
}
function handleClick() {
if (gameStatus.value !== 'playing') return;
colyseusService.sendClick();
isClicking.value = true;
clearTimeout(clickTimeout);
clickTimeout = setTimeout(() => {
isClicking.value = false;
}, 100);
}
function formatTime(seconds: number): string {
const mins = Math.floor(seconds / 60);
@@ -228,7 +219,7 @@ function formatTime(seconds: number): string {
function leaveGame() {
colyseusService.leaveGame();
router.push('/');
router.push(`/${routeUuid.value}`);
}
</script>

View File

@@ -83,12 +83,13 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed, watch } from 'vue';
import PlayerStats from './games/PlayerStats.vue';
import { useRouter } from 'vue-router';
import { useRouter, useRoute } from 'vue-router';
import { colyseusService } from '../services/colyseus';
import { getStateCallbacks } from 'colyseus.js';
import { localDB } from '../services/db';
const router = useRouter();
const route = useRoute();
const routeUuid = computed(() => (route.params as any)?.uuid as string || '');
const inputName = ref('');
const isJoining = ref(false);
const colorInput = ref('#667eea');
@@ -111,25 +112,9 @@ const previewPlayer = computed(() => ({
onMounted(async () => {
try {
// Try reconnect to an ongoing game first
const reconnected = await colyseusService.tryReconnectToOngoingGame();
if (reconnected) {
router.push('/demo');
return;
}
const room = await colyseusService.joinLobby();
colorInput.value = colyseusService.playerColor.value || '#667eea';
// Initialize local DB and prefill inputs if available
try {
await localDB.init();
const profile = localDB.getLocalPlayer();
if (profile?.name) inputName.value = profile.name;
if (profile?.color) colorInput.value = profile.color;
} catch (e) {
console.warn('Local DB not available', e);
}
// Keep color input synced with server-updated color
watch(() => colyseusService.playerColor.value, (c) => {
if (c && c !== colorInput.value) colorInput.value = c;
@@ -190,7 +175,6 @@ onUnmounted(() => {
async function updateName() {
// Send even if empty; server will assign a default unique name when empty
const name = inputName.value.trim();
try { localDB.setName(name); } catch {}
await colyseusService.setPlayerName(name);
}
@@ -205,7 +189,7 @@ async function handleQuickPlay() {
console.log('Starting quickPlay...');
try {
const gameRoom = await colyseusService.quickPlay();
console.log('Game room joined:', gameRoom?.id, 'Full room object:', gameRoom);
console.log('Game room joined. Full room object:', gameRoom);
// Leave the lobby room before navigating
if (colyseusService.lobbyRoom.value) {
@@ -214,8 +198,8 @@ async function handleQuickPlay() {
colyseusService.lobbyRoom.value = null;
}
console.log('Navigating to /demo...');
await router.push('/demo');
console.log('Navigating to /:uuid/demo...');
await router.push(`/${routeUuid.value}/demo`);
console.log('Navigation complete');
} catch (error) {
console.error('Failed to join game:', error);
@@ -234,7 +218,7 @@ async function joinRoom(roomId: string) {
colyseusService.lobbyRoom.value.leave();
colyseusService.lobbyRoom.value = null;
}
router.push('/demo');
router.push(`/${routeUuid.value}/demo`);
} catch (error) {
console.error('Failed to join room:', error);
isJoining.value = false;

View File

@@ -6,15 +6,21 @@ export default defineConfig({
server: {
host: '0.0.0.0',
port: 3004,
allowedHosts: ['z590.interno.com'],
allowedHosts: ['z590.interno.com', 'snatchgame.interno.com'],
cors: {
origin: ['http://localhost:3004', 'http://z590.interno.com:3004']
},
proxy: {
'/api': {
target: 'http://localhost:3000',
target: process.env.VITE_DEV_API_PROXY_TARGET || 'http://localhost:3000',
changeOrigin: true
},
'/ws': {
target: process.env.VITE_DEV_WS_PROXY_TARGET || 'http://localhost:3000',
changeOrigin: true,
ws: true,
rewrite: (path) => path.replace(/^\/ws/, '')
}
}
}
})
})