feat: implement complete Phase 2 frontend foundation with React 18

Major milestone: Frontend implementation complete for Shattered Void MMO

FRONTEND IMPLEMENTATION:
- React 18 + TypeScript + Vite development environment
- Tailwind CSS with custom dark theme for sci-fi aesthetic
- Zustand state management with authentication persistence
- Socket.io WebSocket client with auto-reconnection
- Protected routing with authentication guards
- Responsive design with mobile-first approach

AUTHENTICATION SYSTEM:
- Login/register forms with comprehensive validation
- JWT token management with localStorage persistence
- Password strength validation and user feedback
- Protected routes and authentication guards

CORE GAME INTERFACE:
- Colony management dashboard with real-time updates
- Resource display with live production tracking
- WebSocket integration for real-time game events
- Navigation with connection status indicator
- Toast notifications for user feedback

BACKEND ENHANCEMENTS:
- Complete Research System with technology tree (23 technologies)
- Fleet Management System with ship designs and movement
- Enhanced Authentication with email verification and password reset
- Complete game tick integration for all systems
- Advanced WebSocket events for real-time updates

ARCHITECTURE FEATURES:
- Type-safe TypeScript throughout
- Component-based architecture with reusable UI elements
- API client with request/response interceptors
- Error handling and loading states
- Performance optimized builds with code splitting

Phase 2 Status: Frontend foundation complete (Week 1-2 objectives met)
Ready for: Colony management, fleet operations, research interface
Next: Enhanced gameplay features and admin interface

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
MegaProxy 2025-08-02 18:36:06 +00:00
parent 8d9ef427be
commit d41d1e8125
130 changed files with 33588 additions and 14817 deletions

View file

@ -9,65 +9,65 @@ const logger = require('../utils/logger');
// Rate limiting configuration
const RATE_LIMIT_CONFIG = {
// Global API rate limits
global: {
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 15 * 60 * 1000, // 15 minutes
max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) || 1000, // 1000 requests per window
standardHeaders: true,
legacyHeaders: false,
skipSuccessfulRequests: false,
skipFailedRequests: false
},
// Global API rate limits
global: {
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 15 * 60 * 1000, // 15 minutes
max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) || 1000, // 1000 requests per window
standardHeaders: true,
legacyHeaders: false,
skipSuccessfulRequests: false,
skipFailedRequests: false,
},
// Authentication endpoints (more restrictive)
auth: {
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10, // 10 attempts per window
standardHeaders: true,
legacyHeaders: false,
skipSuccessfulRequests: true, // Don't count successful logins
skipFailedRequests: false
},
// Authentication endpoints (more restrictive)
auth: {
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10, // 10 attempts per window
standardHeaders: true,
legacyHeaders: false,
skipSuccessfulRequests: true, // Don't count successful logins
skipFailedRequests: false,
},
// Player API endpoints
player: {
windowMs: 1 * 60 * 1000, // 1 minute
max: 120, // 120 requests per minute
standardHeaders: true,
legacyHeaders: false,
skipSuccessfulRequests: false,
skipFailedRequests: false
},
// Player API endpoints
player: {
windowMs: 1 * 60 * 1000, // 1 minute
max: 120, // 120 requests per minute
standardHeaders: true,
legacyHeaders: false,
skipSuccessfulRequests: false,
skipFailedRequests: false,
},
// Admin API endpoints (more lenient for legitimate admin users)
admin: {
windowMs: 1 * 60 * 1000, // 1 minute
max: 300, // 300 requests per minute
standardHeaders: true,
legacyHeaders: false,
skipSuccessfulRequests: false,
skipFailedRequests: false
},
// Admin API endpoints (more lenient for legitimate admin users)
admin: {
windowMs: 1 * 60 * 1000, // 1 minute
max: 300, // 300 requests per minute
standardHeaders: true,
legacyHeaders: false,
skipSuccessfulRequests: false,
skipFailedRequests: false,
},
// Game action endpoints (prevent spam)
gameAction: {
windowMs: 30 * 1000, // 30 seconds
max: 30, // 30 actions per 30 seconds
standardHeaders: true,
legacyHeaders: false,
skipSuccessfulRequests: false,
skipFailedRequests: true
},
// Game action endpoints (prevent spam)
gameAction: {
windowMs: 30 * 1000, // 30 seconds
max: 30, // 30 actions per 30 seconds
standardHeaders: true,
legacyHeaders: false,
skipSuccessfulRequests: false,
skipFailedRequests: true,
},
// Message sending (prevent spam)
messaging: {
windowMs: 5 * 60 * 1000, // 5 minutes
max: 10, // 10 messages per 5 minutes
standardHeaders: true,
legacyHeaders: false,
skipSuccessfulRequests: false,
skipFailedRequests: true
}
// Message sending (prevent spam)
messaging: {
windowMs: 5 * 60 * 1000, // 5 minutes
max: 10, // 10 messages per 5 minutes
standardHeaders: true,
legacyHeaders: false,
skipSuccessfulRequests: false,
skipFailedRequests: true,
},
};
/**
@ -75,34 +75,34 @@ const RATE_LIMIT_CONFIG = {
* @returns {Object|null} Redis store or null if Redis unavailable
*/
function createRedisStore() {
try {
const redis = getRedisClient();
if (!redis) {
logger.warn('Redis not available for rate limiting, using memory store');
return null;
}
// Create Redis store for express-rate-limit
try {
const { RedisStore } = require('rate-limit-redis');
return new RedisStore({
sendCommand: (...args) => redis.sendCommand(args),
prefix: 'rl:' // Rate limit prefix
});
} catch (error) {
logger.warn('Failed to create RedisStore, falling back to memory store', {
error: error.message
});
return null;
}
} catch (error) {
logger.warn('Failed to create Redis store for rate limiting', {
error: error.message
});
return null;
try {
const redis = getRedisClient();
if (!redis) {
logger.warn('Redis not available for rate limiting, using memory store');
return null;
}
// Create Redis store for express-rate-limit
try {
const { RedisStore } = require('rate-limit-redis');
return new RedisStore({
sendCommand: (...args) => redis.sendCommand(args),
prefix: 'rl:', // Rate limit prefix
});
} catch (error) {
logger.warn('Failed to create RedisStore, falling back to memory store', {
error: error.message,
});
return null;
}
} catch (error) {
logger.warn('Failed to create Redis store for rate limiting', {
error: error.message,
});
return null;
}
}
/**
@ -111,11 +111,11 @@ function createRedisStore() {
* @returns {Function} Key generator function
*/
function createKeyGenerator(prefix = 'global') {
return (req) => {
const ip = req.ip || req.connection.remoteAddress || 'unknown';
const userId = req.user?.playerId || req.user?.adminId || 'anonymous';
return `${prefix}:${userId}:${ip}`;
};
return (req) => {
const ip = req.ip || req.connection.remoteAddress || 'unknown';
const userId = req.user?.playerId || req.user?.adminId || 'anonymous';
return `${prefix}:${userId}:${ip}`;
};
}
/**
@ -124,32 +124,32 @@ function createKeyGenerator(prefix = 'global') {
* @returns {Function} Rate limit handler function
*/
function createRateLimitHandler(type) {
return (req, res) => {
const correlationId = req.correlationId;
const ip = req.ip || req.connection.remoteAddress;
const userId = req.user?.playerId || req.user?.adminId;
const userType = req.user?.type || 'anonymous';
return (req, res) => {
const correlationId = req.correlationId;
const ip = req.ip || req.connection.remoteAddress;
const userId = req.user?.playerId || req.user?.adminId;
const userType = req.user?.type || 'anonymous';
logger.warn('Rate limit exceeded', {
correlationId,
type,
ip,
userId,
userType,
path: req.path,
method: req.method,
userAgent: req.get('User-Agent'),
retryAfter: res.get('Retry-After')
});
logger.warn('Rate limit exceeded', {
correlationId,
type,
ip,
userId,
userType,
path: req.path,
method: req.method,
userAgent: req.get('User-Agent'),
retryAfter: res.get('Retry-After'),
});
return res.status(429).json({
error: 'Too Many Requests',
message: 'Rate limit exceeded. Please try again later.',
type: type,
retryAfter: res.get('Retry-After'),
correlationId
});
};
return res.status(429).json({
error: 'Too Many Requests',
message: 'Rate limit exceeded. Please try again later.',
type,
retryAfter: res.get('Retry-After'),
correlationId,
});
};
}
/**
@ -159,31 +159,31 @@ function createRateLimitHandler(type) {
* @returns {Function} Skip function
*/
function createSkipFunction(skipPaths = [], skipIPs = []) {
return (req) => {
const ip = req.ip || req.connection.remoteAddress;
// Skip health checks
if (req.path === '/health' || req.path === '/api/health') {
return true;
}
return (req) => {
const ip = req.ip || req.connection.remoteAddress;
// Skip specified paths
if (skipPaths.some(path => req.path.startsWith(path))) {
return true;
}
// Skip health checks
if (req.path === '/health' || req.path === '/api/health') {
return true;
}
// Skip specified IPs (for development/testing)
if (skipIPs.includes(ip)) {
return true;
}
// Skip specified paths
if (skipPaths.some(path => req.path.startsWith(path))) {
return true;
}
// Skip if rate limiting is disabled
if (process.env.DISABLE_RATE_LIMITING === 'true') {
return true;
}
// Skip specified IPs (for development/testing)
if (skipIPs.includes(ip)) {
return true;
}
return false;
};
// Skip if rate limiting is disabled
if (process.env.DISABLE_RATE_LIMITING === 'true') {
return true;
}
return false;
};
}
/**
@ -193,40 +193,40 @@ function createSkipFunction(skipPaths = [], skipIPs = []) {
* @returns {Function} Rate limiter middleware
*/
function createRateLimiter(type, customConfig = {}) {
const config = { ...RATE_LIMIT_CONFIG[type], ...customConfig };
const store = createRedisStore();
const config = { ...RATE_LIMIT_CONFIG[type], ...customConfig };
const store = createRedisStore();
const rateLimiter = rateLimit({
...config,
store,
keyGenerator: createKeyGenerator(type),
handler: createRateLimitHandler(type),
skip: createSkipFunction(),
// Note: onLimitReached is deprecated in express-rate-limit v7
// Removed for compatibility
});
const rateLimiter = rateLimit({
...config,
store,
keyGenerator: createKeyGenerator(type),
handler: createRateLimitHandler(type),
skip: createSkipFunction(),
// Note: onLimitReached is deprecated in express-rate-limit v7
// Removed for compatibility
});
// Log rate limiter creation
logger.info('Rate limiter created', {
type,
windowMs: config.windowMs,
max: config.max,
useRedis: !!store
});
// Log rate limiter creation
logger.info('Rate limiter created', {
type,
windowMs: config.windowMs,
max: config.max,
useRedis: !!store,
});
return rateLimiter;
return rateLimiter;
}
/**
* Pre-configured rate limiters
*/
const rateLimiters = {
global: createRateLimiter('global'),
auth: createRateLimiter('auth'),
player: createRateLimiter('player'),
admin: createRateLimiter('admin'),
gameAction: createRateLimiter('gameAction'),
messaging: createRateLimiter('messaging')
global: createRateLimiter('global'),
auth: createRateLimiter('auth'),
player: createRateLimiter('player'),
admin: createRateLimiter('admin'),
gameAction: createRateLimiter('gameAction'),
messaging: createRateLimiter('messaging'),
};
/**
@ -236,12 +236,12 @@ const rateLimiters = {
* @param {Function} next - Express next function
*/
function addRateLimitHeaders(req, res, next) {
// Add custom headers for client information
res.set({
'X-RateLimit-Policy': 'See API documentation for rate limiting details'
});
// Add custom headers for client information
res.set({
'X-RateLimit-Policy': 'See API documentation for rate limiting details',
});
next();
next();
}
/**
@ -251,42 +251,42 @@ function addRateLimitHeaders(req, res, next) {
* @returns {Function} WebSocket rate limiter function
*/
function createWebSocketRateLimiter(maxConnections = 10, windowMs = 60000) {
const connections = new Map();
const connections = new Map();
return (socket, next) => {
const ip = socket.handshake.address;
const now = Date.now();
return (socket, next) => {
const ip = socket.handshake.address;
const now = Date.now();
// Clean up old connections
if (connections.has(ip)) {
const connectionTimes = connections.get(ip).filter(time => now - time < windowMs);
connections.set(ip, connectionTimes);
}
// Clean up old connections
if (connections.has(ip)) {
const connectionTimes = connections.get(ip).filter(time => now - time < windowMs);
connections.set(ip, connectionTimes);
}
// Check rate limit
const currentConnections = connections.get(ip) || [];
if (currentConnections.length >= maxConnections) {
logger.warn('WebSocket connection rate limit exceeded', {
ip,
currentConnections: currentConnections.length,
maxConnections
});
// Check rate limit
const currentConnections = connections.get(ip) || [];
if (currentConnections.length >= maxConnections) {
logger.warn('WebSocket connection rate limit exceeded', {
ip,
currentConnections: currentConnections.length,
maxConnections,
});
return next(new Error('Connection rate limit exceeded'));
}
return next(new Error('Connection rate limit exceeded'));
}
// Add current connection
currentConnections.push(now);
connections.set(ip, currentConnections);
// Add current connection
currentConnections.push(now);
connections.set(ip, currentConnections);
logger.debug('WebSocket connection allowed', {
ip,
connections: currentConnections.length,
maxConnections
});
logger.debug('WebSocket connection allowed', {
ip,
connections: currentConnections.length,
maxConnections,
});
next();
};
next();
};
}
/**
@ -296,25 +296,25 @@ function createWebSocketRateLimiter(maxConnections = 10, windowMs = 60000) {
* @param {Function} next - Express next function
*/
function dynamicRateLimit(req, res, next) {
const userType = req.user?.type;
let limiter;
if (userType === 'admin') {
limiter = rateLimiters.admin;
} else if (userType === 'player') {
limiter = rateLimiters.player;
} else {
limiter = rateLimiters.global;
}
const userType = req.user?.type;
return limiter(req, res, next);
let limiter;
if (userType === 'admin') {
limiter = rateLimiters.admin;
} else if (userType === 'player') {
limiter = rateLimiters.player;
} else {
limiter = rateLimiters.global;
}
return limiter(req, res, next);
}
module.exports = {
rateLimiters,
createRateLimiter,
createWebSocketRateLimiter,
addRateLimitHeaders,
dynamicRateLimit,
RATE_LIMIT_CONFIG
};
rateLimiters,
createRateLimiter,
createWebSocketRateLimiter,
addRateLimitHeaders,
dynamicRateLimit,
RATE_LIMIT_CONFIG,
};