@devgrid/netron
Version: 
Event bus, streams and remote object invocation.
194 lines • 7.66 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Netron = void 0;
const node_path_1 = __importDefault(require("node:path"));
const cuid_1 = __importDefault(require("@bugsnag/cuid"));
const ws_1 = require("ws");
const async_emitter_1 = require("@devgrid/async-emitter");
const local_peer_1 = require("./local-peer");
const remote_peer_1 = require("./remote-peer");
const task_manager_1 = require("./task-manager");
const common_1 = require("./common");
class Netron extends async_emitter_1.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 ?? (0, cuid_1.default)();
        this.taskManager = new task_manager_1.TaskManager({
            timeout: options?.taskTimeout,
            overwriteStrategy: options?.taskOverwriteStrategy,
        });
        this.peer = new local_peer_1.LocalPeer(this);
    }
    async start() {
        if (this.isStarted) {
            throw new Error('Netron already started');
        }
        await this.taskManager.loadTasksFromDir(node_path_1.default.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 ws_1.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 remote_peer_1.RemotePeer(ws, this, peerId);
                this.peers.set(peer.id, peer);
                ws.send(JSON.stringify({ type: 'id', id: this.id }));
                this.emitSpecial(common_1.NETRON_EVENT_PEER_CONNECT, (0, common_1.getPeerEventName)(peer.id), { peerId });
                ws.on('close', () => {
                    this.peers.delete(peerId);
                    this.emitSpecial(common_1.NETRON_EVENT_PEER_DISCONNECT, (0, common_1.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 ?? common_1.CONNECT_TIMEOUT);
            const ws = new ws_1.WebSocket(`${address}?id=${this.id}`);
            const peer = new remote_peer_1.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(common_1.NETRON_EVENT_PEER_CONNECT, (0, common_1.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(common_1.NETRON_EVENT_PEER_DISCONNECT, (0, common_1.getPeerEventName)(peer.id), { peerId: peer.id });
            });
        });
    }
    disconnect(peerId) {
        const peer = this.peers.get(peerId);
        if (peer) {
            peer.disconnect();
            this.peers.delete(peerId);
            this.emitSpecial(common_1.NETRON_EVENT_PEER_DISCONNECT, (0, common_1.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;
    }
}
exports.Netron = Netron;
//# sourceMappingURL=netron.js.map