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
JavaScript
"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;