Skip to main content
Glama
server.ts11.4 kB
import { Server, Socket } from 'socket.io'; import { createServer } from 'http'; import express from 'express'; // ============================================ // Types // ============================================ interface User { id: string; username: string; socketId: string; rooms: Set<string>; status: 'online' | 'away' | 'busy'; } interface Message { id: string; roomId: string; userId: string; username: string; content: string; timestamp: Date; type: 'text' | 'system' | 'file'; } interface Room { id: string; name: string; createdBy: string; members: Set<string>; isPrivate: boolean; createdAt: Date; } // Server -> Client events interface ServerToClientEvents { 'user:joined': (data: { userId: string; username: string; roomId: string }) => void; 'user:left': (data: { userId: string; username: string; roomId: string }) => void; 'user:status': (data: { userId: string; status: User['status'] }) => void; 'message:new': (message: Message) => void; 'message:typing': (data: { userId: string; username: string; roomId: string }) => void; 'room:created': (room: { id: string; name: string }) => void; 'room:members': (data: { roomId: string; members: { id: string; username: string }[] }) => void; 'error': (data: { message: string; code: string }) => void; } // Client -> Server events interface ClientToServerEvents { 'user:login': (data: { username: string }, callback: (response: { userId: string }) => void) => void; 'user:status': (data: { status: User['status'] }) => void; 'room:join': (data: { roomId: string }) => void; 'room:leave': (data: { roomId: string }) => void; 'room:create': (data: { name: string; isPrivate?: boolean }, callback: (response: { roomId: string }) => void) => void; 'message:send': (data: { roomId: string; content: string; type?: Message['type'] }) => void; 'message:typing': (data: { roomId: string }) => void; } // ============================================ // Data Store // ============================================ class DataStore { users = new Map<string, User>(); rooms = new Map<string, Room>(); messages = new Map<string, Message[]>(); // roomId -> messages private nextUserId = 1; private nextMessageId = 1; constructor() { // Create default room this.createRoom('general', 'system', false); } createUser(username: string, socketId: string): User { const user: User = { id: `user-${this.nextUserId++}`, username, socketId, rooms: new Set(), status: 'online', }; this.users.set(user.id, user); return user; } getUserBySocketId(socketId: string): User | undefined { return Array.from(this.users.values()).find(u => u.socketId === socketId); } removeUser(socketId: string): User | undefined { const user = this.getUserBySocketId(socketId); if (user) { this.users.delete(user.id); } return user; } createRoom(name: string, createdBy: string, isPrivate: boolean): Room { const id = name.toLowerCase().replace(/\s+/g, '-'); const room: Room = { id, name, createdBy, members: new Set(), isPrivate, createdAt: new Date(), }; this.rooms.set(id, room); this.messages.set(id, []); return room; } addMessage(roomId: string, userId: string, username: string, content: string, type: Message['type'] = 'text'): Message { const message: Message = { id: `msg-${this.nextMessageId++}`, roomId, userId, username, content, timestamp: new Date(), type, }; const messages = this.messages.get(roomId) ?? []; messages.push(message); this.messages.set(roomId, messages); return message; } getRecentMessages(roomId: string, limit = 50): Message[] { const messages = this.messages.get(roomId) ?? []; return messages.slice(-limit); } getRoomMembers(roomId: string): User[] { const room = this.rooms.get(roomId); if (!room) return []; return Array.from(room.members) .map(id => this.users.get(id)) .filter((u): u is User => u !== undefined); } } const store = new DataStore(); // ============================================ // Server Setup // ============================================ const app = express(); const httpServer = createServer(app); const io = new Server<ClientToServerEvents, ServerToClientEvents>(httpServer, { cors: { origin: process.env.CORS_ORIGIN || '*', methods: ['GET', 'POST'], }, pingTimeout: 60000, pingInterval: 25000, }); // ============================================ // Middleware // ============================================ io.use((socket, next) => { // Add authentication here if needed const token = socket.handshake.auth.token; if (token) { // Verify token in production socket.data.authenticated = true; } next(); }); // ============================================ // Connection Handler // ============================================ io.on('connection', (socket: Socket<ClientToServerEvents, ServerToClientEvents>) => { console.log(`Socket connected: ${socket.id}`); // User Login socket.on('user:login', ({ username }, callback) => { const user = store.createUser(username, socket.id); socket.data.userId = user.id; socket.data.username = username; // Auto-join general room const generalRoom = store.rooms.get('general'); if (generalRoom) { socket.join('general'); user.rooms.add('general'); generalRoom.members.add(user.id); // Notify room socket.to('general').emit('user:joined', { userId: user.id, username, roomId: 'general', }); // Send system message const systemMsg = store.addMessage('general', 'system', 'System', `${username} joined the chat`, 'system'); io.to('general').emit('message:new', systemMsg); } callback({ userId: user.id }); }); // Update Status socket.on('user:status', ({ status }) => { const user = store.getUserBySocketId(socket.id); if (!user) return; user.status = status; // Notify all rooms user is in user.rooms.forEach(roomId => { socket.to(roomId).emit('user:status', { userId: user.id, status }); }); }); // Join Room socket.on('room:join', ({ roomId }) => { const user = store.getUserBySocketId(socket.id); const room = store.rooms.get(roomId); if (!user || !room) { socket.emit('error', { message: 'Invalid room', code: 'INVALID_ROOM' }); return; } socket.join(roomId); user.rooms.add(roomId); room.members.add(user.id); // Notify room socket.to(roomId).emit('user:joined', { userId: user.id, username: user.username, roomId, }); // Send room members const members = store.getRoomMembers(roomId).map(m => ({ id: m.id, username: m.username })); socket.emit('room:members', { roomId, members }); // Send recent messages const recentMessages = store.getRecentMessages(roomId); recentMessages.forEach(msg => socket.emit('message:new', msg)); }); // Leave Room socket.on('room:leave', ({ roomId }) => { const user = store.getUserBySocketId(socket.id); const room = store.rooms.get(roomId); if (!user || !room) return; socket.leave(roomId); user.rooms.delete(roomId); room.members.delete(user.id); socket.to(roomId).emit('user:left', { userId: user.id, username: user.username, roomId, }); }); // Create Room socket.on('room:create', ({ name, isPrivate = false }, callback) => { const user = store.getUserBySocketId(socket.id); if (!user) { socket.emit('error', { message: 'Not logged in', code: 'UNAUTHORIZED' }); return; } const room = store.createRoom(name, user.id, isPrivate); // Auto-join creator socket.join(room.id); user.rooms.add(room.id); room.members.add(user.id); // Notify all (for public rooms) if (!isPrivate) { io.emit('room:created', { id: room.id, name: room.name }); } callback({ roomId: room.id }); }); // Send Message socket.on('message:send', ({ roomId, content, type = 'text' }) => { const user = store.getUserBySocketId(socket.id); if (!user || !user.rooms.has(roomId)) { socket.emit('error', { message: 'Not in room', code: 'NOT_IN_ROOM' }); return; } const message = store.addMessage(roomId, user.id, user.username, content, type); io.to(roomId).emit('message:new', message); }); // Typing Indicator socket.on('message:typing', ({ roomId }) => { const user = store.getUserBySocketId(socket.id); if (!user || !user.rooms.has(roomId)) return; socket.to(roomId).emit('message:typing', { userId: user.id, username: user.username, roomId, }); }); // Disconnect socket.on('disconnect', (reason) => { console.log(`Socket disconnected: ${socket.id} - ${reason}`); const user = store.removeUser(socket.id); if (user) { user.rooms.forEach(roomId => { const room = store.rooms.get(roomId); if (room) { room.members.delete(user.id); socket.to(roomId).emit('user:left', { userId: user.id, username: user.username, roomId, }); // System message const systemMsg = store.addMessage(roomId, 'system', 'System', `${user.username} left the chat`, 'system'); io.to(roomId).emit('message:new', systemMsg); } }); } }); }); // ============================================ // HTTP Endpoints // ============================================ app.get('/health', (_, res) => { res.json({ status: 'healthy', connections: io.engine.clientsCount, rooms: store.rooms.size, users: store.users.size, }); }); app.get('/rooms', (_, res) => { const publicRooms = Array.from(store.rooms.values()) .filter(r => !r.isPrivate) .map(r => ({ id: r.id, name: r.name, members: r.members.size })); res.json(publicRooms); }); // ============================================ // Start Server // ============================================ const PORT = process.env.PORT || 3000; httpServer.listen(PORT, () => { console.log(`🔌 WebSocket Server: http://localhost:${PORT}`); }); export { io, store };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/millsydotdev/Code-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server