const http = require('http'); const path = require('path'); const fs = require('fs'); const { Server } = require('socket.io'); const PORT = process.env.PORT || 3000; const BASE_PATH = '/realtimechat'; const server = http.createServer((req, res) => { const sockPath = BASE_PATH + '/socket.io/socket.io'; if (req.url === sockPath + '.js' || req.url === sockPath + '.min.js') { const clientPath = path.join(__dirname, 'node_modules', 'socket.io', 'client-dist', 'socket.io.min.js'); return fs.readFile(clientPath, (err, data) => { if (err) { res.writeHead(404); return res.end('Not Found'); } res.writeHead(200, { 'Content-Type': 'application/javascript' }); res.end(data); }); } let file = req.url === BASE_PATH || req.url === BASE_PATH + '/' ? BASE_PATH + '/index.html' : req.url; if (file.indexOf(BASE_PATH) === 0) file = file.slice(BASE_PATH.length) || '/index.html'; const filePath = path.join(__dirname, 'public', file); const ext = path.extname(filePath); const types = { '.html': 'text/html', '.js': 'application/javascript', '.css': 'text/css', '.ico': 'image/x-icon' }; fs.readFile(filePath, (err, data) => { if (err) { res.writeHead(404); return res.end('Not Found'); } res.writeHead(200, { 'Content-Type': types[ext] || 'application/octet-stream' }); res.end(data); }); }); const io = new Server(server, { path: BASE_PATH + '/socket.io', cors: { origin: '*' } }); const rooms = new Map(); io.on('connection', (socket) => { socket.on('create-room', ({ roomId, password }, cb) => { roomId = (roomId || '').trim() || 'room-' + Date.now(); if (!rooms.has(roomId)) rooms.set(roomId, { password: password || '', peers: new Map() }); if (typeof cb === 'function') cb({ ok: true, roomId }); }); socket.on('join-room', ({ roomId, password, nickname }, cb) => { if (!rooms.has(roomId)) return cb && cb({ ok: false, error: 'ไม่พบห้อง' }); const room = rooms.get(roomId); if (room.password && room.password !== (password || '')) return cb && cb({ ok: false, error: 'รหัสผ่านผิด' }); socket.join(roomId); const nick = (nickname || '').trim(); room.peers.set(socket.id, { id: socket.id, nickname: nick }); const peerList = [...room.peers.values()].filter((p) => p.id !== socket.id); if (typeof cb === 'function') cb({ ok: true, peers: peerList }); socket.to(roomId).emit('user-joined', { id: socket.id, nickname: nick }); }); socket.on('disconnect', () => { for (const [rid, room] of rooms) { if (room.peers.has(socket.id)) { room.peers.delete(socket.id); socket.to(rid).emit('user-left', { id: socket.id }); if (room.peers.size === 0) rooms.delete(rid); break; } } }); socket.on('webrtc-offer', ({ to, offer }) => socket.to(to).emit('webrtc-offer', { from: socket.id, offer })); socket.on('webrtc-answer', ({ to, answer }) => socket.to(to).emit('webrtc-answer', { from: socket.id, answer })); socket.on('webrtc-ice', ({ to, candidate }) => socket.to(to).emit('webrtc-ice', { from: socket.id, candidate })); socket.on('chat', (text) => { const roomId = [...socket.rooms].find(r => r !== socket.id && rooms.has(r)); if (!roomId) return; const room = rooms.get(roomId); const peer = room?.peers.get(socket.id); const nickname = peer?.nickname || ''; io.to(roomId).emit('chat', { id: socket.id, nickname, text, time: new Date() }); }); }); server.listen(PORT, () => console.log('RealtimeChat', PORT));