// ───────────────────────────────────────────────────────────── // Server Entry Point — Express + Socket.io // ───────────────────────────────────────────────────────────── import express from 'express'; import { createServer } from 'http'; import cors from 'cors'; import helmet from 'helmet'; import morgan from 'morgan'; import { Server } from 'socket.io'; import dotenv from 'dotenv'; dotenv.config(); const app = express(); const httpServer = createServer(app); const PORT = process.env.PORT || 4000; const CLIENT_URL = process.env.CLIENT_URL || 'http://localhost:3000'; // ─── Middleware ────────────────────────────────────────────── app.use(helmet({ contentSecurityPolicy: false })); app.use(cors({ origin: [CLIENT_URL, 'http://localhost:3000'], credentials: true })); app.use(morgan('dev')); app.use(express.json({ limit: '5mb' })); // ─── Socket.io ─────────────────────────────────────────────── const io = new Server(httpServer, { cors: { origin: [CLIENT_URL, 'http://localhost:3000'], methods: ['GET', 'POST'], credentials: true, }, transports: ['websocket', 'polling'], pingTimeout: 20000, pingInterval: 10000, maxHttpBufferSize: 1e6, }); // Socket authentication middleware io.use((socket, next) => { const token = socket.handshake.auth?.token; if (!token) return next(new Error('Authentication required')); // In production: verify JWT here // const payload = verifyAccessToken(token); (socket as any).userId = 'user-' + Math.random().toString(36).substr(2, 9); (socket as any).userName = 'User'; (socket as any).userColor = '#4ECDC4'; (socket as any).currentRoom = null; next(); }); // Socket connection handler io.on('connection', (socket) => { console.log(`Client connected: ${socket.id}`); // ─── Room Handlers ──────────────────────────────────────── socket.on('room:join', ({ roomId }) => { socket.join(roomId); (socket as any).currentRoom = roomId; socket.emit('room:joined', { room: { id: roomId, name: 'Demo Room', language: 'javascript' }, members: [], files: [{ id: 'file-1', roomId, name: 'main.js', path: '/main.js', content: '// Start coding!\\n', language: 'javascript', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }], }); socket.to(roomId).emit('room:member-joined', { userId: (socket as any).userId, userName: (socket as any).userName, }); }); socket.on('room:leave', () => { const roomId = (socket as any).currentRoom; if (roomId) { socket.leave(roomId); socket.to(roomId).emit('room:member-left', { userId: (socket as any).userId }); (socket as any).currentRoom = null; } }); // ─── Document Sync (CRDT) ───────────────────────────────── socket.on('doc:update', (data) => { const roomId = (socket as any).currentRoom; if (roomId) { socket.to(roomId).emit('doc:sync', data); } }); socket.on('doc:request-state', (data) => { socket.emit('doc:state', { state: [], fileId: data.fileId }); }); // ─── Cursor & Presence ──────────────────────────────────── socket.on('cursor:move', (data) => { const roomId = (socket as any).currentRoom; if (roomId) { socket.to(roomId).emit('cursor:update', [{ userId: (socket as any).userId, userName: (socket as any).userName, userColor: (socket as any).userColor, ...data, }]); } }); // ─── Chat ───────────────────────────────────────────────── socket.on('chat:send', (data) => { const roomId = (socket as any).currentRoom; if (roomId) { const message = { id: Date.now().toString(), content: data.content, userId: (socket as any).userId, userName: (socket as any).userName, userAvatar: null, roomId, type: data.type || 'text', createdAt: new Date().toISOString(), }; io.to(roomId).emit('chat:message', message); } }); socket.on('chat:typing', (data) => { const roomId = (socket as any).currentRoom; if (roomId) { socket.to(roomId).emit('chat:typing', { userId: (socket as any).userId, userName: (socket as any).userName, isTyping: data.isTyping, }); } }); socket.on('chat:history', () => { socket.emit('chat:history', []); }); // ─── Code Execution ─────────────────────────────────────── socket.on('exec:run', async (data) => { const roomId = (socket as any).currentRoom; socket.emit('exec:output', { id: 'exec-1', chunk: `[Running ${data.language}...]\\n`, stream: 'stdout' }); // Simulated execution — in production uses Docker setTimeout(() => { socket.emit('exec:output', { id: 'exec-1', chunk: 'Hello, World!\\n', stream: 'stdout' }); if (roomId) { io.to(roomId).emit('exec:complete', { id: 'exec-1', stdout: 'Hello, World!\\n', stderr: '', exitCode: 0, duration: 42, memoryUsed: 1024, timedOut: false, }); } }, 500); }); // ─── WebRTC Signaling ───────────────────────────────────── socket.on('webrtc:join', () => { const roomId = (socket as any).currentRoom; if (roomId) { socket.to(roomId).emit('webrtc:peer-joined', { peerId: socket.id, peerName: (socket as any).userName, }); } }); socket.on('webrtc:offer', (data) => { const target = io.sockets.sockets.get(data.to); if (target) { target.emit('webrtc:offer', { ...data, from: socket.id }); } }); socket.on('webrtc:answer', (data) => { const target = io.sockets.sockets.get(data.to); if (target) { target.emit('webrtc:answer', { ...data, from: socket.id }); } }); socket.on('webrtc:ice-candidate', (data) => { const target = io.sockets.sockets.get(data.to); if (target) { target.emit('webrtc:ice-candidate', { ...data, from: socket.id }); } }); // ─── Disconnect ─────────────────────────────────────────── socket.on('disconnect', () => { const roomId = (socket as any).currentRoom; if (roomId) { socket.to(roomId).emit('room:member-left', { userId: (socket as any).userId }); socket.to(roomId).emit('webrtc:peer-left', { peerId: socket.id }); } console.log(`Client disconnected: ${socket.id}`); }); }); // ─── REST API Routes ───────────────────────────────────────── app.get('/api/health', (_req, res) => { res.json({ status: 'healthy', timestamp: new Date().toISOString(), version: '1.0.0' }); }); // Auth routes (simplified for demo — full implementation in services/) app.post('/api/auth/register', (req, res) => { const { email, name } = req.body; const user = { id: 'user-' + Math.random().toString(36).substr(2, 9), email, name, avatar: null, provider: 'local', createdAt: new Date().toISOString() }; const tokens = { accessToken: 'demo-token-' + user.id, refreshToken: 'refresh-' + user.id }; res.status(201).json({ success: true, data: { user, tokens } }); }); app.post('/api/auth/login', (req, res) => { const { email } = req.body; const user = { id: 'user-' + Math.random().toString(36).substr(2, 9), email, name: email.split('@')[0], avatar: null, provider: 'local', createdAt: new Date().toISOString() }; const tokens = { accessToken: 'demo-token-' + user.id, refreshToken: 'refresh-' + user.id }; res.json({ success: true, data: { user, tokens } }); }); app.get('/api/rooms', (_req, res) => { res.json({ success: true, data: [] }); }); app.post('/api/rooms', (req, res) => { const { name, language, isPublic } = req.body; const room = { id: 'room-' + Math.random().toString(36).substr(2, 9), name, language, isPublic, ownerId: 'user-1', inviteCode: Math.random().toString(36).substr(2, 8), maxMembers: 10, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }; res.status(201).json({ success: true, data: room }); }); app.post('/api/ai/action', (req, res) => { res.json({ success: true, data: { content: 'AI service requires OPENROUTER_API_KEY configuration.', suggestions: [] } }); }); app.get('/api/snippets', (_req, res) => { res.json({ success: true, data: [] }); }); // ─── Error Handler ─────────────────────────────────────────── app.use((_req, res) => { res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: 'Endpoint not found' } }); }); // ─── Start Server ──────────────────────────────────────────── httpServer.listen(PORT, () => { console.log(` ╔══════════════════════════════════════════════════╗ ║ CodeSync Server Running ║ ╠══════════════════════════════════════════════════╣ ║ HTTP: http://localhost:${PORT} ║ ║ WebSocket: ws://localhost:${PORT} ║ ╚══════════════════════════════════════════════════╝ `); }); export { app, httpServer, io };