myagent10101's picture
feat: Complete CodeSync collaborative coding platform
8f9c4ef verified
// ─────────────────────────────────────────────────────────────
// 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 };