From 47cb814162b44313648e5c97c9e39f7145bcf879 Mon Sep 17 00:00:00 2001 From: bangae1 Date: Mon, 20 Oct 2025 21:15:37 +0900 Subject: [PATCH] Aa --- agent-renderer.js | 128 ++++++++++++++++++++++++++++------------------ main.js | 98 +++++------------------------------ preload.js | 6 ++- 3 files changed, 97 insertions(+), 135 deletions(-) diff --git a/agent-renderer.js b/agent-renderer.js index 862b1c2..6a0ea49 100644 --- a/agent-renderer.js +++ b/agent-renderer.js @@ -7,69 +7,99 @@ let currentStream = null; // 메인 프로세스로부터 명령 수신 (IPC) console.log('electronAPI:', window.electronAPI); console.log('desktopCapturer:', window.desktopCapturer); + +window.electronAPI.send('displays', { types: ['screen'] }); window.electronAPI.receive('start-webrtc', async (payload) => { try { - const { offer, displayId, signalingServer, employeeId } = payload; + const { offer, displayId, signalingServer, targetId } = payload; console.log('start webrtc'); // 1. Socket 연결 (렌더러에서도 가능) socket = io(signalingServer); + socket.on('availableDisplays', async (sources) => { + + const source = sources.find(s => { + console.log(s.id, displayId) + return s.id === displayId + }); + if (!source) throw new Error('디스플레이 없음'); + + const stream = await navigator.mediaDevices.getUserMedia({ + audio: false, + video: { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: source.id, + minWidth: 1920, + minHeight: 1080 + } + } + }); + currentStream = stream; + + + // 3. WebRTC 연결 + peerConnection = new RTCPeerConnection({ + iceServers: [{urls: 'stun:stun.l.google.com:19302'}] + }); + + stream.getTracks().forEach(track => { + console.log('track', track, stream); + peerConnection.addTrack(track, stream) + }); + await peerConnection.setRemoteDescription(new RTCSessionDescription(offer)); + const answer = await peerConnection.createAnswer(); + await peerConnection.setLocalDescription(answer); + + // 4. Answer 전송 + socket.emit('webrtcSignal', { + targetId: targetId, + type: 'answer', + answer + }); + + // 5. ICE candidate + peerConnection.onicecandidate = (e) => { + if (e.candidate) { + socket.emit('webrtcSignal', { + targetId: targetId, + type: 'icecandidate', + candidate: e.candidate + }); + } + }; + + // 6. 입력 이벤트 수신 (robotjs는 메인 프로세스에서 실행 권장) + socket.on('inputEvent', (data) => { + window.electronApi.send('input-event-from-renderer', data); + }); + + + console.log('✅ WebRTC 시작됨 (렌더러)'); + }) // 2. 화면 스트림 캡처 - const sources = await window.desktopCapturer.getSources({ types: ['screen'] }); - const source = sources.find(s => s.id === displayId); - if (!source) throw new Error('디스플레이 없음'); + socket.emit('requestDisplays', 'psn14020') - const stream = await navigator.mediaDevices.getUserMedia({ - audio: false, - video: { - mandatory: { - chromeMediaSource: 'desktop', - chromeMediaSourceId: source.id, - minWidth: 1280, - minHeight: 720 - } - } - }); - currentStream = stream; + // const sources = await window.desktopCapturer.getSources({ types: ['screen'] }); - // 3. WebRTC 연결 - peerConnection = new RTCPeerConnection({ - iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] - }); - - stream.getTracks().forEach(track => peerConnection.addTrack(track, stream)); - - await peerConnection.setRemoteDescription(new RTCSessionDescription(offer)); - const answer = await peerConnection.createAnswer(); - await peerConnection.setLocalDescription(answer); - - // 4. Answer 전송 - socket.emit('webrtcSignal', { - targetId: employeeId, - answer - }); - - // 5. ICE candidate - peerConnection.onicecandidate = (e) => { - if (e.candidate) { - socket.emit('webrtcSignal', { - targetId: employeeId, - type: 'icecandidate', candidate: e.candidate - }); - } - }; - - // 6. 입력 이벤트 수신 (robotjs는 메인 프로세스에서 실행 권장) - socket.on('inputEvent', (data) => { - window.electronApi.send('input-event-from-renderer', data); - }); - - console.log('✅ WebRTC 시작됨 (렌더러)'); } catch (err) { console.error('❌ WebRTC 실패:', err); window.electronApi.send('webrtc-error', err.message); } }); + +window.electronAPI.receive('webrtcSignal', async (data) => { + try { + console.log('webrtcSignal', data) + if (!peerConnection) return; + if (data.type === 'icecandidate' && data.candidate) { + await peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate)); + } + } catch (err) { + console.error('ICE candidate 처리 오류:', err); + } + +}) // desktopCapturer 사용을 위해 preload 필요 // → 다음 단계에서 preload.js 설정 \ No newline at end of file diff --git a/main.js b/main.js index ff1ed59..3fe69b6 100644 --- a/main.js +++ b/main.js @@ -14,6 +14,7 @@ let tray = null; let mainWindow = null; let socket = null; let peerConnection = null; +let displayId = null; // 자동 시작 설정 app.setLoginItemSettings({ @@ -53,6 +54,9 @@ function createTray() { async function sendAvailableDisplays() { try { const sources = await desktopCapturer.getSources({ types: ['screen'] }); + const source = sources[0]; + displayId = source.id + console.log('sources',sources) const displays = sources.map(source => ({ id: source.id, name: source.name, @@ -65,76 +69,6 @@ async function sendAvailableDisplays() { } } -// 🔁 WebRTC 연결 설정 (offer 수신 후 호출) -async function setupWebRTCConnection(offer, requestedDisplayId) { - try { - // 기존 연결 정리 - if (peerConnection) { - peerConnection.close(); - } - - peerConnection = new RTCPeerConnection({ - iceServers: [ - { urls: 'stun:stun.l.google.com:19302' } - // 프로덕션: TURN 서버 추가 권장 - ] - }); - - // 🖼️ 화면 스트림 캡처 - const sources = await desktopCapturer.getSources({ types: ['screen'] }); - const source = sources.find(s => s.id === requestedDisplayId); - if (!source) throw new Error(`디스플레이 ${requestedDisplayId}를 찾을 수 없습니다.`); - - const stream = await navigator.mediaDevices.getUserMedia({ - audio: false, - video: { - mandatory: { - chromeMediaSource: 'desktop', - chromeMediaSourceId: source.id, - minWidth: 1280, - minHeight: 720, - maxWidth: 1920, - maxHeight: 1080 - } - } - }); - - // 스트림을 PeerConnection에 추가 - stream.getTracks().forEach(track => { - peerConnection.addTrack(track, stream); - }); - - // Offer 설정 - await peerConnection.setRemoteDescription(new RTCSessionDescription(offer)); - - // ✅ Answer SDP 생성 - const answer = await peerConnection.createAnswer(); - await peerConnection.setLocalDescription(answer); - - // Answer 전송 - socket.emit('webrtcSignal', { - targetId: EMPLOYEE_ID, - answer - }); - - // ICE candidate 전송 - peerConnection.onicecandidate = (event) => { - if (event.candidate) { - socket.emit('webrtcSignal', { - targetId: EMPLOYEE_ID, - type: 'icecandidate', - candidate: event.candidate - }); - } - }; - - console.log('✅ WebRTC 연결 설정 완료. 화면 공유 시작됨.'); - } catch (err) { - console.error('❌ WebRTC 설정 실패:', err); - dialog.showErrorBox('오류', '화면 공유 시작에 실패했습니다:\n' + err.message); - } -} - // 🖱️ 입력 이벤트 처리 (robotjs 필요) function setupInputHandler() { // robotjs는 별도 설치 및 권한 필요 @@ -175,11 +109,11 @@ function connectToSignaling() { sendAvailableDisplays(); // 연결 시 디스플레이 목록 전송 }); - socket.on('controlRequest', async ({ offer, displayId }) => { + socket.on('controlRequest', async (data) => { const result = await dialog.showMessageBox({ type: 'question', title: '원격 연결 요청', - message: `관리자로부터 원격 제어 요청이 왔습니다.\n허용하시겠습니까?`, + message: `관리자로부터 원격 제어 요청이 왔습니다.\n허용하시겠습니까? ${displayId}`, buttons: ['허용', '거부'], defaultId: 1, cancelId: 1 @@ -188,26 +122,19 @@ function connectToSignaling() { if (result.response === 0) { // ✅ 렌더러 프로세스에 WebRTC 시작 명령 전달 rendererWindow.webContents.send('start-webrtc', { - offer, + offer: data.offer, displayId, signalingServer: SIGNALING_SERVER, - employeeId: EMPLOYEE_ID + targetId: data.from }); } else { - socket.emit('webrtcSignal', { targetId: EMPLOYEE_ID, data: { type: 'reject' } }); + socket.emit('webrtcSignal', { targetId: data.from, data: { type: 'reject' } }); } }); // WebRTC 신호 수신 (ICE candidate 등) - socket.on('webrtcSignal', async ({ data }) => { - if (!peerConnection) return; - try { - if (data.type === 'icecandidate' && data.candidate) { - await peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate)); - } - } catch (err) { - console.error('ICE candidate 처리 오류:', err); - } + socket.on('webrtcSignal', async (data) => { + rendererWindow.webContents.send('webrtcSignal', data); }); socket.on('disconnect', () => { @@ -255,4 +182,5 @@ ipcMain.on('input-event-from-renderer', (event, data) => { } catch (e) { console.error('robotjs 오류:', e); } -}) \ No newline at end of file +}) + diff --git a/preload.js b/preload.js index f255c31..489d10d 100644 --- a/preload.js +++ b/preload.js @@ -1,5 +1,9 @@ // preload.js -const { contextBridge, ipcRenderer, desktopCapturer } = require('electron'); +const { contextBridge, ipcRenderer } = require('electron'); +const { desktopCapturer } = require('electron'); + +console.log('Preload script loaded'); // 이 로그가 안 찍히면 preload 미로드 + contextBridge.exposeInMainWorld('agentAPI', { getEmployeeId: () => ipcRenderer.invoke('get-employee-id')