UNPKG

@devgrid/netron

Version:
187 lines 7.2 kB
import path from 'node:path'; import cuid from '@bugsnag/cuid'; import { WebSocket, WebSocketServer } from 'ws'; import { AsyncEventEmitter } from '@devgrid/async-emitter'; import { LocalPeer } from './local-peer'; import { RemotePeer } from './remote-peer'; import { TaskManager } from './task-manager'; import { CONNECT_TIMEOUT, getPeerEventName, NETRON_EVENT_PEER_CONNECT, NETRON_EVENT_PEER_DISCONNECT } from './common'; export class Netron extends AsyncEventEmitter { constructor(options) { super(); this.options = options; this.ownEvents = new Map(); this.peers = new Map(); this.isStarted = false; this.services = new Map(); this.id = options?.id ?? cuid(); this.taskManager = new TaskManager({ timeout: options?.taskTimeout, overwriteStrategy: options?.taskOverwriteStrategy, }); this.peer = new LocalPeer(this); } async start() { if (this.isStarted) { throw new Error('Netron already started'); } await this.taskManager.loadTasksFromDir(path.join(__dirname, 'core-tasks')); if (!this.options?.listenHost || !this.options?.listenPort) { this.isStarted = true; return Promise.resolve(); } return new Promise((resolve, reject) => { this.wss = new WebSocketServer({ host: this.options?.listenHost, port: this.options?.listenPort, }); this.wss.on('listening', () => { this.isStarted = true; resolve(); }); this.wss.on('error', (err) => { reject(err); }); this.wss.on('connection', (ws, req) => { const peerId = new URL(req.url, 'ws://localhost:8080').searchParams.get('id'); if (!peerId) { ws.close(); return; } const peer = new RemotePeer(ws, this, peerId); this.peers.set(peer.id, peer); ws.send(JSON.stringify({ type: 'id', id: this.id })); this.emitSpecial(NETRON_EVENT_PEER_CONNECT, getPeerEventName(peer.id), { peerId }); ws.on('close', () => { this.peers.delete(peerId); this.emitSpecial(NETRON_EVENT_PEER_DISCONNECT, getPeerEventName(peerId), { peerId }); }); peer.init(false, this.options?.abilities); }); }); } async stop() { if (this.wss) { this.wss.close(); this.wss = undefined; } this.isStarted = false; } async connect(address, abilities) { return new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('Connection timeout')); }, this.options?.connectTimeout ?? CONNECT_TIMEOUT); const ws = new WebSocket(`${address}?id=${this.id}`); const peer = new RemotePeer(ws, this); let isResolved = false; ws.on('open', () => { clearTimeout(timeout); const uidHandler = (message, isBinary) => { if (isBinary) { return; } try { const data = JSON.parse(message.toString()); if (data.type === 'id') { peer.id = data.id; this.peers.set(peer.id, peer); if (!isResolved) { isResolved = true; peer.init(true, abilities).then(() => { this.emitSpecial(NETRON_EVENT_PEER_CONNECT, getPeerEventName(peer.id), { peerId: peer.id }); resolve(peer); }); } } else { ws.close(); } } catch (error) { console.error('Message error:', error); ws.close(); if (!isResolved) { isResolved = true; reject(new Error('Message error')); } } finally { ws.removeListener('message', uidHandler); } }; ws.on('message', uidHandler); }); ws.on('error', (err) => { console.error(`Connection error to ${address}:`, err); if (!isResolved) { isResolved = true; reject(err); } }); ws.on('close', () => { if (!isResolved) { console.warn('Connection closed prematurely.'); reject(new Error('Connection closed prematurely')); } this.peers.delete(peer.id); this.emitSpecial(NETRON_EVENT_PEER_DISCONNECT, getPeerEventName(peer.id), { peerId: peer.id }); }); }); } disconnect(peerId) { const peer = this.peers.get(peerId); if (peer) { peer.disconnect(); this.peers.delete(peerId); this.emitSpecial(NETRON_EVENT_PEER_DISCONNECT, getPeerEventName(peerId), { peerId }); } } getServiceNames() { return [...this.services.keys()]; } addTask(fn) { return this.taskManager.addTask(fn); } async runTask(peer, name, ...args) { return await this.taskManager.runTask(name, peer, ...args); } deleteSpecialEvents(id) { this.ownEvents.delete(id); } async emitSpecial(event, id, data) { const events = this.ownEvents.get(id) || []; events.push({ name: event, data }); this.ownEvents.set(id, events); if (events.length > 1) { return; } while (events.length > 0) { const eventData = events.shift(); if (eventData === void 0) { break; } try { const timeoutPromise = new Promise((_, reject) => { const timeoutId = setTimeout(() => { reject(new Error(`Emit timeout for event: ${eventData.name}`)); }, 5000); this.emitParallel(eventData.name, eventData.data) .finally(() => clearTimeout(timeoutId)) .catch(reject); }); await timeoutPromise; } catch (err) { console.error(`Event emit error: ${err.message}`); } } this.ownEvents.delete(id); } static async create(options) { const netron = new Netron(options); await netron.start(); return netron; } } //# sourceMappingURL=netron.js.map