Troubleshooting - RumenDamyanov/js-chess GitHub Wiki
Comprehensive troubleshooting guide for common issues and problems in the chess showcase application.
This guide covers troubleshooting for:
- Development environment setup issues
- Build and compilation problems
- Runtime errors and bugs
- Performance issues
- Network and connectivity problems
- Chess engine and game logic issues
- Framework-specific problems
# Error message
npm ERR! code EACCES
npm ERR! syscall rename
npm ERR! path /usr/local/lib/node_modules/npm
npm ERR! errno -13
Solution:
# Fix npm permissions (macOS/Linux)
sudo chown -R $(whoami) ~/.npm
sudo chown -R $(whoami) /usr/local/lib/node_modules
# Or use nvm for better Node.js management
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
nvm install 18
nvm use 18
Error: Cannot resolve module 'some-package'
Solution:
# Clear npm cache
npm cache clean --force
# Delete node_modules and reinstall
rm -rf node_modules package-lock.json
npm install
# Check for conflicting package versions
npm ls
npm audit fix
npm WARN peer dep missing: react@^17.0.0
Solution:
# Check package compatibility
npm ls react
npm info react versions --json
# Install compatible versions
npm install react@^17.0.0 react-dom@^17.0.0
# Use npm-check-updates for major updates
npx npm-check-updates -u
npm install
<<<<<<< HEAD
"lockfileVersion": 2,
=======
"lockfileVersion": 1,
>>>>>>> feature-branch
Solution:
# Delete conflicted lock file and regenerate
rm package-lock.json
npm install
# Add to git
git add package-lock.json
git commit -m "Regenerate package-lock.json"
remote: error: File dist/chess-engine.wasm is 134.00 MB; this exceeds GitHub's file size limit of 100.00 MB
Solution:
# Remove large files from history
git filter-branch --tree-filter 'rm -rf dist/' HEAD
# Use Git LFS for large files
git lfs track "*.wasm"
git add .gitattributes
git add file.wasm
git commit -m "Track large files with Git LFS"
// Error: Duplicate identifier 'ChessPosition'
interface ChessPosition {
rank: number;
file: number;
}
Solution:
// Use module augmentation or namespaces
declare namespace Chess {
interface Position {
rank: number;
file: number;
}
}
// Or use different names
interface ChessBoardPosition {
rank: number;
file: number;
}
// Error: Cannot find module '../../shared/chess'
import { ChessEngine } from '../../shared/chess';
Solution:
// tsconfig.json - Add path mapping
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@shared/*": ["shared/*"],
"@chess/*": ["shared/chess/*"]
}
}
}
// Use the mapped paths
import { ChessEngine } from '@chess/engine';
WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB).
Solution:
// webpack.config.js - Add code splitting
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
chess: {
test: /[\\/]src[\\/]chess[\\/]/,
name: 'chess-engine',
chunks: 'all',
}
}
}
}
};
Module not found: Error: Can't resolve 'fs' in '/path/to/project'
Solution:
// webpack.config.js - Add fallbacks for Node.js modules
module.exports = {
resolve: {
fallback: {
"fs": false,
"path": require.resolve("path-browserify"),
"crypto": require.resolve("crypto-browserify")
}
}
};
Error: Zone already loaded
Solution:
// main.ts - Import zone.js only once
import 'zone.js/dist/zone';
// Remove other zone.js imports
// Or configure zone.js
(window as any).__Zone_disable_requestAnimationFrame = true;
(window as any).__Zone_disable_on_property = true;
// Error: Component is not standalone
@Component({
selector: 'app-chess-board',
template: '<div>Chess Board</div>'
})
export class ChessBoardComponent { }
Solution:
// Make component standalone
@Component({
selector: 'app-chess-board',
standalone: true,
imports: [CommonModule],
template: '<div>Chess Board</div>'
})
export class ChessBoardComponent { }
// Warning: React Hook useEffect has a missing dependency
useEffect(() => {
fetchGameData(gameId);
}, []); // Missing gameId dependency
Solution:
// Add missing dependencies
useEffect(() => {
fetchGameData(gameId);
}, [gameId]);
// Or use useCallback for stable references
const fetchData = useCallback(() => {
fetchGameData(gameId);
}, [gameId]);
useEffect(() => {
fetchData();
}, [fetchData]);
// Warning: Can't perform a React state update on an unmounted component
Solution:
// Use cleanup function and ref
function ChessGame() {
const [gameState, setGameState] = useState(null);
const mounted = useRef(true);
useEffect(() => {
return () => {
mounted.current = false;
};
}, []);
const updateGameState = (newState) => {
if (mounted.current) {
setGameState(newState);
}
};
// Or use AbortController for fetch requests
useEffect(() => {
const controller = new AbortController();
fetch('/api/game', { signal: controller.signal })
.then(response => response.json())
.then(data => {
if (!controller.signal.aborted) {
setGameState(data);
}
});
return () => controller.abort();
}, []);
}
// Reactivity not working
const gameState = {
board: [],
currentPlayer: 'white'
};
Solution:
// Use reactive or ref
import { reactive, ref } from 'vue';
const gameState = reactive({
board: [],
currentPlayer: 'white'
});
// Or with ref
const gameState = ref({
board: [],
currentPlayer: 'white'
});
<!-- Error: Property 'gameState' does not exist -->
<template>
<div>{{ gameState.currentPlayer }}</div>
</template>
Solution:
<script setup>
import { ref } from 'vue';
// Define reactive state
const gameState = ref({
currentPlayer: 'white'
});
</script>
<template>
<div>{{ gameState.currentPlayer }}</div>
</template>
// Error: Move validation failed for valid move
const move = { from: 'e2', to: 'e4' };
const isValid = chessEngine.isValidMove(move); // Returns false incorrectly
Diagnosis:
// Debug the game state
console.log('Current board state:', chessEngine.getBoard());
console.log('Current player:', chessEngine.getCurrentPlayer());
console.log('Move being validated:', move);
// Check piece at source square
const piece = chessEngine.getPieceAt('e2');
console.log('Piece at e2:', piece);
// Check if move follows chess rules
const legalMoves = chessEngine.getLegalMoves('e2');
console.log('Legal moves from e2:', legalMoves);
Solution:
// Ensure correct game state initialization
const chessEngine = new ChessEngine();
chessEngine.initializeBoard(); // Make sure board is set up correctly
// Validate move format
function validateMoveFormat(move) {
if (!move || typeof move !== 'object') {
throw new Error('Move must be an object');
}
if (!move.from || !move.to) {
throw new Error('Move must have from and to properties');
}
if (!/^[a-h][1-8]$/.test(move.from) || !/^[a-h][1-8]$/.test(move.to)) {
throw new Error('Invalid square notation');
}
return true;
}
// Use validated move
try {
validateMoveFormat(move);
const isValid = chessEngine.isValidMove(move);
console.log('Move is valid:', isValid);
} catch (error) {
console.error('Move validation error:', error.message);
}
// Engine stops responding after complex position
Diagnosis:
// Add timeout to engine calls
function callEngineWithTimeout(fn, timeout = 5000) {
return Promise.race([
fn(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Engine timeout')), timeout)
)
]);
}
// Monitor engine performance
const startTime = performance.now();
try {
const result = await callEngineWithTimeout(() =>
chessEngine.getBestMove(position)
);
const duration = performance.now() - startTime;
console.log('Engine call completed in', duration, 'ms');
} catch (error) {
console.error('Engine error:', error);
}
Solution:
// Implement engine recovery
class RobustChessEngine {
constructor() {
this.engine = new ChessEngine();
this.backupEngine = new ChessEngine();
this.maxRetries = 3;
}
async callEngine(method, ...args) {
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
try {
const result = await this.engine[method](...args);
return result;
} catch (error) {
console.warn(`Engine attempt ${attempt} failed:`, error);
if (attempt === this.maxRetries) {
// Use backup engine
try {
return await this.backupEngine[method](...args);
} catch (backupError) {
throw new Error(`Both engines failed: ${error.message}, ${backupError.message}`);
}
}
// Reset engine for retry
this.engine = new ChessEngine();
this.engine.loadPosition(this.getCurrentPosition());
}
}
}
getCurrentPosition() {
// Return current game state for engine recovery
return this.engine.getFEN();
}
}
Access to fetch at 'http://localhost:8080/api/games' from origin 'http://localhost:3000' has been blocked by CORS policy
Solution:
// Backend - Express.js CORS setup
const cors = require('cors');
app.use(cors({
origin: ['http://localhost:3000', 'http://localhost:3001'],
credentials: true
}));
// Or development proxy in package.json
{
"name": "chess-frontend",
"proxy": "http://localhost:8080"
}
// Vite proxy configuration
// vite.config.js
export default {
server: {
proxy: {
'/api': 'http://localhost:8080'
}
}
}
// WebSocket connection failed
WebSocket connection to 'ws://localhost:8080/ws' failed: Error in connection establishment
Diagnosis:
// Test WebSocket connectivity
function testWebSocketConnection(url) {
return new Promise((resolve, reject) => {
const ws = new WebSocket(url);
ws.onopen = () => {
console.log('WebSocket connection successful');
ws.close();
resolve(true);
};
ws.onerror = (error) => {
console.error('WebSocket connection failed:', error);
reject(error);
};
ws.onclose = (event) => {
console.log('WebSocket closed:', event.code, event.reason);
};
// Timeout after 5 seconds
setTimeout(() => {
if (ws.readyState === WebSocket.CONNECTING) {
ws.close();
reject(new Error('Connection timeout'));
}
}, 5000);
});
}
// Test different protocols
const wsUrls = [
'ws://localhost:8080/ws',
'wss://localhost:8080/ws',
'ws://127.0.0.1:8080/ws'
];
for (const url of wsUrls) {
try {
await testWebSocketConnection(url);
console.log('Working URL:', url);
break;
} catch (error) {
console.log('Failed URL:', url, error.message);
}
}
Solution:
// Robust WebSocket implementation
class RobustWebSocket {
constructor(url, options = {}) {
this.url = url;
this.options = {
reconnectInterval: 1000,
maxReconnectAttempts: 5,
...options
};
this.reconnectAttempts = 0;
this.connect();
}
connect() {
try {
this.ws = new WebSocket(this.url);
this.setupEventHandlers();
} catch (error) {
console.error('WebSocket creation failed:', error);
this.handleReconnect();
}
}
setupEventHandlers() {
this.ws.onopen = () => {
console.log('WebSocket connected');
this.reconnectAttempts = 0;
this.onConnect?.();
};
this.ws.onmessage = (event) => {
this.onMessage?.(event);
};
this.ws.onclose = (event) => {
console.log('WebSocket closed:', event.code, event.reason);
// Attempt reconnection for unexpected closures
if (event.code !== 1000) {
this.handleReconnect();
}
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
this.onError?.(error);
};
}
handleReconnect() {
if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
console.error('Max reconnection attempts reached');
return;
}
this.reconnectAttempts++;
const delay = this.options.reconnectInterval * this.reconnectAttempts;
console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
setTimeout(() => {
this.connect();
}, delay);
}
send(data) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
} else {
console.warn('WebSocket not connected, message queued');
// Implement message queuing if needed
}
}
close() {
if (this.ws) {
this.ws.close(1000, 'Client closing');
}
}
}
// Memory usage keeps increasing during gameplay
Diagnosis:
// Monitor memory usage
function monitorMemory() {
if (performance.memory) {
console.log('Memory usage:', {
used: Math.round(performance.memory.usedJSHeapSize / 1024 / 1024) + ' MB',
total: Math.round(performance.memory.totalJSHeapSize / 1024 / 1024) + ' MB',
limit: Math.round(performance.memory.jsHeapSizeLimit / 1024 / 1024) + ' MB'
});
}
}
// Check every 10 seconds
setInterval(monitorMemory, 10000);
// Profile specific functions
function profileFunction(fn, name) {
return function(...args) {
const start = performance.now();
const memBefore = performance.memory?.usedJSHeapSize || 0;
const result = fn.apply(this, args);
const duration = performance.now() - start;
const memAfter = performance.memory?.usedJSHeapSize || 0;
const memDelta = memAfter - memBefore;
console.log(`${name}:`, {
duration: `${duration.toFixed(2)}ms`,
memoryDelta: `${(memDelta / 1024).toFixed(2)}KB`
});
return result;
};
}
Solution:
// Clean up event listeners
class ChessGame {
constructor() {
this.boundHandlers = {
handleKeyPress: this.handleKeyPress.bind(this),
handleMouseMove: this.handleMouseMove.bind(this)
};
}
addEventListeners() {
document.addEventListener('keypress', this.boundHandlers.handleKeyPress);
document.addEventListener('mousemove', this.boundHandlers.handleMouseMove);
}
removeEventListeners() {
document.removeEventListener('keypress', this.boundHandlers.handleKeyPress);
document.removeEventListener('mousemove', this.boundHandlers.handleMouseMove);
}
destroy() {
this.removeEventListeners();
// Clear any intervals or timeouts
if (this.gameTimer) {
clearInterval(this.gameTimer);
}
// Clear references
this.gameState = null;
this.players = null;
}
}
// Use WeakMap for object associations
const gameStates = new WeakMap();
const playerData = new WeakMap();
// Instead of storing references in objects
function associateGameState(game, state) {
gameStates.set(game, state);
}
function getGameState(game) {
return gameStates.get(game);
}
Error: connect ECONNREFUSED 127.0.0.1:5432
Diagnosis:
// Test database connectivity
async function testDatabaseConnection() {
try {
const db = new Database(process.env.DATABASE_URL);
await db.connect();
// Test basic query
const result = await db.query('SELECT 1 as test');
console.log('Database test successful:', result);
await db.close();
return true;
} catch (error) {
console.error('Database connection failed:', error);
return false;
}
}
// Check database status
async function checkDatabaseStatus() {
const checks = {
connection: false,
tables: false,
permissions: false
};
try {
const db = new Database(process.env.DATABASE_URL);
await db.connect();
checks.connection = true;
// Check required tables exist
const tables = await db.query(`
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
`);
const requiredTables = ['games', 'users', 'moves'];
const existingTables = tables.map(t => t.table_name);
checks.tables = requiredTables.every(table => existingTables.includes(table));
// Check permissions
await db.query('INSERT INTO games (id) VALUES ($1) ON CONFLICT DO NOTHING', ['test']);
await db.query('DELETE FROM games WHERE id = $1', ['test']);
checks.permissions = true;
await db.close();
} catch (error) {
console.error('Database check failed:', error);
}
return checks;
}
Solution:
// Database connection with retry logic
class DatabaseConnection {
constructor(connectionString) {
this.connectionString = connectionString;
this.pool = null;
this.maxRetries = 5;
this.retryDelay = 1000;
}
async connect() {
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
try {
this.pool = new Pool({
connectionString: this.connectionString,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000
});
// Test connection
const client = await this.pool.connect();
await client.query('SELECT NOW()');
client.release();
console.log('Database connected successfully');
return;
} catch (error) {
console.error(`Database connection attempt ${attempt} failed:`, error.message);
if (attempt === this.maxRetries) {
throw new Error(`Failed to connect after ${this.maxRetries} attempts`);
}
await this.delay(this.retryDelay * attempt);
}
}
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async query(text, params) {
if (!this.pool) {
throw new Error('Database not connected');
}
try {
const result = await this.pool.query(text, params);
return result;
} catch (error) {
console.error('Database query error:', error);
// Check if connection was lost
if (error.code === 'ECONNRESET' || error.code === 'ENOTFOUND') {
console.log('Connection lost, attempting to reconnect...');
await this.connect();
return await this.pool.query(text, params);
}
throw error;
}
}
}
// QuotaExceededError: Failed to execute 'setItem' on 'Storage'
Solution:
// Safe localStorage with quota management
class SafeStorage {
constructor(storageType = 'localStorage') {
this.storage = window[storageType];
this.maxSize = 5 * 1024 * 1024; // 5MB typical limit
}
setItem(key, value) {
try {
const serialized = JSON.stringify(value);
// Check if item would exceed quota
const currentSize = this.getStorageSize();
const itemSize = serialized.length * 2; // Rough estimate (UTF-16)
if (currentSize + itemSize > this.maxSize) {
this.cleanup();
}
this.storage.setItem(key, serialized);
} catch (error) {
if (error.name === 'QuotaExceededError') {
console.warn('Storage quota exceeded, cleaning up...');
this.cleanup();
// Try again after cleanup
try {
this.storage.setItem(key, JSON.stringify(value));
} catch (retryError) {
console.error('Storage still full after cleanup');
throw retryError;
}
} else {
throw error;
}
}
}
getItem(key) {
try {
const item = this.storage.getItem(key);
return item ? JSON.parse(item) : null;
} catch (error) {
console.error(`Failed to parse storage item ${key}:`, error);
this.storage.removeItem(key); // Remove corrupted item
return null;
}
}
cleanup() {
const items = [];
// Collect all items with timestamps
for (let i = 0; i < this.storage.length; i++) {
const key = this.storage.key(i);
const value = this.storage.getItem(key);
try {
const parsed = JSON.parse(value);
items.push({
key,
value,
timestamp: parsed.timestamp || 0,
size: value.length * 2
});
} catch (error) {
// Remove corrupted items
this.storage.removeItem(key);
}
}
// Sort by timestamp (oldest first)
items.sort((a, b) => a.timestamp - b.timestamp);
// Remove oldest items until under 80% capacity
const targetSize = this.maxSize * 0.8;
let currentSize = this.getStorageSize();
for (const item of items) {
if (currentSize <= targetSize) break;
this.storage.removeItem(item.key);
currentSize -= item.size;
console.log(`Removed storage item: ${item.key}`);
}
}
getStorageSize() {
let total = 0;
for (let key in this.storage) {
if (this.storage.hasOwnProperty(key)) {
total += this.storage[key].length * 2;
}
}
return total;
}
// Add timestamp to stored data
setItemWithTimestamp(key, value) {
const dataWithTimestamp = {
...value,
timestamp: Date.now()
};
this.setItem(key, dataWithTimestamp);
}
}
const safeStorage = new SafeStorage();
// Debug chess game state
function debugGameState() {
console.group('Chess Game Debug Info');
console.log('Board State:', window.chessGame?.getBoard());
console.log('Current Player:', window.chessGame?.getCurrentPlayer());
console.log('Game History:', window.chessGame?.getHistory());
console.log('Available Moves:', window.chessGame?.getLegalMoves());
// Check for JavaScript errors
console.log('Recent Errors:', window.errors || []);
// Performance metrics
if (performance.memory) {
console.log('Memory Usage:', {
used: `${Math.round(performance.memory.usedJSHeapSize / 1024 / 1024)}MB`,
total: `${Math.round(performance.memory.totalJSHeapSize / 1024 / 1024)}MB`
});
}
console.groupEnd();
}
// Make debug function globally available
window.debugChess = debugGameState;
// Debug WebSocket connections
function debugWebSocket() {
console.group('WebSocket Debug Info');
const ws = window.chessWebSocket;
if (ws) {
console.log('WebSocket State:', {
readyState: ws.readyState,
url: ws.url,
protocol: ws.protocol
});
console.log('Connection States:', {
CONNECTING: WebSocket.CONNECTING,
OPEN: WebSocket.OPEN,
CLOSING: WebSocket.CLOSING,
CLOSED: WebSocket.CLOSED
});
} else {
console.log('No WebSocket connection found');
}
console.groupEnd();
}
window.debugWebSocket = debugWebSocket;
// Production-safe debug logging
class DebugLogger {
constructor() {
this.enabled = process.env.NODE_ENV === 'development' ||
localStorage.getItem('debug-mode') === 'true';
this.logs = [];
this.maxLogs = 100;
}
log(level, message, data = {}) {
const logEntry = {
timestamp: new Date().toISOString(),
level,
message,
data,
url: window.location.href,
userAgent: navigator.userAgent
};
this.logs.push(logEntry);
// Keep only recent logs
if (this.logs.length > this.maxLogs) {
this.logs = this.logs.slice(-this.maxLogs);
}
if (this.enabled) {
console[level](message, data);
}
// Send critical errors to monitoring
if (level === 'error') {
this.sendToMonitoring(logEntry);
}
}
error(message, data) {
this.log('error', message, data);
}
warn(message, data) {
this.log('warn', message, data);
}
info(message, data) {
this.log('info', message, data);
}
debug(message, data) {
this.log('debug', message, data);
}
exportLogs() {
const logData = {
timestamp: new Date().toISOString(),
logs: this.logs,
userAgent: navigator.userAgent,
url: window.location.href
};
const blob = new Blob([JSON.stringify(logData, null, 2)], {
type: 'application/json'
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `chess-debug-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
}
async sendToMonitoring(logEntry) {
try {
await fetch('/api/debug/log', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(logEntry)
});
} catch (error) {
// Fail silently in production
}
}
enableDebugMode() {
localStorage.setItem('debug-mode', 'true');
this.enabled = true;
console.log('Debug mode enabled');
}
disableDebugMode() {
localStorage.removeItem('debug-mode');
this.enabled = false;
console.log('Debug mode disabled');
}
}
const debugLogger = new DebugLogger();
// Make available globally for debugging
window.debugLogger = debugLogger;
window.exportDebugLogs = () => debugLogger.exportLogs();
- GitHub Issues: Report bugs and feature requests
-
Stack Overflow: Tag questions with
chess-js
and framework tags - Discord/Slack: Join the community chat for real-time help
- Documentation: Check the wiki for detailed guides
# Bug Report Template
## Environment
- OS: [e.g., macOS 12.6]
- Browser: [e.g., Chrome 108.0.5359.124]
- Node.js Version: [e.g., 18.12.1]
- Framework: [e.g., React 18.2.0]
## Steps to Reproduce
1. Start a new game
2. Make move e2-e4
3. AI responds with e7-e5
4. Click on knight at b1
5. Error occurs
## Expected Behavior
Knight should highlight valid moves
## Actual Behavior
JavaScript error: "Cannot read property 'type' of null"
## Additional Context
- Error started after updating to version 2.1.0
- Only happens with AI games, not human vs human
- Console shows additional errors: [paste console output]
## Debug Information
[Paste output from `window.debugChess()` or attach exported logs]
// Reset application state
function emergencyReset() {
console.log('Performing emergency reset...');
// Clear all storage
localStorage.clear();
sessionStorage.clear();
// Clear service worker cache
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then(registrations => {
registrations.forEach(registration => registration.unregister());
});
}
// Clear application cache
if ('caches' in window) {
caches.keys().then(cacheNames => {
cacheNames.forEach(cacheName => caches.delete(cacheName));
});
}
// Reload page
window.location.reload(true);
}
// Make available globally
window.emergencyReset = emergencyReset;
// Safe mode activation
function activateSafeMode() {
localStorage.setItem('safe-mode', 'true');
sessionStorage.setItem('debug-mode', 'true');
// Disable non-essential features
window.disableAnimations = true;
window.disableWebSocket = true;
window.disableAI = true;
console.log('Safe mode activated - refresh page to apply');
}
window.activateSafeMode = activateSafeMode;
- Monitoring - Set up monitoring to prevent issues
- Performance - Optimize application performance
- Security - Security troubleshooting and incident response