import { Server as SocketIOServer, Socket } from 'socket.io'; import { upsertPixelsWithConflictResolution, getAllPixels, db, validatePixel } from './database'; import { Cursor, Pixel } from '../shared/types'; import passport from 'passport'; function generateRandomColor(): string { const colors = [ '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E2' ]; return colors[Math.floor(Math.random() * colors.length)]; } interface UserSocket extends Socket { userId: string; username: string; color: string; user?: any; } interface PaintRequest { pixels: Pixel[]; timestamp: number; } interface PaintAck { success: boolean; saved: number; conflicts: number; pixels?: Pixel[]; message?: string; } function getUserById(userId: number) { try { const stmt = db.prepare('SELECT * FROM users WHERE id = ?'); return stmt.get(userId); } catch (error) { console.error('Error fetching user:', error); return null; } } export function setupSocketIO(io: SocketIOServer): void { const connectedUsers = new Map(); io.on('connection', (socket: Socket) => { const userSocket = socket as UserSocket; socket.on('join-canvas', (token: string) => { try { const decoded = JSON.parse(Buffer.from(token, 'base64').toString()); if (Date.now() - decoded.timestamp > 5 * 60 * 1000) { socket.emit('error', 'Token expired'); socket.disconnect(); return; } const userData = getUserById(decoded.userId); if (userData) { userSocket.user = userData; const userId = `auth_${userData.id}`; const username = userData.username || userData.display_name; const color = generateRandomColor(); connectedUsers.set(userId, { x: 0, y: 0, color: color, username: username, userId: userData.id.toString() }); socket.emit('canvas-state', getAllPixels()); socket.emit('cursor-update', Array.from(connectedUsers.values())); socket.emit('auth-success', { user: userData }); socket.broadcast.emit('cursor-update', Array.from(connectedUsers.values())); socket.on('cursor-move', (cursor: Cursor) => { const user = connectedUsers.get(userId); if (user) { user.x = cursor.x; user.y = cursor.y; socket.broadcast.emit('cursor-update', Array.from(connectedUsers.values())); } }); socket.on('paint-pixels', async (request: PaintRequest, callback: (ack: PaintAck) => void) => { try { if (!userSocket.user) { const ack: PaintAck = { success: false, saved: 0, conflicts: 0, message: 'Authentication required' }; callback(ack); return; } const { pixels, timestamp } = request; if (!Array.isArray(pixels) || pixels.length === 0) { const ack: PaintAck = { success: false, saved: 0, conflicts: 0, message: 'Invalid pixels data' }; callback(ack); return; } for (const pixel of pixels) { const validationError = validatePixel(pixel); if (validationError) { const ack: PaintAck = { success: false, saved: 0, conflicts: 0, message: validationError }; callback(ack); return; } } const userIdNum = typeof userData.id === 'number' ? userData.id : parseInt(userData.id, 10); const result = upsertPixelsWithConflictResolution( pixels.map(p => ({ ...p, userId: userIdNum })), timestamp || 0 ); if (result.saved > 0) { socket.broadcast.emit('canvas-update', { pixels }); } const ack: PaintAck = { success: true, saved: result.saved, conflicts: result.conflicts, pixels: result.conflicts > 0 ? pixels : undefined }; callback(ack); } catch (error) { console.error('Error saving pixels:', error); const ack: PaintAck = { success: false, saved: 0, conflicts: 0, message: 'Failed to save pixels' }; callback(ack); } }); socket.on('disconnect', () => { connectedUsers.delete(userId); socket.broadcast.emit('cursor-update', Array.from(connectedUsers.values())); }); } else { console.error('User not found for token:', decoded.userId); socket.emit('error', 'Invalid user'); socket.disconnect(); } } catch (error) { console.error('Token verification failed:', error); socket.emit('error', 'Invalid token'); socket.disconnect(); } }); }); }