@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