// modules/api-manager/WorkerManager.js import { Logger } from '../../core/logger.js'; /** * Worker APIs Manager - Web Workers, Service Workers, Shared Workers */ export class WorkerManager { constructor(config = {}) { this.config = config; this.activeWorkers = new Map(); this.messageHandlers = new Map(); this.workerScripts = new Map(); // Check API support this.support = { webWorkers: 'Worker' in window, serviceWorker: 'serviceWorker' in navigator, sharedWorker: 'SharedWorker' in window, offscreenCanvas: 'OffscreenCanvas' in window }; Logger.info('[WorkerManager] Initialized with support:', this.support); } /** * Web Workers for background processing */ web = { // Create a new Web Worker create: (script, options = {}) => { if (!this.support.webWorkers) { throw new Error('Web Workers not supported'); } let worker; const workerId = this.generateId('worker'); try { // Handle different script types if (typeof script === 'string') { // URL string worker = new Worker(script, options); } else if (typeof script === 'function') { // Function to blob const blob = this.createWorkerBlob(script); worker = new Worker(URL.createObjectURL(blob), options); } else if (script instanceof Blob) { // Blob directly worker = new Worker(URL.createObjectURL(script), options); } else { throw new Error('Invalid script type'); } // Enhanced worker wrapper const workerWrapper = { id: workerId, worker, // Send message to worker send: (message, transfer = null) => { if (transfer) { worker.postMessage(message, transfer); } else { worker.postMessage(message); } Logger.info(`[WorkerManager] Message sent to worker: ${workerId}`); }, // Listen for messages onMessage: (callback) => { worker.addEventListener('message', (event) => { callback(event.data, event); }); }, // Handle errors onError: (callback) => { worker.addEventListener('error', callback); worker.addEventListener('messageerror', callback); }, // Terminate worker terminate: () => { worker.terminate(); this.activeWorkers.delete(workerId); Logger.info(`[WorkerManager] Worker terminated: ${workerId}`); }, // Execute function in worker execute: (fn, data = null) => { return new Promise((resolve, reject) => { const messageId = this.generateId('msg'); const handler = (event) => { if (event.data.id === messageId) { worker.removeEventListener('message', handler); if (event.data.error) { reject(new Error(event.data.error)); } else { resolve(event.data.result); } } }; worker.addEventListener('message', handler); worker.postMessage({ id: messageId, type: 'execute', function: fn.toString(), data }); // Timeout after 30 seconds setTimeout(() => { worker.removeEventListener('message', handler); reject(new Error('Worker execution timeout')); }, 30000); }); } }; this.activeWorkers.set(workerId, workerWrapper); Logger.info(`[WorkerManager] Web Worker created: ${workerId}`); return workerWrapper; } catch (error) { Logger.error('[WorkerManager] Worker creation failed:', error); throw error; } }, // Create worker pool for parallel processing createPool: (script, poolSize = navigator.hardwareConcurrency || 4, options = {}) => { const workers = []; for (let i = 0; i < poolSize; i++) { workers.push(this.web.create(script, options)); } let currentWorker = 0; const pool = { workers, // Execute task on next available worker execute: async (fn, data = null) => { const worker = workers[currentWorker]; currentWorker = (currentWorker + 1) % workers.length; return worker.execute(fn, data); }, // Broadcast message to all workers broadcast: (message) => { workers.forEach(worker => { worker.send(message); }); }, // Terminate all workers terminate: () => { workers.forEach(worker => { worker.terminate(); }); workers.length = 0; Logger.info('[WorkerManager] Worker pool terminated'); } }; Logger.info(`[WorkerManager] Worker pool created with ${poolSize} workers`); return pool; }, // Common worker tasks tasks: { // Heavy computation compute: (fn, data) => { const workerCode = ` self.addEventListener('message', function(e) { const { id, function: fnString, data } = e.data; try { const fn = new Function('return ' + fnString)(); const result = fn(data); self.postMessage({ id, result }); } catch (error) { self.postMessage({ id, error: error.message }); } }); `; const worker = this.web.create(workerCode); return worker.execute(fn, data); }, // Image processing processImage: (imageData, filters) => { const workerCode = ` self.addEventListener('message', function(e) { const { id, data: { imageData, filters } } = e.data; try { const pixels = imageData.data; for (let i = 0; i < pixels.length; i += 4) { // Apply filters if (filters.brightness) { pixels[i] *= filters.brightness; // R pixels[i + 1] *= filters.brightness; // G pixels[i + 2] *= filters.brightness; // B } if (filters.contrast) { const factor = (259 * (filters.contrast * 255 + 255)) / (255 * (259 - filters.contrast * 255)); pixels[i] = factor * (pixels[i] - 128) + 128; pixels[i + 1] = factor * (pixels[i + 1] - 128) + 128; pixels[i + 2] = factor * (pixels[i + 2] - 128) + 128; } if (filters.grayscale) { const gray = 0.299 * pixels[i] + 0.587 * pixels[i + 1] + 0.114 * pixels[i + 2]; pixels[i] = gray; pixels[i + 1] = gray; pixels[i + 2] = gray; } } self.postMessage({ id, result: imageData }, [imageData.data.buffer]); } catch (error) { self.postMessage({ id, error: error.message }); } }); `; const worker = this.web.create(workerCode); return worker.execute(null, { imageData, filters }); }, // Data processing processData: (data, processor) => { const workerCode = ` self.addEventListener('message', function(e) { const { id, data, function: processorString } = e.data; try { const processor = new Function('return ' + processorString)(); const result = data.map(processor); self.postMessage({ id, result }); } catch (error) { self.postMessage({ id, error: error.message }); } }); `; const worker = this.web.create(workerCode); return worker.execute(processor, data); } } }; /** * Service Workers for caching and background sync */ service = { // Register service worker register: async (scriptURL, options = {}) => { if (!this.support.serviceWorker) { throw new Error('Service Worker not supported'); } try { const registration = await navigator.serviceWorker.register(scriptURL, options); Logger.info('[WorkerManager] Service Worker registered:', scriptURL); // Enhanced registration wrapper return { registration, scope: registration.scope, // Update service worker update: () => registration.update(), // Unregister service worker unregister: () => registration.unregister(), // Send message to service worker postMessage: (message) => { if (registration.active) { registration.active.postMessage(message); } }, // Listen for updates onUpdate: (callback) => { registration.addEventListener('updatefound', () => { const newWorker = registration.installing; newWorker.addEventListener('statechange', () => { if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { callback(newWorker); } }); }); }, // Check for updates checkForUpdates: () => { return registration.update(); } }; } catch (error) { Logger.error('[WorkerManager] Service Worker registration failed:', error); throw error; } }, // Get registration getRegistration: async (scope = '/') => { if (!this.support.serviceWorker) { return null; } try { return await navigator.serviceWorker.getRegistration(scope); } catch (error) { Logger.error('[WorkerManager] Get registration failed:', error); return null; } }, // Get all registrations getRegistrations: async () => { if (!this.support.serviceWorker) { return []; } try { return await navigator.serviceWorker.getRegistrations(); } catch (error) { Logger.error('[WorkerManager] Get registrations failed:', error); return []; } } }; /** * Shared Workers for cross-tab communication */ shared = { // Create shared worker create: (script, options = {}) => { if (!this.support.sharedWorker) { throw new Error('Shared Worker not supported'); } try { const sharedWorker = new SharedWorker(script, options); const port = sharedWorker.port; const workerId = this.generateId('shared'); // Start the port port.start(); const workerWrapper = { id: workerId, worker: sharedWorker, port, // Send message send: (message, transfer = null) => { if (transfer) { port.postMessage(message, transfer); } else { port.postMessage(message); } }, // Listen for messages onMessage: (callback) => { port.addEventListener('message', (event) => { callback(event.data, event); }); }, // Handle errors onError: (callback) => { sharedWorker.addEventListener('error', callback); port.addEventListener('messageerror', callback); }, // Close connection close: () => { port.close(); this.activeWorkers.delete(workerId); Logger.info(`[WorkerManager] Shared Worker closed: ${workerId}`); } }; this.activeWorkers.set(workerId, workerWrapper); Logger.info(`[WorkerManager] Shared Worker created: ${workerId}`); return workerWrapper; } catch (error) { Logger.error('[WorkerManager] Shared Worker creation failed:', error); throw error; } } }; /** * Offscreen Canvas for worker-based rendering */ offscreen = { // Create offscreen canvas worker create: (canvas, workerScript = null) => { if (!this.support.offscreenCanvas) { throw new Error('Offscreen Canvas not supported'); } try { const offscreenCanvas = canvas.transferControlToOffscreen(); const defaultWorkerScript = ` self.addEventListener('message', function(e) { const { canvas, type, data } = e.data; if (type === 'init') { self.canvas = canvas; self.ctx = canvas.getContext('2d'); } if (type === 'draw' && self.ctx) { // Basic drawing operations const { operations } = data; operations.forEach(op => { switch (op.type) { case 'fillRect': self.ctx.fillRect(...op.args); break; case 'strokeRect': self.ctx.strokeRect(...op.args); break; case 'fillText': self.ctx.fillText(...op.args); break; case 'setFillStyle': self.ctx.fillStyle = op.value; break; case 'setStrokeStyle': self.ctx.strokeStyle = op.value; break; } }); } }); `; const script = workerScript || defaultWorkerScript; const worker = this.web.create(script); // Initialize worker with canvas worker.send({ type: 'init', canvas: offscreenCanvas }, [offscreenCanvas]); return { worker, // Draw operations draw: (operations) => { worker.send({ type: 'draw', data: { operations } }); }, // Send custom message send: (message) => worker.send(message), // Listen for messages onMessage: (callback) => worker.onMessage(callback), // Terminate worker terminate: () => worker.terminate() }; } catch (error) { Logger.error('[WorkerManager] Offscreen Canvas creation failed:', error); throw error; } } }; // Helper methods createWorkerBlob(fn) { const code = ` (function() { const workerFunction = ${fn.toString()}; if (typeof workerFunction === 'function') { // If function expects to be called immediately if (workerFunction.length === 0) { workerFunction(); } } // Standard worker message handling self.addEventListener('message', function(e) { if (e.data.type === 'execute' && e.data.function) { try { const fn = new Function('return ' + e.data.function)(); const result = fn(e.data.data); self.postMessage({ id: e.data.id, result }); } catch (error) { self.postMessage({ id: e.data.id, error: error.message }); } } }); })(); `; return new Blob([code], { type: 'application/javascript' }); } generateId(prefix = 'worker') { return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Terminate all active workers */ terminateAll() { this.activeWorkers.forEach(worker => { if (worker.terminate) { worker.terminate(); } else if (worker.close) { worker.close(); } }); this.activeWorkers.clear(); Logger.info('[WorkerManager] All workers terminated'); } /** * Get worker statistics */ getStats() { const workers = Array.from(this.activeWorkers.values()); return { total: workers.length, byType: workers.reduce((acc, worker) => { const type = worker.id.split('_')[0]; acc[type] = (acc[type] || 0) + 1; return acc; }, {}), support: this.support, hardwareConcurrency: navigator.hardwareConcurrency || 'unknown' }; } /** * Create common worker utilities */ utils = { // Benchmark function performance benchmark: async (fn, data, iterations = 1000) => { const worker = this.web.create(() => { self.addEventListener('message', (e) => { const { id, function: fnString, data, iterations } = e.data; try { const fn = new Function('return ' + fnString)(); const start = performance.now(); for (let i = 0; i < iterations; i++) { fn(data); } const end = performance.now(); const totalTime = end - start; const avgTime = totalTime / iterations; self.postMessage({ id, result: { totalTime, avgTime, iterations, opsPerSecond: 1000 / avgTime } }); } catch (error) { self.postMessage({ id, error: error.message }); } }); }); const result = await worker.execute(fn, data, iterations); worker.terminate(); return result; }, // Parallel array processing parallelMap: async (array, fn, chunkSize = null) => { const chunks = chunkSize || Math.ceil(array.length / (navigator.hardwareConcurrency || 4)); const pool = this.web.createPool(() => { self.addEventListener('message', (e) => { const { id, data: { chunk, function: fnString } } = e.data; try { const fn = new Function('return ' + fnString)(); const result = chunk.map(fn); self.postMessage({ id, result }); } catch (error) { self.postMessage({ id, error: error.message }); } }); }); const promises = []; for (let i = 0; i < array.length; i += chunks) { const chunk = array.slice(i, i + chunks); promises.push(pool.execute(fn, chunk)); } const results = await Promise.all(promises); pool.terminate(); return results.flat(); } }; }