diff --git a/src/server/server.js b/src/server/server.js index b13145b..59d6ac2 100644 --- a/src/server/server.js +++ b/src/server/server.js @@ -1,18 +1,13 @@ const fs = require("fs") - const http = require("http") const https = require("https") const io = require("socket.io") const tokenizer = require("corenode/libs/tokenizer") -const { randomWord } = require("@corenode/utils") - const { serverManifest, outputServerError, internalConsole } = require("./lib") const InternalConsole = global.InternalInternalConsole = internalConsole -const builtInMiddlewares = [] - const HTTPProtocolsInstances = { http: http, https: https, @@ -30,70 +25,56 @@ const HTTPEngines = { }, } +const linebridge_ascii = require("./linebridge_ascii.js") + class Server { - constructor(params = {}, controllers = [], middlewares = {}) { + constructor(params = {}, controllers = {}, middlewares = {}, headers = {}) { + // register aliases this.params = { ...global.DEFAULT_SERVER_PARAMS, ...params } - this.controllers = [ + + this.controllers = { ...controllers - ] + } this.middlewares = { - ...middlewares + ...middlewares.default ?? middlewares, } this.headers = { - ...global.DEFAULT_HEADERS, - ...this.params.headers + ...global.DEFAULT_SERVER_HEADERS, + ...headers } - this.endpointsMap = {} - this.listenPort = this.params.port ?? 3010 + this.endpoints_map = {} - // TODO: Handle HTTPS and WSS - this.HTTPAddress = `${this.params.protocol}://${global.LOCALHOST_ADDRESS}:${this.listenPort}` - this.WSAddress = `${this.params.wsProtocol}://${global.LOCALHOST_ADDRESS}:${this.listenPort}` + // fix and fulfill params + this.params.listen_port = this.params.listen_port ?? 3000 + this.params.http_protocol = this.params.http_protocol ?? "http" + this.params.ws_protocol = this.params.ws_protocol ?? "ws" + + this.params.http_address = `${this.params.http_protocol}://${global.LOCALHOST_ADDRESS}:${this.params.listen_port}` + this.params.ws_address = `${this.params.ws_protocol}://${global.LOCALHOST_ADDRESS}:${this.params.listen_port}` - //* set server basics // check if engine is supported - if (typeof HTTPProtocolsInstances[this.params.protocol].createServer !== "function") { + if (typeof HTTPProtocolsInstances[this.params.http_protocol]?.createServer !== "function") { throw new Error("Invalid HTTP protocol (Missing createServer function)") } - this.engineInstance = global.engineInstance = HTTPEngines[this.params.httpEngine]() - this.httpInstance = global.httpInstance = HTTPProtocolsInstances[this.params.protocol].createServer({ + // create instances the 3 main instances of the server (Engine, HTTP, WebSocket) + this.engine_instance = global.engine_instance = HTTPEngines[this.params.engine]() + + this.http_instance = global.http_instance = HTTPProtocolsInstances[this.params.http_protocol].createServer({ ...this.params.httpOptions ?? {}, - }, this.engineInstance) - this.wsInterface = global.wsInterface = { - io: new io.Server(this.httpInstance), + }, this.engine_instance) + + this.websocket_instance = global.websocket_instance = { + io: new io.Server(this.http_instance), map: {}, eventsChannels: [], } - //? check if origin.server exists - if (!fs.existsSync(serverManifest.filepath)) { - serverManifest.create() - } - - //? check origin.server integrity - const MANIFEST_DATA = global.MANIFEST_DATA = serverManifest.get() - const MANIFEST_STAT = global.MANIFEST_STAT = serverManifest.stat() - - if (typeof MANIFEST_DATA.created === "undefined") { - InternalConsole.warn("Server generation file not contains an creation date") - serverManifest.write({ created: Date.parse(MANIFEST_STAT.birthtime) }) - } - - if (typeof MANIFEST_DATA.serverToken === "undefined") { - InternalConsole.warn("Missing server token!") - serverManifest.create() - } - - this.id = this.params.id ?? randomWord.generate() ?? "unavailable" - this.usid = tokenizer.generateUSID() - this.oskid = serverManifest.get("serverToken") - - serverManifest.write({ lastStart: Date.now() }) + this.initializeManifest() // handle silent mode global.consoleSilent = this.params.silent @@ -107,39 +88,67 @@ class Server { } } + // handle exit events + process.on("SIGTERM", this.cleanupProcess) + process.on("SIGINT", this.cleanupProcess) + return this } + initializeManifest = () => { + // check if origin.server exists + if (!fs.existsSync(serverManifest.filepath)) { + serverManifest.create() + } + + // check origin.server integrity + const MANIFEST_DATA = global.MANIFEST_DATA = serverManifest.get() + const MANIFEST_STAT = global.MANIFEST_STAT = serverManifest.stat() + + if (typeof MANIFEST_DATA.created === "undefined") { + InternalConsole.warn("Server generation file not contains an creation date") + serverManifest.write({ created: Date.parse(MANIFEST_STAT.birthtime) }) + } + + if (typeof MANIFEST_DATA.server_token === "undefined") { + InternalConsole.warn("Missing server token!") + serverManifest.create() + } + + this.usid = tokenizer.generateUSID() + this.server_token = serverManifest.get("server_token") + + serverManifest.write({ last_start: Date.now() }) + } + initialize = async () => { + InternalConsole.log(linebridge_ascii) + InternalConsole.info(`🚀 Starting server...`) + //* set server defined headers this.initializeHeaders() //* set server defined middlewares - this.initializeMiddlewares() - - //* register main index endpoint `/` - await this.registerBaseEndpoints() + this.initializeRequiredMiddlewares() //* register controllers await this.initializeControllers() + //* register main index endpoint `/` + await this.registerBaseEndpoints() + // initialize main socket - this.wsInterface.io.on("connection", this.handleWSClientConnection) + this.websocket_instance.io.on("connection", this.handleWSClientConnection) // initialize http server - await this.httpInstance.listen(this.listenPort, this.params.listen ?? "0.0.0.0") - - // output server info - InternalConsole.log(`✅ Server is up and running!`) - this.OutputServerInfo() - - // handle exit events - process.on("SIGTERM", this.cleanupProcess) - process.on("SIGINT", this.cleanupProcess) + await this.http_instance.listen(this.params.listen_port, this.params.listen_ip ?? "0.0.0.0", () => { + // output server info + this.outputServerInfo() + }) } initializeHeaders = () => { - this.engineInstance.use((req, res, next) => { + this.engine_instance.use((req, res, next) => { Object.keys(this.headers).forEach((key) => { res.setHeader(key, this.headers[key]) }) @@ -148,18 +157,20 @@ class Server { }) } - initializeMiddlewares = () => { - const useMiddlewares = [...builtInMiddlewares, ...global.DEFAULT_MIDDLEWARES, ...(this.params.middlewares ?? [])] + initializeRequiredMiddlewares = () => { + const useMiddlewares = [...this.params.useMiddlewares ?? [], ...global.DEFAULT_MIDDLEWARES] useMiddlewares.forEach((middleware) => { if (typeof middleware === "function") { - this.engineInstance.use(middleware) + this.engine_instance.use(middleware) } }) } initializeControllers = async () => { - for await (let controller of this.controllers) { + const controllers = Object.entries(this.controllers) + + for await (let [key, controller] of controllers) { if (typeof controller !== "function") { throw new Error(`Controller must use the controller class!`) } @@ -173,8 +184,8 @@ class Server { const ControllerInstance = new controller() // get endpoints from controller (ComplexController) - const HTTPEndpoints = ControllerInstance.getEndpoints() - const WSEndpoints = ControllerInstance.getWSEndpoints() + const HTTPEndpoints = ControllerInstance.__get_http_endpoints() + const WSEndpoints = ControllerInstance.__get_ws_endpoints() HTTPEndpoints.forEach((endpoint) => { this.registerHTTPEndpoint(endpoint, ...this.resolveMiddlewares(controller.useMiddlewares)) @@ -204,7 +215,7 @@ class Server { } // check if method is supported - if (typeof this.engineInstance[endpoint.method] !== "function") { + if (typeof this.engine_instance[endpoint.method] !== "function") { throw new Error(`Method [${endpoint.method}] is not supported!`) } @@ -216,19 +227,19 @@ class Server { } // make sure method has root object on endpointsMap - if (typeof this.endpointsMap[endpoint.method] !== "object") { - this.endpointsMap[endpoint.method] = {} + if (typeof this.endpoints_map[endpoint.method] !== "object") { + this.endpoints_map[endpoint.method] = {} } // create model for http interface router const routeModel = [endpoint.route, ...middlewares, this.createHTTPRequestHandler(endpoint)] // register endpoint to http interface router - this.engineInstance[endpoint.method](...routeModel) + this.engine_instance[endpoint.method](...routeModel) // extend to map - this.endpointsMap[endpoint.method] = { - ...this.endpointsMap[endpoint.method], + this.endpoints_map[endpoint.method] = { + ...this.endpoints_map[endpoint.method], [endpoint.route]: { route: endpoint.route, enabled: endpoint.enabled ?? true, @@ -239,9 +250,9 @@ class Server { registerWSEndpoint = (endpoint, ...execs) => { endpoint.nsp = endpoint.nsp ?? "/main" - this.wsInterface.eventsChannels.push([endpoint.nsp, endpoint.on, endpoint.dispatch]) + this.websocket_instance.eventsChannels.push([endpoint.nsp, endpoint.on, endpoint.dispatch]) - this.wsInterface.map[endpoint.on] = { + this.websocket_instance.map[endpoint.on] = { nsp: endpoint.nsp, channel: endpoint.on, } @@ -261,53 +272,47 @@ class Server { fn: (req, res) => { return res.json({ LINEBRIDGE_SERVER_VERSION: LINEBRIDGE_SERVER_VERSION, - id: this.id, usid: this.usid, - oskid: this.oskid, requestTime: new Date().getTime(), - endpointsMap: this.endpointsMap, - wsEndpointsMap: this.wsInterface.map, + endpointsMap: this.endpoints_map, + wsEndpointsMap: this.websocket_instance.map, }) } }) } //* resolvers - resolveMiddlewares = (middlewares) => { - middlewares = Array.isArray(middlewares) ? middlewares : [middlewares] - const middlewaresArray = [] + resolveMiddlewares = (requestedMiddlewares) => { + requestedMiddlewares = Array.isArray(requestedMiddlewares) ? requestedMiddlewares : [requestedMiddlewares] - middlewares.forEach((middleware) => { - if (typeof middleware === "string") { - if (typeof this.middlewares[middleware] !== "function") { - throw new Error(`Middleware ${middleware} not found!`) + const execs = [] + + requestedMiddlewares.forEach((middlewareKey) => { + if (typeof middlewareKey === "string") { + if (typeof this.middlewares[middlewareKey] !== "function") { + throw new Error(`Middleware ${middlewareKey} not found!`) } - middlewaresArray.push(this.middlewares[middleware]) + execs.push(this.middlewares[middlewareKey]) } - if (typeof middleware === "function") { - middlewaresArray.push(middleware) + if (typeof middlewareKey === "function") { + execs.push(middlewareKey) } }) - return middlewaresArray + return execs } - log = (...args) => { - if (!this.params.silent) { - InternalConsole.log(...args) - } - } cleanupProcess = () => { - this.log("🛑 Stopping server...") + InternalConsole.log("🛑 Stopping server...") - if (typeof this.engineInstance.close === "function") { - this.engineInstance.close() + if (typeof this.engine_instance.close === "function") { + this.engine_instance.close() } - this.wsInterface.io.close() + this.websocket_instance.io.close() process.exit(1) } @@ -317,7 +322,7 @@ class Server { return async (req, res) => { try { // check if endpoint is disabled - if (!this.endpointsMap[endpoint.method][endpoint.route].enabled) { + if (!this.endpoints_map[endpoint.method][endpoint.route].enabled) { throw new Error("Endpoint is disabled!") } @@ -355,7 +360,7 @@ class Server { await this.params.onWSClientConnection(client) } - for await (const [nsp, on, dispatch] of this.wsInterface.eventsChannels) { + for await (const [nsp, on, dispatch] of this.websocket_instance.eventsChannels) { client.on(on, async (...args) => { try { await dispatch(client, ...args).catch((error) => { @@ -383,30 +388,28 @@ class Server { } // public methods - OutputServerInfo = () => { - InternalConsole.log(`🌐 Server info:`) + outputServerInfo = () => { + InternalConsole.log("✅ Ready !") + InternalConsole.table({ - "ID": this.id, - "HTTPEngine": this.params.httpEngine, - "Version": LINEBRIDGE_SERVER_VERSION, - "WS Protocol": this.params.wsProtocol, - "Protocol": this.params.protocol, - "HTTP address": this.HTTPAddress, - "WS address": this.WSAddress, - "Listen port": this.listenPort, + "linebridge_version": LINEBRIDGE_SERVER_VERSION, + "engine": this.params.engine, + "http_address": this.params.http_address, + "websocket_address": this.params.ws_address, + "listen_port": this.params.listen_port, }) } toogleEndpointReachability = (method, route, enabled) => { - if (typeof this.endpointsMap[method] !== "object") { + if (typeof this.endpoints_map[method] !== "object") { throw new Error(`Cannot toogle endpoint, method [${method}] not set!`) } - if (typeof this.endpointsMap[method][route] !== "object") { + if (typeof this.endpoints_map[method][route] !== "object") { throw new Error(`Cannot toogle endpoint [${route}], is not registered!`) } - this.endpointsMap[method][route].enabled = enabled ?? !this.endpointsMap[method][route].enabled + this.endpoints_map[method][route].enabled = enabled ?? !this.endpoints_map[method][route].enabled } }