// server.js const express = require('express'); const http = require('http'); const socketIo = require('socket.io'); const app = express(); const server = http.createServer(app); const io = socketIo(server, { cors: { origin: "*", // 프로덕션에서는 정확한 도메인/포트 지정 methods: ["GET", "POST"] } }); // 직원 ID ↔ socket.id 매핑 const employeeSockets = new Map(); // { employeeId: socketId } const employeeDisplays = new Map(); // { employeeId: displays[] } io.on('connection', (socket) => { console.log('새 클라이언트 연결됨:', socket.id); // 1. 직원이 자신의 ID를 등록 (예: "EMP123") socket.on('register', (employeeId) => { if (employeeId) { employeeSockets.set(employeeId, socket.id); socket.employeeId = employeeId; console.log(`직원 등록됨: ${employeeId} → ${socket.id}`); // 선택: 관리자에게 직원 온라인 알림 socket.broadcast.emit('employeeOnline', employeeId); } }); // 2. 관리자가 연결 요청 socket.on('requestControl', ({ displayId, targetId }) => { console.log('requestControl', targetId, displayId); const targetSocketId = employeeSockets.get(targetId); if (targetSocketId) { io.to(targetSocketId).emit('responseControl', { targetId: socket.id, displayId // 선택된 디스플레이 ID 전달 }); } else { console.log(`직원 ${targetId}가 오프라인입니다.`); socket.emit('error', `직원 ${targetId}가 오프라인입니다.`); } }); // 2.5. 관리자가 디스플레이 목록 요청 socket.on('requestDisplays', (employeeId) => { const displays = employeeDisplays.get(employeeId); if (displays) { socket.emit('responseDisplays', {displays}); } else { socket.emit('error', `직원 ${employeeId}의 디스플레이 정보가 없습니다.`); } }); // 3. WebRTC 시그널링 (ICE) socket.on('icecandidate', (data) => { if(data.server === 'agent') { if (data.targetId && data.candidate) { console.log('icecandidate agent', data); const to = io.to(data.targetId) data.targetId = socket.id; to.emit('icecandidate', data); } } else { const targetSocketId = employeeSockets.get(data.targetId); if (targetSocketId && data.candidate) { console.log('icecandidate client', data); const to = io.to(targetSocketId) data.targetId = socket.id; to.emit('icecandidate', data); } } }); // 4. 입력 이벤트 전달 (관리자 → 직원) socket.on('inputEvent', async (data) => { console.log('inputEvent', data); const targetSocketId = employeeSockets.get(data.targetId); if (targetSocketId) { // console.log('inputEvent', data); await io.to(targetSocketId).emit('inputEvent', data); } }); // 연결 종료 시 정리 socket.on('disconnect', () => { if (socket.employeeId) { employeeSockets.delete(socket.employeeId); employeeDisplays.delete(socket.employeeId); socket.broadcast.emit('employeeOffline', socket.employeeId); console.log(`직원 오프라인: ${socket.employeeId}`); } }); // rtc close socket.on('forceClose', (data) => { if(data.server === 'agent') { } else if(data.server === 'client') { const targetSocketId = employeeSockets.get(data.targetId); io.to(targetSocketId).emit('forceClose', data); } }); socket.on('availableDisplays', ({ displays }) => { if (socket.employeeId && displays) { employeeDisplays.set(socket.employeeId, displays); console.log(`디스플레이 목록 저장됨: ${socket.employeeId}`, displays); } }); socket.on('requestOffer', (data) => { const targetSocketId = employeeSockets.get(data.targetId); console.log('requestOffer', employeeSockets, targetSocketId); if(targetSocketId) { const to = io.to(targetSocketId) data.targetId = socket.id to.emit('responseOffer', data); } }) socket.on('requestAnswer', (data) => { console.log('requestAnswer', employeeSockets, data.targetId); if(data.targetId) { const to = io.to(data.targetId) data.targetId = socket.id to.emit('responseAnswer', data); } }) socket.on('start', (data) => { console.log('start', employeeSockets, data); if(data.targetId) { const to = io.to(data.targetId) data.targetId = socket.id to.emit('start', data); } }) }); const PORT = process.env.PORT || 3001; const ADDR = process.env.ADDR || '0.0.0.0'; server.listen(PORT, ADDR, () => { console.log(`시그널링 서버 실행 중: http://${ADDR}:${PORT}`); });