187 lines
5.7 KiB
JavaScript
187 lines
5.7 KiB
JavaScript
// main.js
|
|
const { app, BrowserWindow, Tray, Menu, nativeImage, dialog, ipcMain } = require('electron');
|
|
const path = require('path');
|
|
const io = require('socket.io-client');
|
|
const { desktopCapturer } = require('electron');
|
|
const { mouse, Point, straightTo, keyboard, Key, clipboard } = require('@nut-tree-fork/nut-js')
|
|
// 고유 직원 ID
|
|
const EMPLOYEE_ID = "psn14020";
|
|
|
|
// 시그널링 서버 주소
|
|
const SIGNALING_SERVER = 'http://localhost:3001';
|
|
|
|
let tray = null;
|
|
let mainWindow = null;
|
|
let socket = null;
|
|
let peerConnection = null;
|
|
let displayId = null;
|
|
|
|
// 자동 시작 설정
|
|
app.setLoginItemSettings({
|
|
openAtLogin: true,
|
|
openAsHidden: true
|
|
});
|
|
|
|
// 백그라운드 창 (화면 공유용)
|
|
function createHiddenWindow() {
|
|
const win = new BrowserWindow({
|
|
show: true,
|
|
webPreferences: {
|
|
contextIsolation: true,
|
|
preload: path.join(__dirname, 'preload.js')
|
|
},
|
|
|
|
});
|
|
win.webContents.openDevTools();
|
|
win.loadFile('index.html');
|
|
return win;
|
|
}
|
|
|
|
// 트레이 아이콘
|
|
function createTray() {
|
|
const iconPath = path.join(__dirname, 'resources', 'icon.png');
|
|
const icon = nativeImage.createFromPath(iconPath);
|
|
tray = new Tray(icon || undefined);
|
|
const contextMenu = Menu.buildFromTemplate([
|
|
{ label: `직원 ID: ${EMPLOYEE_ID}`, enabled: false },
|
|
{ label: '종료', click: () => app.quit() }
|
|
]);
|
|
tray.setToolTip('원격 지원 에이전트 실행 중');
|
|
tray.setContextMenu(contextMenu);
|
|
}
|
|
|
|
// 🖥️ 사용 가능한 디스플레이 목록 전송
|
|
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,
|
|
width: source.thumbnail.getSize().width,
|
|
height: source.thumbnail.getSize().height
|
|
}));
|
|
socket.emit('availableDisplays', { displays });
|
|
} catch (err) {
|
|
console.error('디스플레이 목록 가져오기 실패:', err);
|
|
}
|
|
}
|
|
|
|
// 🖱️ 입력 이벤트 처리 (robotjs 필요)
|
|
function setupInputHandler() {
|
|
// robotjs는 별도 설치 및 권한 필요
|
|
|
|
socket.on('inputEvent', async({ type, ...data }) => {
|
|
try {
|
|
if (type === 'mouse') {
|
|
const { x, y, action } = data;
|
|
const targetPoint = new Point(x, y)
|
|
await mouse.move(straightTo(targetPoint));
|
|
if (action === 'click') {
|
|
await mouse.leftClick();
|
|
}
|
|
} else if (type === 'keyboard') {
|
|
const { key } = data;
|
|
// 간단한 키 매핑 (보안상 복잡한 키는 제한 권장)
|
|
if (key.length === 1 || ['Enter', 'Backspace', 'Tab', 'Escape'].includes(key)) {
|
|
await keyboard.pressKey(key.toLowerCase());
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error('입력 이벤트 처리 오류:', err);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 시그널링 연결
|
|
function connectToSignaling() {
|
|
socket = io(SIGNALING_SERVER, {
|
|
reconnection: true,
|
|
reconnectionAttempts: Infinity,
|
|
reconnectionDelay: 2000
|
|
});
|
|
|
|
socket.on('connect', () => {
|
|
console.log('시그널링 서버 연결됨. 직원 등록 중...');
|
|
socket.emit('register', EMPLOYEE_ID);
|
|
sendAvailableDisplays(); // 연결 시 디스플레이 목록 전송
|
|
});
|
|
|
|
socket.on('controlRequest', async (data) => {
|
|
const result = await dialog.showMessageBox({
|
|
type: 'question',
|
|
title: '원격 연결 요청',
|
|
message: `관리자로부터 원격 제어 요청이 왔습니다.\n허용하시겠습니까? ${displayId}`,
|
|
buttons: ['허용', '거부'],
|
|
defaultId: 1,
|
|
cancelId: 1
|
|
});
|
|
|
|
if (result.response === 0) {
|
|
// ✅ 렌더러 프로세스에 WebRTC 시작 명령 전달
|
|
rendererWindow.webContents.send('start-webrtc', {
|
|
offer: data.offer,
|
|
displayId,
|
|
signalingServer: SIGNALING_SERVER,
|
|
targetId: data.from
|
|
});
|
|
} else {
|
|
socket.emit('webrtcSignal', { targetId: data.from, data: { type: 'reject' } });
|
|
}
|
|
});
|
|
|
|
// WebRTC 신호 수신 (ICE candidate 등)
|
|
socket.on('webrtcSignal', async (data) => {
|
|
rendererWindow.webContents.send('webrtcSignal', data);
|
|
});
|
|
|
|
socket.on('disconnect', () => {
|
|
console.log('시그널링 서버와 연결 끊김');
|
|
if (peerConnection) {
|
|
peerConnection.close();
|
|
peerConnection = null;
|
|
}
|
|
});
|
|
|
|
socket.on('error', (err) => {
|
|
console.error('소켓 오류:', err);
|
|
});
|
|
|
|
// 입력 이벤트 리스너 설정
|
|
setupInputHandler();
|
|
}
|
|
// 앱 준비 시
|
|
let rendererWindow = null;
|
|
// 앱 준비 완료
|
|
app.whenReady().then(() => {
|
|
rendererWindow = createHiddenWindow();
|
|
createTray();
|
|
connectToSignaling();
|
|
|
|
app.on('activate', () => {
|
|
// macOS 대응
|
|
});
|
|
});
|
|
|
|
app.on('window-all-closed', () => {
|
|
// Windows/Linux에서 종료하지 않음
|
|
});
|
|
|
|
ipcMain.on('input-event-from-renderer', (event, data) => {
|
|
// 여기서 robotjs로 입력 실행 (메인 프로세스에서만 가능)
|
|
try {
|
|
const robot = require('robotjs');
|
|
if (data.type === 'mouse') {
|
|
robot.moveMouse(Math.round(data.x), Math.round(data.y));
|
|
if (data.action === 'click') robot.mouseClick();
|
|
} else if (data.type === 'keyboard') {
|
|
robot.keyTap(data.key);
|
|
}
|
|
} catch (e) {
|
|
console.error('robotjs 오류:', e);
|
|
}
|
|
})
|
|
|