Skip to content

Merge feature/socket-reconnect to main, redis implementation done till game waiting room#23

Open
vishalpokuri wants to merge 34 commits intomainfrom
feature/socket-reconnect
Open

Merge feature/socket-reconnect to main, redis implementation done till game waiting room#23
vishalpokuri wants to merge 34 commits intomainfrom
feature/socket-reconnect

Conversation

@vishalpokuri
Copy link
Copy Markdown
Collaborator

No description provided.

ayush4345 and others added 30 commits November 18, 2025 16:49
* Initial plan

* Add Redis pub-sub support for scalable backend architecture

Co-authored-by: ayush4345 <99096397+ayush4345@users.noreply.github.com>

* Remove logs from git tracking and update .gitignore

Co-authored-by: ayush4345 <99096397+ayush4345@users.noreply.github.com>

* Fix async API consistency in gameStateManager functions

Co-authored-by: ayush4345 <99096397+ayush4345@users.noreply.github.com>

* fix the user management bug

* Fix WebSocket idle connection timeout on /play page

Co-authored-by: ayush4345 <99096397+ayush4345@users.noreply.github.com>

* Refactor /play page to use global socketManager instead of creating its own socket connection

Co-authored-by: ayush4345 <99096397+ayush4345@users.noreply.github.com>

* remove uneccesary long lines

* remove unnecessary docs

* refactor

* fix the user management bug

* fix the import error of abi

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: ayush4345 <99096397+ayush4345@users.noreply.github.com>
Co-authored-by: ayushk4543 <sanusingh335@gmail.com>
Co-authored-by: ayush4345 <99096397+ayush4345@users.noreply.github.com>
Co-authored-by: ayush4345 <99096397+ayush4345@users.noreply.github.com>
Co-authored-by: ayush4345 <99096397+ayush4345@users.noreply.github.com>
Co-authored-by: ayush4345 <99096397+ayush4345@users.noreply.github.com>
Co-authored-by: ayush4345 <99096397+ayush4345@users.noreply.github.com>
Co-authored-by: ayush4345 <99096397+ayush4345@users.noreply.github.com>
Co-authored-by: ayush4345 <99096397+ayush4345@users.noreply.github.com>
Co-authored-by: ayush4345 <99096397+ayush4345@users.noreply.github.com>
…ased user management

- Add Redis-based user storage with UUID generation for global user accounts
- Implement registerOrLoginUser system: creates new account on first connect, returns existing on subsequent connections
- Refactor storage layer into modular architecture:
  - BaseRedisStorage: shared Redis client and operations
  - UserStorage: user account and room management with Redis + in-memory fallback
  - GameStorage: game state persistence with Redis + disk backup
- Add smart logging utility (log.ts): console.log in dev, winston in production
- Update User interface to support optional room (users exist before joining rooms)
- Add socket event handlers for user registration when wallet connects
- Integrate wallet connection with user registration in frontend
- Add Docker support with Dockerfile and docker-compose.yml
- Install uuid package for unique user ID generation

Redis Keys:
  - user:${userId}: User object with wallet, name, status, timestamps
  - users:all: Set of all registered user IDs
  - room:users:${roomId}: Array of user IDs in seat order

Breaking Changes:
  - Removed legacy users.ts, gameStateManager.ts, redisStorage.ts
  - User.room is now optional (users can exist without being in a room)
  - All storage methods now use dependency injection pattern
…andler

- Set socketId when user registers/logs in via registerOrLoginUser
- Update user's room field in database when joining game room via joinRoom
- Ensure socketId is properly tracked across socket connections
- Fix user lookup to use getUserBySocketId for current connection state
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements comprehensive socket reconnection functionality with Redis-based state persistence for the Zunno multiplayer UNO game. The changes include a complete backend migration to TypeScript, enhanced socket management with automatic reconnection, wallet-based user persistence, and UI improvements for connection status visibility.

Key changes:

  • Socket reconnection with exponential backoff and action buffering
  • Redis integration for persistent game state and user management
  • Backend migration from JavaScript to TypeScript with modular architecture
  • Wallet-based user identification for cross-session continuity

Reviewed changes

Copilot reviewed 67 out of 73 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
frontend/src/services/socketManager.ts New socket manager with reconnection, heartbeat, and buffering
frontend/src/services/socket.ts Socket proxy wrapper for stale reference prevention
frontend/src/utils/walletStorage.ts LocalStorage utilities for wallet persistence
frontend/src/context/SocketConnectionContext.tsx React context for global connection state
frontend/src/components/ConnectionStatusIndicator.tsx UI component showing connection status
frontend/src/components/gameroom/Room.tsx Enhanced with reconnection and state sync logic
frontend/src/components/gameroom/Game.js Refactored game logic with action buffering
frontend/src/constants/gameConstants.ts Shared game constants (MAX_PLAYERS = 3)
backend/index.ts New TypeScript entry point with modular structure
backend/services/storage/* Redis-based storage layer for users and games
backend/socket/* Modular socket event handlers
backend/config/* Configuration for Redis and Socket.IO
backend/types/users.ts TypeScript type definitions for users
frontend/src/app/provider.jsx Added SocketConnectionProvider
contracts/script/deploy.s.sol Commented out console.log
Multiple UI files Height changed from 100vh to 100svh, positioning adjustments
Files not reviewed (1)
  • backend/pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const CONNECTION =
process.env.NEXT_PUBLIC_WEBSOCKET_URL ||
"https://zkuno-669372856670.us-central1.run.app";
import socket, { socketManager } from "@/services/socket";
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import socket.

Copilot uses AI. Check for mistakes.

import React from 'react';
import { useSocketConnection } from '@/context/SocketConnectionContext';
import { AlertCircle, Wifi, WifiOff, RefreshCw } from 'lucide-react';
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import Wifi.

Copilot uses AI. Check for mistakes.

const isWildCard = (card) => card === 'W' || card === 'D4W';
const isSkipCard = (card) => card.startsWith('skip');
const isReverseCard = (card) => card.startsWith('_');
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable isReverseCard.

Copilot uses AI. Check for mistakes.
const [playerHand, setPlayerHand] = useState<string[]>([])
const [offChainGameState, setOffChainGameState] =
useState<OffChainGameState | null>(null);
const [error, setError] = useState<string | null>(null);
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable error.

Copilot uses AI. Check for mistakes.
const [offChainGameState, setOffChainGameState] =
useState<OffChainGameState | null>(null);
const [error, setError] = useState<string | null>(null);
const [playerHand, setPlayerHand] = useState<string[]>([]);
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable playerHand.

Copilot uses AI. Check for mistakes.
*/

const WALLET_ADDRESS_KEY = 'zunno_wallet_address';
const USER_ID_KEY = 'zunno_user_id';
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable USER_ID_KEY.

Copilot uses AI. Check for mistakes.
// Get current player's deck
const playerDeck = allPlayerDecks[currentUser] || [];

console.log(currentUser, "currentUser")
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid automated semicolon insertion (95% of all statements in the enclosing function have an explicit semicolon).

Copilot uses AI. Check for mistakes.
Comment thread backend/Dockerfile Outdated

# Set the working directory in the container
WORKDIR /usr/src/app
WORKDIR /app
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Dockerfile no longer sets NODE_ENV=production inside the container, which means the Express app will run in development mode by default if the environment variable is not provided externally. In Express, non‑production mode enables verbose error responses and stack traces to clients, which can leak internal file paths, implementation details, and other sensitive information that an attacker can trigger by causing server errors. To avoid this, ensure the container always runs with NODE_ENV set to production (either in the Dockerfile or via your deployment environment) so that production-safe error handling is enforced.

Suggested change
WORKDIR /app
WORKDIR /app
ENV NODE_ENV=production

Copilot uses AI. Check for mistakes.
Comment on lines +85 to +101
socket.on('requestGameStateSync', ({ roomId, gameId }) => {
try {
// Fetch current game state from your game state storage
const gameState = getGameState(roomId, gameId);
const cardHashMap = getCardHashMap(gameId);

if (gameState) {
// Send state back to the requesting client
socket.emit(`gameStateSync-${roomId}`, {
newState: gameState,
cardHashMap: cardHashMap
});
}
} catch (error) {
console.error('Error syncing game state:', error);
}
});
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This requestGameStateSync handler returns the full game state to any client that supplies a roomId or gameId, without checking whether the requesting socket is actually a participant in that game. An attacker who can guess or learn valid room or game IDs can open a socket connection, call requestGameStateSync, and obtain complete game state (including other players’ hands) for games they are not authorized to view. Add an authorization check that ties the requesting socket to a user record and verifies that user is a member of the specified room/game before sending any state back.

Copilot uses AI. Check for mistakes.
Comment on lines +50 to +71
socket.on('rejoinRoom', ({ room, gameId }, callback) => {
try {
// Check if room exists
const roomExists = rooms.has(room);

if (roomExists) {
// Add socket back to room
socket.join(room);

// Restore user's session if needed
const user = findUserBySocketId(socket.id);
if (user) {
user.connected = true;
}

callback({ success: true, room, gameId });

// Notify other players
socket.to(room).emit('playerReconnected', {
userId: socket.id,
room
});
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This rejoinRoom handler lets any connected client join an existing room by passing a room value, as long as rooms.has(room) is true, without verifying that the socket corresponds to the same user who was previously in that room. An attacker can guess or brute‑force room IDs and call this event to join arbitrary active rooms, observe game events, and potentially interfere with games they were never authorized to join. You should tie reconnection to a previously stored user identity (e.g., wallet or session ID) for that room and refuse reconnection when the socket/user does not match an existing room participant.

Copilot uses AI. Check for mistakes.
@vishal-insight-ai
Copy link
Copy Markdown

@ayushk-45 Can merge now safely
Do delete the branch after the merge

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants