/** * WebZilla 백엔드 프록시 서버 (Node.js) v1.1 * * 기능 추가: * - 로컬 파일 시스템 접근 (fs) * - 설정 데이터 저장 (AppData/Roaming 또는 ~/.config) */ const WebSocket = require('ws'); const ftp = require('basic-ftp'); const fs = require('fs'); const path = require('path'); const os = require('os'); // --- 로컬 저장소 경로 설정 (AppData 구현) --- function getConfigDir() { const homedir = os.homedir(); // Windows: C:\Users\User\AppData\Roaming\WebZilla if (process.platform === 'win32') { return path.join(process.env.APPDATA || path.join(homedir, 'AppData', 'Roaming'), 'WebZilla'); } // macOS: ~/Library/Application Support/WebZilla else if (process.platform === 'darwin') { return path.join(homedir, 'Library', 'Application Support', 'WebZilla'); } // Linux: ~/.config/webzilla else { return path.join(homedir, '.config', 'webzilla'); } } // 앱 시작 시 설정 디렉토리 생성 const configDir = getConfigDir(); if (!fs.existsSync(configDir)) { try { fs.mkdirSync(configDir, { recursive: true }); console.log(`📂 설정 폴더가 생성되었습니다: ${configDir}`); } catch (e) { console.error(`❌ 설정 폴더 생성 실패: ${e.message}`); } } else { console.log(`📂 설정 폴더 로드됨: ${configDir}`); } const wss = new WebSocket.Server({ port: 8080 }); console.log("🚀 WebZilla FTP Proxy Server가 ws://localhost:8080 에서 실행 중입니다."); wss.on('connection', (ws) => { console.log("클라이언트가 접속했습니다."); const client = new ftp.Client(); ws.on('message', async (message) => { try { const data = JSON.parse(message); switch (data.command) { // --- FTP 연결 관련 --- case 'CONNECT': console.log(`FTP 연결 시도: ${data.user}@${data.host}:${data.port}`); try { await client.access({ host: data.host, user: data.user, password: data.pass, port: parseInt(data.port), secure: false }); ws.send(JSON.stringify({ type: 'status', status: 'connected', message: 'FTP 서버에 연결되었습니다.' })); console.log("FTP 연결 성공"); } catch (err) { ws.send(JSON.stringify({ type: 'error', message: `연결 실패: ${err.message}` })); } break; case 'LIST': if (client.closed) { ws.send(JSON.stringify({ type: 'error', message: 'FTP 연결이 끊어져 있습니다.' })); return; } const listPath = data.path || '/'; const list = await client.list(listPath); const files = list.map(f => ({ id: `ftp-${Date.now()}-${Math.random()}`, name: f.name, type: f.isDirectory ? 'FOLDER' : 'FILE', size: f.size, date: f.rawModifiedAt || new Date().toISOString(), permissions: '-' })); ws.send(JSON.stringify({ type: 'list', files, path: listPath })); break; case 'DISCONNECT': client.close(); ws.send(JSON.stringify({ type: 'status', status: 'disconnected', message: '연결이 종료되었습니다.' })); break; // --- 로컬 설정 저장 관련 (새로 추가됨) --- case 'SAVE_SITE': // 예: 사이트 정보를 JSON 파일로 저장 try { const sitesFile = path.join(configDir, 'sites.json'); let sites = []; if (fs.existsSync(sitesFile)) { sites = JSON.parse(fs.readFileSync(sitesFile, 'utf8')); } sites.push(data.siteInfo); fs.writeFileSync(sitesFile, JSON.stringify(sites, null, 2)); console.log(`💾 사이트 정보 저장됨: ${data.siteInfo.host}`); ws.send(JSON.stringify({ type: 'success', message: '사이트 정보가 로컬(AppData)에 저장되었습니다.' })); } catch (err) { ws.send(JSON.stringify({ type: 'error', message: `저장 실패: ${err.message}` })); } break; case 'GET_SITES': try { const sitesFile = path.join(configDir, 'sites.json'); if (fs.existsSync(sitesFile)) { const sites = JSON.parse(fs.readFileSync(sitesFile, 'utf8')); ws.send(JSON.stringify({ type: 'sites_list', sites })); } else { ws.send(JSON.stringify({ type: 'sites_list', sites: [] })); } } catch (err) { ws.send(JSON.stringify({ type: 'error', message: `로드 실패: ${err.message}` })); } break; default: console.log(`알 수 없는 명령: ${data.command}`); } } catch (err) { console.error("오류 발생:", err); ws.send(JSON.stringify({ type: 'error', message: err.message })); } }); ws.on('close', () => { console.log("클라이언트 접속 종료"); client.close(); }); });