다운로드 화면 추가 및 full exe 추가

This commit is contained in:
backuppc
2026-01-21 09:31:31 +09:00
parent a1a1971a1f
commit c5e7ec8436
8 changed files with 1894 additions and 191 deletions

View File

@@ -18,6 +18,26 @@ const path = require('path');
const os = require('os');
const { exec } = require('child_process');
const readline = require('readline');
const http = require('http');
// MIME Types for static serving
const MIME_TYPES = {
'.html': 'text/html',
'.js': 'text/javascript',
'.css': 'text/css',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.ttf': 'font/ttf',
'.eot': 'application/vnd.ms-fontobject',
'.otf': 'font/otf',
'.wasm': 'application/wasm'
};
// --- 로컬 저장소 경로 설정 (AppData 구현) ---
function getConfigDir() {
@@ -38,11 +58,91 @@ if (!fs.existsSync(configDir)) {
const PORT = 8090;
// --- 서버 시작 함수 (재시도 로직 포함) ---
// --- 서버 시작 함수 (재시도 로직 포함) ---
function startServer() {
const wss = new WebSocket.Server({ port: PORT });
const server = http.createServer((req, res) => {
// Basic Static File Server
// pkg friendly path: use path.join(__dirname, 'dist')
// When packaged with pkg, __dirname points to the virtual filesystem inside the executable
wss.on('error', (err) => {
// Special handling for executables (Download Center)
// Serve from the host filesystem (where the .exe is running)
// This prevents the need to bundle the executables inside the pkg bundle itself
if (req.url === '/webftp.exe' || req.url === '/webftp-backend.exe') {
const exeDir = path.dirname(process.execPath); // Directory of the running executable
// Fallback for dev mode (node backend_proxy.cjs) -> serve from public or current dir
const targetPath = process.pkg ? path.join(exeDir, req.url) : path.join(__dirname, 'public', req.url);
if (fs.existsSync(targetPath)) {
const stat = fs.statSync(targetPath);
// Handle HEAD request for size check
if (req.method === 'HEAD') {
res.writeHead(200, {
'Content-Type': 'application/octet-stream',
'Content-Length': stat.size
});
res.end();
return;
}
res.writeHead(200, {
'Content-Type': 'application/octet-stream',
'Content-Length': stat.size,
'Content-Disposition': `attachment; filename="${path.basename(targetPath)}"`
});
const readStream = fs.createReadStream(targetPath);
readStream.pipe(res);
return;
}
}
let filePath = path.join(__dirname, 'dist', req.url === '/' ? 'index.html' : req.url);
// Prevent directory traversal
if (!filePath.startsWith(path.join(__dirname, 'dist'))) {
res.writeHead(403);
res.end('Forbidden');
return;
}
const extname = path.extname(filePath);
let contentType = MIME_TYPES[extname] || 'application/octet-stream';
fs.readFile(filePath, (err, content) => {
if (err) {
if (err.code === 'ENOENT') {
// SPA fallback: serve index.html for unknown paths (if not an asset)
if (!extname || extname === '.html') {
fs.readFile(path.join(__dirname, 'dist', 'index.html'), (err2, content2) => {
if (err2) {
res.writeHead(404);
res.end('404 Not Found');
} else {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(content2, 'utf-8');
}
});
} else {
res.writeHead(404);
res.end('File Not Found');
}
} else {
res.writeHead(500);
res.end(`Server Error: ${err.code}`);
}
} else {
res.writeHead(200, { 'Content-Type': contentType });
res.end(content, 'utf-8');
}
});
});
const wss = new WebSocket.Server({ server });
server.on('error', (err) => {
if (err.code === 'EADDRINUSE') {
console.error(`\n❌ 포트 ${PORT}이(가) 이미 사용 중입니다.`);
handlePortConflict();
@@ -52,9 +152,12 @@ function startServer() {
}
});
wss.on('listening', () => {
console.log(`\n🚀 WebZilla FTP Proxy Server [v${APP_VERSION}] 가 ws://localhost:${PORT} 에서 실행 중입니다.`);
server.listen(PORT, () => {
console.log(`\n🚀 WebFTP Proxy Server [v${APP_VERSION}] 가 http://localhost:${PORT} 에서 실행 중입니다.`);
console.log(`📂 설정 폴더: ${configDir}`);
// Open Browser
openBrowser(`http://localhost:${PORT}`);
});
wss.on('connection', (ws) => {
@@ -383,5 +486,14 @@ function askToKill(pid) {
});
}
function openBrowser(url) {
const start = (process.platform == 'darwin' ? 'open' : process.platform == 'win32' ? 'start' : 'xdg-open');
exec(`${start} ${url}`, (err) => {
if (err) {
console.log("브라우저를 자동으로 열 수 없습니다:", err.message);
}
});
}
// 초기 실행
startServer();