419 lines
14 KiB
JavaScript
419 lines
14 KiB
JavaScript
/**
|
|
* 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;
|
|
}
|