110 lines
3.6 KiB
TypeScript
110 lines
3.6 KiB
TypeScript
import { Server as SocketIOServer, Socket } from 'socket.io';
|
|
import { upsertPixels, getAllPixels, db } 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;
|
|
}
|
|
|
|
// Helper function to get user data from db
|
|
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<string, { x: number; y: number; color: string; username: string; userId: number }>();
|
|
|
|
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 (pixels: Pixel[]) => {
|
|
try {
|
|
if (!userSocket.user) {
|
|
socket.emit('error', 'Authentication required to paint pixels');
|
|
return;
|
|
}
|
|
|
|
upsertPixels(pixels);
|
|
socket.broadcast.emit('canvas-update', { pixels });
|
|
} catch (error) {
|
|
console.error('Error saving pixels:', error);
|
|
socket.emit('error', 'Failed to save pixels');
|
|
}
|
|
});
|
|
|
|
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();
|
|
}
|
|
});
|
|
});
|
|
} |