/** * Local Server for Singleplayer Mode * A simplified server that runs within the Electron client for offline/singleplayer functionality * NOTE: This version requires express, socket.io, and cors dependencies to be installed */ class LocalServer { constructor() { this.app = null; this.server = null; this.io = null; this.port = null; this.isRunning = false; this.connectedClients = new Map(); console.log('[LOCAL SERVER] LocalServer initialized'); } async initialize() { try { // Try to require dependencies if (typeof require !== 'undefined') { const express = require('express'); const { createServer } = require('http'); const { Server } = require('socket.io'); const cors = require('cors'); this.setupExpress(express, cors); this.createServer = createServer; this.ServerClass = Server; console.log('[LOCAL SERVER] Dependencies loaded successfully'); return true; } else { console.warn('[LOCAL SERVER] require() not available, running in browser context'); return false; } } catch (error) { console.error('[LOCAL SERVER] Failed to load dependencies:', error.message); console.log('[LOCAL SERVER] Please install dependencies: npm install express socket.io cors'); return false; } } setupExpress(express, cors) { // Initialize Express app this.app = express(); // Middleware this.app.use(cors({ origin: ["http://localhost:3000", "http://127.0.0.1:3000", "file://"], credentials: true })); this.app.use(express.json({ limit: '10mb' })); this.app.use(express.urlencoded({ extended: true })); // Health check endpoint this.app.get('/health', (req, res) => { res.status(200).json({ status: 'OK', timestamp: new Date().toISOString(), uptime: process.uptime(), mode: 'local' }); }); // API version endpoint this.app.get('/api/ssc/version', (req, res) => { res.status(200).json({ version: '1.0.0', service: 'galaxystrikeonline-local-server', timestamp: new Date().toISOString(), mode: 'local' }); }); // Mock authentication endpoints for singleplayer this.app.post('/api/auth/login', (req, res) => { const { email, password } = req.body; // Auto-authenticate for singleplayer mode const mockUser = { id: 'local-user', email: email || 'local@player.com', username: 'Local Player', token: 'local-token-' + Date.now(), createdAt: new Date().toISOString() }; res.status(200).json({ success: true, user: mockUser, token: mockUser.token, message: 'Logged in to local mode' }); }); this.app.post('/api/auth/register', (req, res) => { const { email, password, username } = req.body; // Auto-register for singleplayer mode const mockUser = { id: 'local-user', email: email || 'local@player.com', username: username || 'Local Player', token: 'local-token-' + Date.now(), createdAt: new Date().toISOString() }; res.status(201).json({ success: true, user: mockUser, token: mockUser.token, message: 'Registered in local mode' }); }); // Mock server browser endpoints this.app.get('/api/servers', (req, res) => { // Return a single local server const localServer = { id: 'local-server', name: 'Local Singleplayer', description: 'Your personal local server for singleplayer gaming', type: 'private', region: 'local', maxPlayers: 1, currentPlayers: 0, owner: 'Local Player', address: 'localhost', port: this.port, status: 'online', createdAt: new Date().toISOString(), ping: 0 }; res.status(200).json({ success: true, servers: [localServer] }); }); // Mock game data endpoints this.app.get('/api/game/player/:id', (req, res) => { // Return player data from local storage if available const playerId = req.params.id; let saveData; try { // In Electron context, access localStorage differently if (typeof localStorage !== 'undefined') { saveData = localStorage.getItem(`gso_save_slot_1`); } } catch (error) { console.warn('[LOCAL SERVER] Could not access localStorage:', error); } if (saveData) { try { const parsedData = JSON.parse(saveData); res.status(200).json({ success: true, player: parsedData.player }); } catch (error) { res.status(500).json({ success: false, error: 'Failed to parse save data' }); } } else { res.status(404).json({ success: false, error: 'No save data found' }); } }); this.app.post('/api/game/player/:id/save', (req, res) => { // Save player data to local storage const playerId = req.params.id; const playerData = req.body; try { let existingSaveData = '{}'; // In Electron context, access localStorage differently if (typeof localStorage !== 'undefined') { existingSaveData = localStorage.getItem(`gso_save_slot_1`) || '{}'; } const parsedExisting = JSON.parse(existingSaveData); // Merge player data parsedExisting.player = playerData; parsedExisting.lastSave = Date.now(); if (typeof localStorage !== 'undefined') { localStorage.setItem(`gso_save_slot_1`, JSON.stringify(parsedExisting)); } res.status(200).json({ success: true, message: 'Player data saved locally' }); } catch (error) { res.status(500).json({ success: false, error: 'Failed to save player data' }); } }); } async start(port = 3004) { if (this.isRunning) { console.log('[LOCAL SERVER] Server is already running on port', this.port); return { success: false, error: 'Server already running' }; } try { // Initialize dependencies if not already done if (!this.app) { const initialized = await this.initialize(); if (!initialized) { return { success: false, error: 'Failed to initialize server dependencies' }; } } this.port = port; this.server = this.createServer(this.app); this.io = new this.ServerClass(this.server, { cors: { origin: ["http://localhost:3000", "http://127.0.0.1:3000", "file://"], methods: ["GET", "POST"] } }); // Setup Socket.IO handlers this.setupSocketHandlers(); // Start the server await new Promise((resolve, reject) => { this.server.listen(port, (error) => { if (error) { reject(error); } else { resolve(); } }); }); this.isRunning = true; console.log(`[LOCAL SERVER] Local server started on port ${port}`); return { success: true, port: port, url: `http://localhost:${port}` }; } catch (error) { console.error('[LOCAL SERVER] Failed to start server:', error); return { success: false, error: error.message }; } } setupSocketHandlers() { this.io.on('connection', (socket) => { console.log('[LOCAL SERVER] Client connected:', socket.id); this.connectedClients.set(socket.id, { connectedAt: Date.now(), playerData: null }); // Handle authentication socket.on('authenticate', (data) => { console.log('[LOCAL SERVER] Authenticating client:', socket.id, data); // Auto-authenticate for local mode socket.emit('authenticated', { success: true, user: { id: 'local-user', username: 'Local Player', token: 'local-token-' + Date.now() } }); // Update client info const clientInfo = this.connectedClients.get(socket.id); if (clientInfo) { clientInfo.playerData = { id: 'local-user', username: 'Local Player' }; } }); // Handle game data sync socket.on('saveGameData', (data) => { console.log('[LOCAL SERVER] Saving game data for:', socket.id); // Save to localStorage (this will be handled by the client-side save system) socket.emit('gameDataSaved', { success: true, timestamp: Date.now() }); }); socket.on('loadGameData', (data) => { console.log('[LOCAL SERVER] Loading game data for:', socket.id); // Load from localStorage (this will be handled by the client-side load system) let saveData; try { if (typeof localStorage !== 'undefined') { saveData = localStorage.getItem(`gso_save_slot_1`); } } catch (error) { console.warn('[LOCAL SERVER] Could not access localStorage:', error); } if (saveData) { try { const parsedData = JSON.parse(saveData); socket.emit('gameDataLoaded', { success: true, data: parsedData }); } catch (error) { socket.emit('gameDataLoaded', { success: false, error: 'Failed to parse save data' }); } } else { socket.emit('gameDataLoaded', { success: false, error: 'No save data found' }); } }); // Handle disconnection socket.on('disconnect', () => { console.log('[LOCAL SERVER] Client disconnected:', socket.id); this.connectedClients.delete(socket.id); }); // Send welcome message socket.emit('welcome', { message: 'Connected to local server', serverInfo: { mode: 'local', port: this.port, timestamp: new Date().toISOString() } }); }); } async stop() { if (!this.isRunning) { console.log('[LOCAL SERVER] Server is not running'); return { success: false, error: 'Server is not running' }; } try { // Disconnect all clients if (this.io) { this.io.disconnectSockets(); } // Close the server if (this.server) { await new Promise((resolve) => { this.server.close(resolve); }); } this.isRunning = false; this.port = null; this.connectedClients.clear(); console.log('[LOCAL SERVER] Local server stopped'); return { success: true }; } catch (error) { console.error('[LOCAL SERVER] Failed to stop server:', error); return { success: false, error: error.message }; } } getStatus() { return { isRunning: this.isRunning, port: this.port, connectedClients: this.connectedClients.size, uptime: this.isRunning ? process.uptime() : 0 }; } getUrl() { return this.isRunning ? `http://localhost:${this.port}` : null; } } // Export for use in Node.js environment if (typeof module !== 'undefined' && module.exports) { module.exports = LocalServer; } // Export for use in browser environment if (typeof window !== 'undefined') { window.LocalServer = LocalServer; }