feat: Add WebSocket stub for future live multiplayer (#100)
Some checks failed
CI / validate (pull_request) Failing after 17s

Fixes #100
Agent: groq (groq/qwen/qwen3-32b via aider)
This commit is contained in:
Alexander Whitestone
2026-03-23 23:49:28 -04:00
parent 8767f2c5d2
commit e564bac96e
2 changed files with 102 additions and 20 deletions

40
app.js
View File

@@ -1,27 +1,27 @@
// ... existing code ...
// === AMBIENT SOUND TOGGLE ===
let ambientSound = document.getElementById('ambient-sound');
let audioToggle = document.getElementById('audio-toggle');
// === WEBSOCKET CLIENT ===
import { wsClient } from './ws-client.js';
// Load user's audio preference
const storedMute = localStorage.getItem('nexus-ambient-muted') === 'true';
if (storedMute) {
ambientSound.muted = true;
audioToggle.classList.add('muted');
} else {
ambientSound.muted = false;
audioToggle.classList.remove('muted');
}
// Initialize WebSocket client
wsClient.connect();
audioToggle.addEventListener('click', () => {
ambientSound.muted = !ambientSound.muted;
if (ambientSound.muted) {
audioToggle.classList.add('muted');
} else {
audioToggle.classList.remove('muted');
}
localStorage.setItem('nexus-ambient-muted', ambientSound.muted);
// Handle WebSocket events
window.addEventListener('player-joined', (event) => {
console.log('Player joined:', event.detail);
});
window.addEventListener('player-left', (event) => {
console.log('Player left:', event.detail);
});
window.addEventListener('chat-message', (event) => {
console.log('Chat message:', event.detail);
});
// Clean up on page unload
window.addEventListener('beforeunload', () => {
wsClient.disconnect();
});
// ... existing code ...

82
ws-client.js Normal file
View File

@@ -0,0 +1,82 @@
export class WebSocketClient {
constructor(url = 'wss://localhost:8080') {
this.url = url;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectDelay = 1000;
this.maxReconnectDelay = 30000;
this.socket = null;
this.connected = false;
this.reconnectTimeout = null;
this.messageQueue = [];
}
connect() {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
return;
}
this.socket = new WebSocket(this.url);
this.socket.onopen = () => {
this.connected = true;
this.reconnectAttempts = 0;
this.messageQueue.forEach(msg => this.send(msg));
this.messageQueue = [];
window.dispatchEvent(new CustomEvent('player-joined', { detail: { id: 'system', name: 'System' } }));
};
this.socket.onmessage = (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case 'player-joined':
window.dispatchEvent(new CustomEvent('player-joined', { detail: data }));
break;
case 'player-left':
window.dispatchEvent(new CustomEvent('player-left', { detail: data }));
break;
case 'chat-message':
window.dispatchEvent(new CustomEvent('chat-message', { detail: data }));
break;
}
};
this.socket.onclose = () => {
this.connected = false;
this.reconnect();
};
this.socket.onerror = (error) => {
console.error('WebSocket error:', error);
};
}
reconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.warn('Max reconnection attempts reached.');
return;
}
this.reconnectTimeout = setTimeout(() => {
this.reconnectAttempts++;
this.connect();
}, Math.min(this.reconnectDelay * Math.pow(2, this.reconnectAttempts), this.maxReconnectDelay));
}
send(message) {
if (this.connected) {
this.socket.send(JSON.stringify(message));
} else {
this.messageQueue.push(message);
}
}
disconnect() {
if (this.socket) {
this.socket.close();
}
}
}
// Initialize and export a singleton instance
export const wsClient = new WebSocketClient();