Files
narrow_casting_system/admin/js/websocket.js
Alvin-Zilverstand 8e446a1339 🎿 Complete SnowWorld Narrowcasting System - MBO Challenge 18
 Full-stack narrowcasting platform implementation
 Real-time WebSocket communication for instant updates
 Zone-specific content distribution (reception, restaurant, skislope, lockers, shop)
 Professional admin dashboard with content management interface
 Beautiful client display with winter/snow theme matching SnowWorld branding
 Comprehensive technical documentation and test suite
 Docker deployment support with CI/CD pipeline
 All system tests passing successfully

🏗️ Technical Implementation:
- Backend: Node.js/Express with SQLite database
- Frontend: Vanilla HTML/CSS/JavaScript (no frameworks)
- Real-time: Socket.io WebSocket communication
- Database: Complete schema with content, schedule, zones, logs tables
- Security: File validation, input sanitization, CORS protection
- Performance: Optimized for fast loading and real-time updates

🚀 Features Delivered:
- Content upload (images, videos) with drag-and-drop interface
- Content scheduling and planning system
- Weather widget with real-time snow information
- Responsive design for all screen sizes
- Comprehensive error handling and fallback mechanisms
- Professional winter theme with snow animations
- Keyboard shortcuts and accessibility features

📁 Project Structure:
- /backend: Complete Node.js server with API and WebSocket
- /admin: Professional admin dashboard interface
- /client: Beautiful client display application
- /deployment: Docker and deployment configurations
- /docs: Comprehensive technical documentation
- /test_system.js: Complete test suite (all tests passing)

🧪 Testing Results:
- Server health:  Online and responsive
- API endpoints:  All endpoints functional
- Database operations:  All operations successful
- WebSocket communication:  Real-time updates working
- Zone distribution:  6 zones correctly loaded
- Weather integration:  Weather data available

Ready for production deployment at SnowWorld! 🎿❄️
2026-01-19 10:02:11 +01:00

240 lines
7.2 KiB
JavaScript

// WebSocket Management for SnowWorld Admin Dashboard
class WebSocketManager {
constructor() {
this.socket = null;
this.isConnected = false;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectDelay = 1000;
this.init();
}
init() {
this.connect();
}
connect() {
try {
this.socket = io('http://localhost:3000', {
transports: ['websocket', 'polling'],
timeout: 5000,
forceNew: true
});
this.setupEventListeners();
} catch (error) {
console.error('WebSocket connection error:', error);
this.handleConnectionError();
}
}
setupEventListeners() {
this.socket.on('connect', () => {
console.log('WebSocket connected');
this.isConnected = true;
this.reconnectAttempts = 0;
this.updateConnectionStatus(true);
// Join admin room for global updates
this.socket.emit('joinZone', 'admin');
this.showToast('Verbonden met server', 'success');
});
this.socket.on('disconnect', () => {
console.log('WebSocket disconnected');
this.isConnected = false;
this.updateConnectionStatus(false);
// Attempt reconnection
this.attemptReconnect();
});
this.socket.on('connect_error', (error) => {
console.error('WebSocket connection error:', error);
this.handleConnectionError();
});
// Content updates
this.socket.on('contentUpdated', (data) => {
console.log('Content update received:', data);
this.handleContentUpdate(data);
});
// Schedule updates
this.socket.on('scheduleUpdated', (data) => {
console.log('Schedule update received:', data);
this.handleScheduleUpdate(data);
});
// Zone-specific updates
this.socket.on('zoneUpdate', (data) => {
console.log('Zone update received:', data);
this.handleZoneUpdate(data);
});
// System notifications
this.socket.on('systemNotification', (data) => {
console.log('System notification:', data);
this.handleSystemNotification(data);
});
}
handleContentUpdate(data) {
// Clear content cache to force refresh
if (window.ui) {
window.ui.clearContentCache();
}
// Show notification based on update type
switch (data.type) {
case 'content_added':
this.showToast(`Nieuwe content toegevoegd: ${data.content.title}`, 'info');
break;
case 'content_deleted':
this.showToast('Content verwijderd', 'warning');
break;
case 'content_updated':
this.showToast('Content bijgewerkt', 'info');
break;
}
// Refresh current view if on content tab
if (window.ui && window.ui.currentTab === 'content') {
window.ui.loadContent();
}
}
handleScheduleUpdate(data) {
// Show notification
this.showToast(`Planning bijgewerkt voor zone: ${data.zone}`, 'info');
// Refresh schedule view if currently viewing this zone
const currentZone = document.getElementById('scheduleZoneSelect')?.value;
if (window.ui && window.ui.currentTab === 'schedule' && currentZone === data.zone) {
window.ui.loadSchedule();
}
}
handleZoneUpdate(data) {
// Handle zone-specific updates
this.showToast(`Zone ${data.zone} bijgewerkt`, 'info');
// Refresh relevant views
if (window.ui) {
if (window.ui.currentTab === 'zones') {
window.ui.loadZonesOverview();
} else if (window.ui.currentTab === 'content') {
window.ui.loadContent();
}
}
}
handleSystemNotification(data) {
// Handle system-level notifications
const { message, type, duration } = data;
this.showToast(message, type || 'info', duration);
}
updateConnectionStatus(connected) {
const statusDot = document.getElementById('connectionStatus');
const statusText = document.getElementById('connectionText');
if (statusDot) {
statusDot.className = connected ? 'status-dot' : 'status-dot disconnected';
}
if (statusText) {
statusText.textContent = connected ? 'Verbonden' : 'Verbinding verbroken';
}
}
attemptReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('Max reconnection attempts reached');
this.showToast('Kan geen verbinding maken met de server', 'error');
return;
}
this.reconnectAttempts++;
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
console.log(`Attempting reconnection ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms`);
setTimeout(() => {
if (!this.isConnected) {
this.connect();
}
}, delay);
}
handleConnectionError() {
this.isConnected = false;
this.updateConnectionStatus(false);
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.showToast('Verbinding verbroken. Probeert opnieuw...', 'warning');
} else {
this.showToast('Kan geen verbinding maken met de server', 'error');
}
}
// Public methods
joinZone(zone) {
if (this.isConnected && this.socket) {
this.socket.emit('joinZone', zone);
console.log(`Joined zone: ${zone}`);
}
}
leaveZone(zone) {
if (this.isConnected && this.socket) {
this.socket.emit('leaveZone', zone);
console.log(`Left zone: ${zone}`);
}
}
sendMessage(event, data) {
if (this.isConnected && this.socket) {
this.socket.emit(event, data);
} else {
console.warn('Cannot send message: not connected');
}
}
disconnect() {
if (this.socket) {
this.socket.disconnect();
this.isConnected = false;
this.updateConnectionStatus(false);
}
}
reconnect() {
this.disconnect();
this.reconnectAttempts = 0;
this.connect();
}
// Utility methods
showToast(message, type = 'info', duration = 5000) {
if (window.ui) {
window.ui.showToast(message, type);
} else {
// Fallback to browser notification
console.log(`Toast [${type}]: ${message}`);
}
}
// Get connection status
getConnectionStatus() {
return {
connected: this.isConnected,
reconnectAttempts: this.reconnectAttempts,
socketId: this.socket?.id || null
};
}
}
// Create global WebSocket instance
window.wsManager = new WebSocketManager();