Files
michaelschiemer/public/js/sse-client.js
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

175 lines
4.9 KiB
JavaScript

/**
* SSE Client - Server-Sent Events Manager
* Zero-Dependency EventSource wrapper with auto-reconnect
*/
class SSEManager {
constructor() {
this.connections = new Map();
}
/**
* Connect to SSE endpoint
* @param {string} url - SSE endpoint URL
* @param {Object} handlers - Event handlers
* @param {Function} [handlers.onOpen] - Connection opened callback
* @param {Function} [handlers.onError] - Error callback
* @param {Function} [handlers.onMessage] - Default message handler
* @param {Object} [handlers.events] - Named event handlers
* @returns {EventSource}
*/
connect(url, handlers = {}) {
// Close existing connection
this.disconnect(url);
const eventSource = new EventSource(url);
// Default handlers
eventSource.onopen = () => {
console.log('SSE connected:', url);
handlers.onOpen?.();
};
eventSource.onerror = (error) => {
console.error('SSE error:', error);
handlers.onError?.(error);
// Auto-reconnect after 5 seconds
setTimeout(() => {
console.log('SSE reconnecting:', url);
this.connect(url, handlers);
}, 5000);
};
// Custom event handlers
if (handlers.events) {
Object.entries(handlers.events).forEach(([event, handler]) => {
eventSource.addEventListener(event, (e) => {
try {
const data = JSON.parse(e.data);
handler(data, e);
} catch {
handler(e.data, e);
}
});
});
}
// Default message handler
eventSource.onmessage = (e) => {
try {
const data = JSON.parse(e.data);
handlers.onMessage?.(data, e);
} catch {
handlers.onMessage?.(e.data, e);
}
};
this.connections.set(url, eventSource);
return eventSource;
}
/**
* Disconnect from SSE endpoint
* @param {string} url - SSE endpoint URL
*/
disconnect(url) {
const connection = this.connections.get(url);
if (connection) {
connection.close();
this.connections.delete(url);
console.log('SSE disconnected:', url);
}
}
/**
* Disconnect all SSE connections
*/
disconnectAll() {
this.connections.forEach((conn, url) => {
conn.close();
console.log('SSE disconnected:', url);
});
this.connections.clear();
}
/**
* Check if connected to URL
* @param {string} url
* @returns {boolean}
*/
isConnected(url) {
return this.connections.has(url);
}
/**
* Get connection by URL
* @param {string} url
* @returns {EventSource|undefined}
*/
getConnection(url) {
return this.connections.get(url);
}
}
// Global instance
window.sseManager = new SSEManager();
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
window.sseManager.disconnectAll();
});
/**
* Usage Examples:
*
* // 1. Notification Stream
* window.sseManager.connect('/notifications/stream', {
* events: {
* 'notification': (data) => {
* showNotification(data.message, data.type);
* },
* 'heartbeat': (data) => {
* console.log('Server alive:', data.status);
* }
* },
* onError: () => {
* console.log('Reconnecting to notifications...');
* }
* });
*
* // 2. Live Component Updates via SSE
* window.sseManager.connect('/live-component/UserCardComponent:123/stream', {
* events: {
* 'component-update': (data) => {
* const component = window.liveComponents.components.get('UserCardComponent:123');
* if (component) {
* component.element.innerHTML = data.html;
* component.state = JSON.parse(data.state);
* window.liveComponents.setupListeners(component.element, 'UserCardComponent:123');
* }
* }
* }
* });
*
* // 3. Job Progress Tracking
* function trackJobProgress(jobId) {
* const progressBar = document.getElementById('progress-bar');
* const progressText = document.getElementById('progress-text');
*
* window.sseManager.connect(`/jobs/${jobId}/progress`, {
* events: {
* 'progress': (data) => {
* progressBar.style.width = `${data.percentage}%`;
* progressText.textContent = data.message;
*
* if (data.status === 'completed') {
* window.sseManager.disconnect(`/jobs/${jobId}/progress`);
* alert('Job completed!');
* }
* }
* }
* });
* }
*/