UNPKG

@iprokit/service

Version:

Powering distributed systems with simplicity and speed.

245 lines 7.74 kB
"use strict"; /** * @iProKit/Service * Copyright (c) 2019-2025 Rutvik Katuri / iProTechs * SPDX-License-Identifier: Apache-2.0 */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); // Import Libs. const events_1 = require("events"); const dgram_1 = require("dgram"); // Import Local. const pod_1 = __importDefault(require("./pod")); // Symbol Definitions. const socket = Symbol('Socket'); /** * `Server` binds to a multicast address and port number, listening for incoming SDP client connections. * Tracks pods' availability and emits events when their states change. * * @emits `listening` when the server is bound after calling `listen()`. * @emits `available` when a pod becomes available. * @emits `unavailable` when a pod becomes unavailable. * @emits `error` when an error occurs. * @emits `close` when the server is fully closed. */ class Server extends events_1.EventEmitter { /** * Pods discovered. */ pods; /** * Underlying UDP Socket. */ [socket]; /** * Representation of the server as a Pod. */ #pod; /** * Multicast group that have been joined. */ #membership; /** * Local port of the server. */ #localPort; /** * Local address of the server. */ #localAddress; /** * Creates an instance of SDP `Server`. * * @param identifier unique identifier of the server. */ constructor(identifier) { super(); // Initialize variables. this.pods = new Map(); this[socket] = (0, dgram_1.createSocket)({ type: 'udp4', reuseAddr: true }); this.#pod = new pod_1.default(identifier, Server.UNAVAILABLE_TOKEN); this.#membership = null; this.#localPort = null; this.#localAddress = null; // Bind listeners. this.onMessage = this.onMessage.bind(this); // Add listeners. this[socket].addListener('message', this.onMessage); this[socket].addListener('error', (error) => this.emit('error', error)); } ////////////////////////////// //////// Gets/Sets ////////////////////////////// /** * Unique identifier of the server. */ get identifier() { return this.#pod.identifier; } /** * Attributes of the server. */ get attributes() { return this.#pod.attributes; } /** * Multicast group that have been joined. */ get membership() { return this.#membership; } /** * Local port of the server. */ get localPort() { return this.#localPort; } /** * Local address of the server. */ get localAddress() { return this.#localAddress; } /** * `true` when the server is listening for connections, `false` otherwise. */ get listening() { return this.#pod.session !== Server.UNAVAILABLE_TOKEN; } /** * Retrieves the bound address, family, and port of the server as reported by operating system. */ address() { return this.listening ? this[socket].address() : null; } ////////////////////////////// //////// Event Listeners ////////////////////////////// /** * @emits `available` when a pod is available. * @emits `unavailable` when a pod is unavailable. */ onMessage(buffer, remoteInfo) { const { identifier, session, attributes } = pod_1.default.objectify(buffer.toString()); const { address: host } = remoteInfo; if (identifier === this.identifier) { this[socket].emit('echo', host); return; } // Be ready to be confused. 😈 const foundPod = this.pods.get(identifier); if (foundPod && session === foundPod.session) return; if (session !== Server.UNAVAILABLE_TOKEN) { this.pods.set(identifier, { session, attributes, host }); this.send(() => this.emit('available', identifier, attributes, host)); } else { this.pods.set(identifier, { session: Server.UNAVAILABLE_TOKEN, attributes: null, host: null }); this.emit('unavailable', identifier); } } ////////////////////////////// //////// Send/Echo ////////////////////////////// /** * Encodes and multicasts `this.#pod` on the network. * * @param callback called once the pod is multicast. */ send(callback) { this[socket].send(this.#pod.stringify(), this.#localPort, this.#membership, (error) => callback && callback()); } /** * Encodes and multicasts `this.#pod` on the network, then waits for an echo. * * @param callback called once the echo is received. */ echo(callback) { // Read this[socket].once('echo', (address) => callback(address)); // Write this.send(); } ////////////////////////////// //////// Connection Management ////////////////////////////// /** * Starts listening for pods on the network and emits `listening` event. * * @param port local port. * @param address address of the multicast group. * @param callback optional callback added as a one-time listener for the `listening` event. */ listen(port, address, callback) { callback && this.once('listening', callback); this.#localPort = port; this.#membership = address; this[socket].bind(this.#localPort, () => { this[socket].addMembership(this.#membership); this.#pod.session = Server.createToken(); this.echo((address) => { this.#localAddress = address; this.emit('listening'); }); }); return this; } /** * Closes the underlying UDP socket and stops listening for pods, emitting the `close` event. * * @param callback optional callback added as a one-time listener for the `close` event. */ close(callback) { callback && this.once('close', callback); this.#pod.session = Server.UNAVAILABLE_TOKEN; this.echo((address) => { this[socket].dropMembership(this.#membership); this[socket].close(() => { this.#membership = null; this.#localPort = null; this.#localAddress = null; this.emit('close'); }); }); return this; } ////////////////////////////// //////// Ref/Unref ////////////////////////////// /** * References the socket, preventing it from closing automatically. * Calling `ref` again has no effect if already referenced. */ ref() { this[socket].ref(); return this; } /** * Unreferences the socket, allowing it to close automatically when no other event loop activity is present. * Calling `unref` again has no effect if already unreferenced. */ unref() { this[socket].unref(); return this; } ////////////////////////////// //////// Session Helpers ////////////////////////////// /** * Returns a new session token for an available pod. */ static createToken() { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; return Array.from({ length: 5 }, () => chars[Math.floor(Math.random() * chars.length)]).join(''); } /** * Session token representing an unavailable pod. */ static UNAVAILABLE_TOKEN = '00000'; } exports.default = Server; //# sourceMappingURL=server.js.map