slavery-js
Version:
A simple clustering app that allows you to scale an application on multiple thread, containers or machines
247 lines (215 loc) • 9.29 kB
text/typescript
import Connection from './Connection.js';
import { uuid, log, Pool } from '../utils/index.js';
import type Listener from './types/Listener.js';
import Server from './Server.js';
type NetworkOptions = {
timeout?: number
}
type NetworkParameters = {
name?: string,
id?: string | undefined,
options?: NetworkOptions
}
class Network {
/* *
* this class will handle the connections of a node in the network.
* this node can be in either a server or a client.
* in a server it will have a Server instance and it will handle the nodes connections
* in a client it will have a pool of connections to other servers
* Each Node will have a NetworkNode
* */
public id: string;
// this is where a node store its server,
// which in turn stores its connections to clients
public server: Server | null;
public name: string;
private listeners: Listener[];
// this is where we store our connections to servers
public connections: Pool<Connection>;
// callback for when a new service connection is made
public serviceConnectionCallback?: Function;
// callback for when a service disconnects
public serviceDisconnectCallback?: Function;
// callback for when a new listener is added
public newListenersCallback?: Function;
public timeout: number;
constructor({ name = '', id = undefined, options} : NetworkParameters) {
//log(`[Network][${name}] netowrk created`);
this.name = name;
this.listeners = [];
this.id = id || uuid();
this.server = null;
this.connections = new Pool();
this.serviceConnectionCallback = undefined;
this.serviceDisconnectCallback = undefined;
this.newListenersCallback = undefined;
this.timeout = options?.timeout || 5 * 60 * 1000; // 5 minutes
}
async connect({ name, host, port, as } : { name?: string, host: string, port: number, as?: string }): Promise<Connection> {
/* this function connects to a server instance or a Node Manager
* and it keeps track of the conenction by adding it to a pool of server connection
* it uses the name as the key in the pool
* then run the callback */
const connection = new Connection({
name : this.name, host, port, id: this.id,
options: {
timeout: this.timeout,
onSetListeners: this.newListenersCallback
}
});
// await connection and handshake
await connection.connected();
// get the name of the target service
let server_name = connection.getTargetName();
if(server_name === undefined)
throw new Error('Server name is undefined');
if(name !== undefined) // check if the name is the same
if(server_name !== name)
throw new Error(`Server name mismatch: ${server_name} !== ${name}`);
// if we are saving the connection as a different name
if(as !== undefined) server_name = as;
// if we already have a connection with the same id, remove it
if(this.connections.has(server_name)) {
let conn = this.connections.remove(server_name);
conn && conn.close();
}
// add the listeners to the new connection
connection.setListeners(this.listeners);
// add the new connection
this.connections.add(server_name, connection);
// new connection callback
this.serviceConnectionCallback && this.serviceConnectionCallback(connection);
// return the connection
return connection;
}
public async connectAll(services: { name: string, host: string, port: number }[]) {
log(`[Network][${this.name}] connecting to all services`, services);
// connect to all the services
let connections = await Promise.all(services.map(
async service => await this.connect({
name: service.name,
host: service.host,
port: service.port
})
)).catch((err) => {
log(`[Network][${this.name}] error connecting to services:`, err);
return [];
})
//log(`[Network][${this.name}] returning connections:`, connections);
return connections;
}
public createServer(
name: string, host: string, port: number,
listeners: Listener[] = []
) {
// the server keeps track of it client connections
this.server = new Server({ name, host, port, listeners });
}
public close() {
// if we have a server we close it
if(this.server){
this.server.close()
}
// close all the connections
this.connections.toArray().forEach((connection: Connection) => {
connection.close();
});
}
public getService(name: string): Connection {
// get the service connection
let service = this.connections.get(name);
// if the service is not found we throw an error
if(service === null)
throw new Error(`Service ${name} not found for ${this.name}`);
return service;
}
public getServices(): Connection[] {
// get all the clients that are services types
return this.connections.toArray();
}
public getNode(id: string): Connection {
// get the client that is a node type
if(this.server === null) throw new Error('Server is not created');
let client = this.server.getClient(id);
// throw an error if the client is not found
if(client === null) throw new Error('Client not found');
// return the client
return client;
}
public getNodes(): Connection[] {
// get all the clients that are nodes types
let nodes = this.server?.getClients().
filter((conn: Connection) => conn.targetType === 'client');
return nodes || [];
}
public getConnections(): Connection[] {
// get all the connections
return this.connections.toArray().concat(this.server?.getClients() || []);
}
public closeService(name: string) {
// close the connection
let connection = this.connections.remove(name);
if(connection) connection.close();
// callback
this.serviceDisconnectCallback &&
this.serviceDisconnectCallback(connection);
}
public closeConnection(id: string) {
// close the connection
let connection = this.connections.remove(id);
if(connection) connection.close();
}
public registerListeners(listeners: Listener[]) {
/* this function registers the listeners to the network and overwrites the old ones */
//log(`[Network][RegisterListners][${this.name}]`, listeners);
//log(`[Network][RegisterListners][${this.name}] connections:`, this.connections);
// store the listeners
this.listeners = listeners;
// if we have a server created we pass the listeners to it
if(this.server) this.server.setListeners(this.listeners);
// for every conenction in out pool we register the listeners
this.connections.toArray().forEach((connection: Connection) => {
connection.setListeners(listeners);
});
}
public addListeners(listeners: Listener[]) {
/* this function adds the listeners to the network */
this.listeners = this.listeners.concat(listeners);
// if we have a server created we pass the listeners to it
if(this.server) this.server.addListeners(listeners);
// for every conenction in out pool we register the listeners
this.connections.toArray().forEach((connection: Connection) => {
connection.addListeners(listeners);
});
}
public getRegisteredListeners(): any {
// get the listeners from the server
let server_listeners = this.server?.getListeners() || [];
let connections_listeners = this.connections.toArray().map((connection: Connection) => {
log('inside loop conenction:', connection);
return { id: connection.id, listeners: connection.getListeners() };
});
// return the listeners
return { server: server_listeners, connections: connections_listeners };
}
/* callbacks */
public onNodeConnection(callback: (connection: Connection) => void) {
if(this.server === null) throw new Error('Server is not created');
this.server.onConnection(callback);
}
public onNodeDisconnect(callback: (connection: Connection) => void) {
if(this.server === null) throw new Error('Server is not created');
this.server.onDisconnect(callback);
}
public onServiceConnection(callback: (connection: Connection) => void) {
// set the network listners to the connection
this.serviceConnectionCallback = callback;
}
public onServiceDisconnect(callback: (connection: Connection) => void) {
this.serviceDisconnectCallback = callback;
}
public onNewListeners(callback: (listeners: Listener[]) => void) {
this.newListenersCallback = callback;
}
}
export default Network;