UNPKG

woolball-client

Version:

Client-side library for Woolball enabling secure browser resource sharing for distributed AI task processing

228 lines (227 loc) 8.38 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("../utils"); const web_worker_1 = __importDefault(require("web-worker")); const worker_string_1 = __importDefault(require("./transformers-js/worker-string")); class Woolball { constructor(id, url = 'ws://localhost:9003/ws') { this.wsConnection = null; this.eventListeners = new Map(); (0, utils_1.verifyBrowserCompatibility)(); this.clientId = id; this.wsUrl = url; this.eventListeners.set('started', new Set()); this.eventListeners.set('success', new Set()); this.eventListeners.set('error', new Set()); this.eventListeners.set('node_count', new Set()); this.workerTypes = new Map(); this.activeWorkers = new Set(); // Register available worker types this.workerTypes.set('automatic-speech-recognition', worker_string_1.default); this.workerTypes.set('text-to-speech', worker_string_1.default); this.workerTypes.set('translation', worker_string_1.default); this.workerTypes.set('text-generation', worker_string_1.default); // Add more worker types here as needed } start() { if (this.wsConnection) { console.warn('WebSocket connection already exists'); return; } this.connectWebSocket(this.wsUrl); } destroy() { // Close WebSocket connection if it exists if (this.wsConnection) { this.wsConnection.close(); this.wsConnection = null; } // Terminate all active workers this.activeWorkers.forEach(worker => { worker.terminate(); }); this.activeWorkers.clear(); // Clear all event listeners this.eventListeners.clear(); this.eventListeners.set('started', new Set()); this.eventListeners.set('success', new Set()); this.eventListeners.set('error', new Set()); this.eventListeners.set('node_count', new Set()); // Clear worker types this.workerTypes.clear(); } /** * Establishes WebSocket connection and sets up message handlers */ connectWebSocket(url) { this.wsConnection = new WebSocket(`${url}/${this.clientId}`); this.wsConnection.onopen = () => { console.log('WebSocket connection established'); }; this.wsConnection.onmessage = (event) => { if (event.data === 'ping') { return; } // Check if this is a node_count event if (event.data.startsWith('node_count:')) { const nodeCountStr = event.data.split(':')[1]; const nodeCount = parseInt(nodeCountStr, 10); if (!isNaN(nodeCount)) { this.emitEvent('node_count', { id: '', type: 'node_count', status: 'node_count', nodeCount: nodeCount }); } return; } this.handleWebSocketMessage(JSON.parse(event.data)); }; this.wsConnection.onerror = (error) => { console.error('WebSocket error:', error); }; this.wsConnection.onclose = (event) => { console.log(`WebSocket connection closed: ${event.code} ${event.reason}`); }; } /** * Handles incoming WebSocket messages */ async handleWebSocketMessage(message) { const { Id, Key, Value } = message; if (!Id || !Key || !Value) { console.error('Invalid message format:', message); return; } try { this.emitEvent('started', { id: Id, type: Key, status: 'started' }); const response = await this.processEvent(Key, Value); if (response.error) { const errorData = { id: Id, type: Key, status: 'error', }; // Show detailed errors in main console console.error(`Error processing ${Key}:`, response.error); this.emitEvent('error', errorData); this.sendWebSocketMessage({ type: 'ERROR', data: { requestId: Id, error: response.error, } }); return; } this.emitEvent('success', { id: Id, type: Key, status: 'success', }); this.sendWebSocketMessage({ type: 'PROCESS_RESULT', data: { requestId: Id, response } }); } catch (error) { console.error('Error handling WebSocket message:', error); this.sendWebSocketMessage({ type: 'ERROR', data: { requestId: Id, error: error instanceof Error ? error.message : 'Unknown error', } }); } } /** * Sends a message to the WebSocket server */ sendWebSocketMessage(message) { if (this.wsConnection && this.wsConnection.readyState === WebSocket.OPEN) { this.wsConnection.send(JSON.stringify(message)); return true; } return false; } createWorker(type) { if (typeof window === 'undefined') { throw new Error('Environment not supported for Web Workers'); } const workerCode = this.workerTypes.get(type); if (!workerCode) { throw new Error(`Worker type not found: ${type}`); } const blob = new Blob([workerCode], { type: 'application/javascript' }); const workerUrl = URL.createObjectURL(blob); const worker = new web_worker_1.default(workerUrl); // Add error listener to capture worker errors worker.addEventListener('error', (err) => { console.error('Worker error:', err); }); this.activeWorkers.add(worker); return worker; } terminateWorker(worker) { worker.terminate(); this.activeWorkers.delete(worker); } async processEvent(type, value) { const worker = this.createWorker(type); return new Promise((resolve, reject) => { const messageHandler = (e) => { worker.removeEventListener('message', messageHandler); worker.removeEventListener('error', errorHandler); this.terminateWorker(worker); // Log worker response for debugging if (e.data.error) { console.error(`Worker ${type} error:`, e.data.error); } resolve(e.data); }; const errorHandler = (error) => { console.error(`Worker ${type} execution error:`, error); worker.removeEventListener('message', messageHandler); worker.removeEventListener('error', errorHandler); this.terminateWorker(worker); reject(error); }; worker.addEventListener('message', messageHandler); worker.addEventListener('error', errorHandler); // Debug log console.log(`Sending data to worker ${type}:`, value); worker.postMessage(value); }); } on(status, listener) { const listeners = this.eventListeners.get(status); if (listeners) { listeners.add(listener); } } off(status, listener) { const listeners = this.eventListeners.get(status); if (listeners) { listeners.delete(listener); } } emitEvent(status, data) { const listeners = this.eventListeners.get(status); if (listeners) { listeners.forEach(listener => listener(data)); } } } exports.default = Woolball;