slavery-js
Version:
A simple clustering app that allows you to scale an application on multiple thread, containers or machines
187 lines (170 loc) • 6.93 kB
text/typescript
import { Server } from "socket.io";
import http from "http";
import { createServer } from "http";
import { Socket } from "socket.io";
import { log, Pool } from '../utils/index.js';
import type Listener from './types/Listener.js';
import Connection from "./Connection.js";
class NetworkServer {
/* this class will handle the logic managing the server conenctions with clilent,
* it will keep track of the node id and it will handle connection and dicoections */
private io: Server;
private host: string;
private port: number;
private maxTransferSize: number;
private clients: Pool<Connection>;
public name: string;
public isLan: boolean;
public connectionCallback: any;
public disconnectCallback: any;
public listeners: Listener[];
public httpServer?: http.Server;
public isReady: boolean;
private ioOptions: any;
constructor({ name, host, port, listeners } : {
name: string, host: string, port: number, listeners: Listener[]
}, options?: { maxTransferSize: number }) {
this.host = host || "localhost";
this.isLan = this.host !== 'localhost'
this.port = port || 0; // zero means random port
this.isReady = false;
this.maxTransferSize = options?.maxTransferSize || 1e9; // default 1GB
this.name = name? name : "server";
this.connectionCallback = null;
this.disconnectCallback = null;
this.clients = new Pool<Connection>();
this.listeners = listeners || [];
this.ioOptions = {
maxHttpBufferSize: this.maxTransferSize,
};
// initiate with the server
if(this.isLan){ // if we are in a over lan
// create a http server
this.httpServer = createServer();
this.io = new Server(this.httpServer, this.ioOptions);
this.httpServer.listen(this.port, this.host, () => {
let address = this.httpServer?.address();
if(!address || typeof address === "string") {
console.error("Server is not running");
return;
}
this.isReady = true;
this.port = address.port
});
}else{ // if we are in localhost
this.io = new Server(this.port, this.ioOptions);
// get the port number
this.port = (this.io as any).httpServer.address().port;
}
// create a new socket.io client instance
this.io.on("connection", this.handleConnection.bind(this));
this.io.on("reconnect", () => log("[Server] on reconnect triggered"));
this.io.on("disconnect", this.handleDisconnection.bind(this));
// set the listener on the server socket
this.setListeners(this.listeners);
}
private async handleConnection(socket: Socket) {
log("[Server] got new connection");
// make a new connectection instance
let connection = new Connection({
socket, name: this.name, listeners: this.listeners
});
// await fo connection to be established
await connection.connected();
// get the id of the connection
let id = connection.getTargetId();
log("[Server] connection id: ", id);
// check if id is null
if(id == null) throw new Error("Connection id is null");
// check if connection already exists
if(this.clients.has(id)) {
let client = this.clients.remove(id);
client && client.close();
}
// give server listeners to the connection
// await connection.setListeners(this.listeners);
// add connection to pool
this.clients.add(id, connection);
// run callback
if(this.connectionCallback)
this.connectionCallback(connection);
}
private handleDisconnection(socket: Socket) {
console.log('[Server] got disconnection from node');
let socketId = socket.id
console.log('[Server] socket id: ', socketId);
// filter every client based on the socket id
let conn = this.clients.toArray()
.filter((client: Connection) => client.socketId === socketId)[0];
// cast conn as type Connection
if(conn) {
// close the connection
conn.close();
let id = conn.getTargetId();
if(id === undefined)
throw new Error("Connection id is undefined");
this.clients.remove(conn.getTargetId() as string);
// run the disconnect callback
if(this.disconnectCallback)
this.disconnectCallback(conn);
}
}
public setListeners(listeners: Listener[]) {
// set the listeners on the server socket
listeners.forEach((listener: Listener) => {
// run the listener callback and emit the result to the client
let callback = async ( ...args: any[] ) => {
// run the listener callback
let result = await listener.callback(...args);
// emit the result to the client
this.io.emit(listener.event, result);
}
// remove any previous listeners
this.io.removeAllListeners(listener.event);
// set the new listener
this.io.on(listener.event, callback);
});
// store the listeners
this.listeners = listeners;
// broadcast the new listeners to all clients
this.io.emit('_set_listeners', this.listeners);
}
public broadcast(event: string, data: any) {
// broadcast an event to all clients
this.io.emit(event, data);
}
public addListeners(listeners: Listener[]) {
// add a new listener to the server
// if we have the same event name, we will overwrite it
const eventMap = new Map(this.listeners.map(l => [l.event, l]));
listeners.forEach(l => eventMap.set(l.event, l));
listeners = Array.from(eventMap.values());
// set the listener on the server socket
this.setListeners(this.listeners);
}
public getClient(id: string) : Connection | null {
return this.clients.get(id);
}
public getClients() : Connection[] {
return this.clients.toArray();
}
public onConnection(callback: any) {
this.connectionCallback = callback;
}
public onDisconnect(callback: any) {
this.disconnectCallback = callback;
}
public getListeners() {
// the the listneres from the first client
let client = this.clients.toArray()[0];
// if client is null return an empty array
if(!client) return [];
// return the listeners
return client.getListeners();
}
async close() {
// close all sockets
this.io.close();
}
}
export default NetworkServer;