const { spawn } = require('child_process'); const path = require('path'); const fs = require('fs'); const config = require('../../config'); const paths = require('../../utils/paths'); const logger = require('../../utils/logger'); class FRPService { constructor(options = {}) { this.enabled = options.enabled !== false; this.frpcPath = options.frpcPath || path.join(paths.getFRPPath(), 'frpc.exe'); this.configPath = options.configPath || path.join(paths.getFRPPath(), 'frpc.toml'); this.process = null; this.isRunning = false; } _prepareConfig() { const frpDir = paths.getFRPPath(); const logPath = path.join(paths.getBasePath(), 'logs', 'frpc.log'); const logsDir = path.dirname(logPath); if (!fs.existsSync(logsDir)) { fs.mkdirSync(logsDir, { recursive: true }); } if (fs.existsSync(this.configPath)) { let content = fs.readFileSync(this.configPath, 'utf8'); content = content.replace(/log\.to\s*=\s*"[^"]*"/, `log.to = "${logPath.replace(/\\/g, '\\\\')}"`); const tempConfigPath = path.join(frpDir, 'frpc-runtime.toml'); fs.writeFileSync(tempConfigPath, content); return tempConfigPath; } return this.configPath; } start() { if (!this.enabled) { logger.info('FRP service is disabled'); return; } if (this.isRunning) { logger.warn('FRP service is already running'); return; } try { if (!fs.existsSync(this.frpcPath)) { logger.error('FRP client not found', { path: this.frpcPath }); return; } if (!fs.existsSync(this.configPath)) { logger.error('FRP config not found', { path: this.configPath }); return; } const runtimeConfigPath = this._prepareConfig(); logger.info('Starting FRP client', { frpcPath: this.frpcPath, configPath: runtimeConfigPath }); this.process = spawn(this.frpcPath, ['-c', runtimeConfigPath], { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true }); this.isRunning = true; this.process.stdout.on('data', (data) => { const output = data.toString().trim(); if (output) { logger.info(`[FRP] ${output}`); } }); this.process.stderr.on('data', (data) => { const output = data.toString().trim(); if (output) { logger.error(`[FRP] ${output}`); } }); this.process.on('error', (error) => { logger.error('FRP process error', { error: error.message }); this.isRunning = false; }); this.process.on('close', (code) => { logger.info('FRP process closed', { code }); this.isRunning = false; this.process = null; }); logger.info('FRP service started successfully'); } catch (error) { logger.error('Failed to start FRP service', { error: error.message }); this.isRunning = false; } } stop() { if (!this.isRunning || !this.process) { return; } logger.info('Stopping FRP service'); try { this.process.kill(); this.process = null; this.isRunning = false; logger.info('FRP service stopped'); } catch (error) { logger.error('Failed to stop FRP service', { error: error.message }); } } getStatus() { return { enabled: this.enabled, running: this.isRunning }; } } module.exports = FRPService;