diff --git a/src/classes/rtengine/index.js b/src/classes/rtengine/index.js index 40b3da9..5dfe542 100755 --- a/src/classes/rtengine/index.js +++ b/src/classes/rtengine/index.js @@ -6,254 +6,287 @@ import { EventEmitter } from "@foxify/events" import RedisMap from "../../lib/redis_map" export default class RTEngineServer { - constructor(params = {}) { - this.params = params - this.clusterMode = !!cluster.isWorker + constructor(params = {}) { + this.params = params + this.clusterMode = !!cluster.isWorker - this.redisConnParams = { - host: this.params.redisOptions?.host ?? process.env.REDIS_HOST ?? "localhost", - port: this.params.redisOptions?.port ?? process.env.REDIS_PORT ?? 6379, - username: this.params.redisOptions?.username ?? (process.env.REDIS_AUTH && process.env.REDIS_AUTH.split(":")[0]), - password: this.params.redisOptions?.password ?? (process.env.REDIS_AUTH && process.env.REDIS_AUTH.split(":")[1]), - db: this.params.redisOptions?.db ?? process.env.REDIS_DB ?? 0 - } + this.redisConnParams = { + host: + this.params.redisOptions?.host ?? + process.env.REDIS_HOST ?? + "localhost", + port: + this.params.redisOptions?.port ?? + process.env.REDIS_PORT ?? + 6379, + username: + this.params.redisOptions?.username ?? + (process.env.REDIS_AUTH && + process.env.REDIS_AUTH.split(":")[0]), + password: + this.params.redisOptions?.password ?? + (process.env.REDIS_AUTH && + process.env.REDIS_AUTH.split(":")[1]), + db: this.params.redisOptions?.db ?? process.env.REDIS_DB ?? 0, + } - this.redis = params.redis - this.io = params.io - } + this.redis = params.redis + this.io = params.io + } - worker_id = nanoid() + worker_id = nanoid() - io = null - redis = null + io = null + redis = null - connections = null - users = null + connections = null + users = null - events = new Map() + events = new Map() - async initialize() { - console.log("🌐 Initializing RTEngine server...") + async initialize() { + console.log("🌐 Initializing RTEngine server...") - if (!this.io) { - this.io = new SocketIO.Server({ - path: this.params.root ?? "/", - }) - } + if (!this.io) { + this.io = new SocketIO.Server({ + path: this.params.root ?? "/", + }) + } - if (!this.redis) { - this.redis = new redis({ - host: this.redisConnParams.host, - port: this.redisConnParams.port, - username: this.redisConnParams.username, - password: this.redisConnParams.password, - db: this.redisConnParams.db, - }) - } + if (!this.redis) { + this.redis = new redis({ + lazyConnect: true, + host: this.redisConnParams.host, + port: this.redisConnParams.port, + username: this.redisConnParams.username, + password: this.redisConnParams.password, + db: this.redisConnParams.db, + maxRetriesPerRequest: null, + }) + } - // create mappers - this.connections = new RedisMap(this.redis, { - refKey: "connections", - worker_id: this.worker_id, - }) + await this.redis.connect() - this.users = new RedisMap(this.redis, { - refKey: "users", - worker_id: this.worker_id, - }) + // create mappers + this.connections = new RedisMap(this.redis, { + refKey: "connections", + worker_id: this.worker_id, + }) - // register middlewares - if (typeof this.middlewares === "object" && Array.isArray(this.middlewares)) { - for (const middleware of this.middlewares) { - this.io.use(middleware) - } - } + this.users = new RedisMap(this.redis, { + refKey: "users", + worker_id: this.worker_id, + }) - // handle connection - this.io.on("connection", (socket) => { - this.eventHandler(this.onConnect, socket) - }) + // register middlewares + if ( + typeof this.middlewares === "object" && + Array.isArray(this.middlewares) + ) { + for (const middleware of this.middlewares) { + this.io.use(middleware) + } + } - console.log(`[RTEngine] Listening...`) - console.log(`[RTEngine] Universal worker id [${this.worker_id}]`) + // handle connection + this.io.on("connection", (socket) => { + this.eventHandler(this.onConnect, socket) + }) - return true - } + console.log(`[RTEngine] Listening...`) + console.log(`[RTEngine] Universal worker id [${this.worker_id}]`) - close = () => { - console.log(`Cleaning up RTEngine server...`) + return true + } - // WARN: Do not flush connections pls - if (process.env.NODE_ENV !== "production") { - console.log(`Flushing previus connections... (Only for dev mode)`) - this.connections.flush() - } + close = () => { + console.log(`Cleaning up RTEngine server...`) - if (this.clusterMode) { - this.connections.flush(cluster.worker.id) - } + // WARN: Do not flush connections pls + if (process.env.NODE_ENV !== "production") { + console.log(`Flushing previus connections... (Only for dev mode)`) + this.connections.flush() + } - if (this.io) { - this.io.close() - } + if (this.clusterMode) { + this.connections.flush(cluster.worker.id) + } - if (this.redis) { - this.redis.quit() - } - } + if (this.io) { + this.io.close() + } - onConnect = async (socket) => { - console.log(`[RTEngine] new:client | id [${socket.id}]`) + if (this.redis) { + this.redis.quit() + } + } - // create eventBus - socket.eventBus = new EventEmitter() - socket.pendingTimeouts = new Set() + onConnect = async (socket) => { + console.log(`[RTEngine] new:client | id [${socket.id}]`) - // register events - if (typeof this.events === "object") { - for (const [key, handler] of this.events.entries()) { - socket.on(key, (...args) => { - this.eventHandler(handler, socket, ...args) - }) - } - } + // create eventBus + socket.eventBus = new EventEmitter() + socket.pendingTimeouts = new Set() - // handle ping - socket.on("ping", () => { - socket.emit("pong") - }) + // register events + if (typeof this.events === "object") { + console.log("registering events", this.events) + for (const [key, handler] of this.events.entries()) { + socket.on(key, (...args) => { + this.eventHandler(handler, socket, ...args) + }) + } + } - // handle disconnect - socket.on("disconnect", () => { - this.eventHandler(this.onDisconnect, socket) - }) + // handle ping + socket.on("ping", () => { + socket.emit("pong") + }) - await this.connections.set(socket.id, socket) + // handle disconnect + socket.on("disconnect", () => { + this.eventHandler(this.onDisconnect, socket) + }) - if (this.params.requireAuth) { - await this.onAuth(socket, null, (this.params.handleAuth ?? this.handleAuth)) - } else if (socket.handshake.auth.token ?? socket.handshake.query.auth) { - await this.onAuth(socket, (socket.handshake.auth.token ?? socket.handshake.query.auth), (this.params.handleAuth ?? this.handleAuth)) - } - } + await this.connections.set(socket.id, socket) - onDisconnect = async (socket,) => { - console.log(`[RTEngine] disconnect:client | id [${socket.id}]`) + if (this.params.requireAuth) { + await this.onAuth( + socket, + null, + this.params.handleAuth ?? this.handleAuth, + ) + } else if (socket.handshake.auth.token ?? socket.handshake.query.auth) { + await this.onAuth( + socket, + socket.handshake.auth.token ?? socket.handshake.query.auth, + this.params.handleAuth ?? this.handleAuth, + ) + } + } - if (socket.eventBus.emit) { - socket.eventBus.emit("disconnect") - } else { - console.warn(`[${socket.id}][@${socket.userData.username}] Cannot emit disconnect event`) - } + onDisconnect = async (socket) => { + console.log(`[RTEngine] disconnect:client | id [${socket.id}]`) - const conn = await this.connections.get(socket.id) + if (socket.eventBus.emit) { + socket.eventBus.emit("disconnect") + } else { + console.warn( + `[${socket.id}][@${socket.userData.username}] Cannot emit disconnect event`, + ) + } - if (conn) { - if (conn.user_id) { - await this.users.del(conn.user_id) - } - } + const conn = await this.connections.get(socket.id) - await this.connections.del(socket.id) - } + if (conn) { + if (conn.user_id) { + await this.users.del(conn.user_id) + } + } - onAuth = async (socket, token, handleAuth) => { - if (typeof handleAuth !== "function") { - console.log(`[RTEngine] [${socket.id}] No auth handler provided`) - return false - } + await this.connections.del(socket.id) + } - if (!token) { - if (socket.handshake.auth.token) { - token = socket.handshake.auth.token - } - if (socket.handshake.query.auth) { - token = socket.handshake.query.auth - } - } + onAuth = async (socket, token, handleAuth) => { + if (typeof handleAuth !== "function") { + console.log(`[RTEngine] [${socket.id}] No auth handler provided`) + return false + } - function err(code, message) { - console.log(`[RTEngine] [${socket.id}] Auth error: ${code} >`, message) + if (!token) { + if (socket.handshake.auth.token) { + token = socket.handshake.auth.token + } + if (socket.handshake.query.auth) { + token = socket.handshake.query.auth + } + } - socket.emit("response:error", { - code, - message, - }) + function err(code, message) { + console.log( + `[RTEngine] [${socket.id}] Auth error: ${code} >`, + message, + ) - socket.disconnect() + socket.emit("response:error", { + code, + message, + }) - return false - } + socket.disconnect() - if (!token) { - return err(401, "auth:token_required") - } + return false + } - const authResult = await handleAuth(socket, token, err) + if (!token) { + return err(401, "auth:token_required") + } - if (authResult) { - const conn = await this.connections.has(socket.id) + const authResult = await handleAuth(socket, token, err) - // check if connection update is valid to avoid race condition(When user disconnect before auth verification is completed) - if (!conn) { - console.log(`Auth aborted`) - return false - } + if (authResult) { + const conn = await this.connections.has(socket.id) - this.users.set(authResult.user_id.toString(), { - socket_id: socket.id, - ...authResult, - }) + // check if connection update is valid to avoid race condition(When user disconnect before auth verification is completed) + if (!conn) { + console.log(`Auth aborted`) + return false + } - socket.emit("response:auth:ok") + this.users.set(authResult.user_id.toString(), { + socket_id: socket.id, + ...authResult, + }) - console.log(`[RTEngine] client:authenticated | socket_id [${socket.id}] | user_id [${authResult.user_id}] | username [@${authResult.username}]`) - } - } + socket.emit("response:auth:ok") - eventHandler = async (fn, socket, payload) => { - try { - await fn(socket, payload, this) - } catch (error) { - console.error(error) + console.log( + `[RTEngine] client:authenticated | socket_id [${socket.id}] | user_id [${authResult.user_id}] | username [@${authResult.username}]`, + ) + } + } - if (typeof socket.emit === "function") { - socket.emit("response:error", { - code: 500, - message: error.message, - }) - } - } - } + eventHandler = async (fn, socket, payload) => { + try { + await fn(socket, payload, this) + } catch (error) { + console.error(error) - find = { - manyById: async (ids) => { - if (typeof ids === "string") { - ids = [ids] - } + if (typeof socket.emit === "function") { + socket.emit("response:error", { + code: 500, + message: error.message, + }) + } + } + } - const users = await this.users.getMany(ids) + find = { + manyById: async (ids) => { + if (typeof ids === "string") { + ids = [ids] + } - return users - }, - userBySocket: (socket_id) => { + const users = await this.users.getMany(ids) - }, - userById: async (user_id) => { - const user = await this.users.get(user_id) + return users + }, + userBySocket: (socket_id) => {}, + userById: async (user_id) => { + const user = await this.users.get(user_id) - return user - }, - socketByUserId: async (user_id) => { - const user = await this.users.get(user_id) + return user + }, + socketByUserId: async (user_id) => { + const user = await this.users.get(user_id) - if (!user) { - return null - } + if (!user) { + return null + } - const socket = await this.connections.get(user.socket_id) + const socket = await this.connections.get(user.socket_id) - return socket - } - } -} \ No newline at end of file + return socket + }, + } +} diff --git a/src/engines/hyper-express/index.js b/src/engines/hyper-express/index.js index 26dea2e..40add18 100644 --- a/src/engines/hyper-express/index.js +++ b/src/engines/hyper-express/index.js @@ -2,140 +2,149 @@ import he from "hyper-express" import rtengine from "../../classes/rtengine" export default class Engine { - constructor(params) { - this.params = params - } + constructor(params, ctx) { + this.params = params + this.ctx = ctx + } - app = null - router = null - ws = null + app = null + router = null + ws = null - initialize = async (params) => { - const serverParams = { - max_body_length: 50 * 1024 * 1024, //50MB in bytes, - } + initialize = async (params) => { + const serverParams = { + max_body_length: 50 * 1024 * 1024, //50MB in bytes, + } - if (params.ssl) { - serverParams.key_file_name = params.ssl?.key ?? null - serverParams.cert_file_name = params.ssl?.cert ?? null - } + if (params.ssl) { + serverParams.key_file_name = params.ssl?.key ?? null + serverParams.cert_file_name = params.ssl?.cert ?? null + } - this.app = new he.Server(serverParams) + this.app = new he.Server(serverParams) - this.router = new he.Router() + this.router = new he.Router() - // create a router map - if (typeof this.router.map !== "object") { - this.router.map = {} - } + // create a router map + if (typeof this.router.map !== "object") { + this.router.map = {} + } - await this.router.any("*", (req, res) => { - return res.status(404).json({ - code: 404, - message: "Not found" - }) - }) + await this.router.any("*", (req, res) => { + return res.status(404).json({ + code: 404, + message: "Not found", + }) + }) - await this.app.use(async (req, res, next) => { - if (req.method === "OPTIONS") { - // handle cors - if (params.ignoreCors) { - res.setHeader("Access-Control-Allow-Methods", "*") - res.setHeader("Access-Control-Allow-Origin", "*") - res.setHeader("Access-Control-Allow-Headers", "*") - } + await this.app.use(async (req, res, next) => { + if (req.method === "OPTIONS") { + // handle cors + if (params.ignoreCors) { + res.setHeader("Access-Control-Allow-Methods", "*") + res.setHeader("Access-Control-Allow-Origin", "*") + res.setHeader("Access-Control-Allow-Headers", "*") + } - return res.status(204).end() - } + return res.status(204).end() + } - // register body parser - if (req.headers["content-type"]) { - if (!req.headers["content-type"].startsWith("multipart/form-data")) { - req.body = await req.urlencoded() - req.body = await req.json(req.body) - } - } - }) + // register body parser + if (req.headers["content-type"]) { + if ( + !req.headers["content-type"].startsWith( + "multipart/form-data", + ) + ) { + req.body = await req.urlencoded() + req.body = await req.json(req.body) + } + } + }) - if (params.enableWebsockets) { - this.ws = global.websocket = new rtengine({ - ...params, - handleAuth: params.handleWsAuth, - root: `/${params.refName}` - }) + if (params.enableWebsockets) { + this.ws = global.websocket = new rtengine({ + ...params, + handleAuth: params.handleWsAuth, + root: `/${params.refName}`, + }) - this.ws.initialize() + this.ws.initialize() - await this.ws.io.attachApp(this.app.uws_instance) - } - } + await this.ws.io.attachApp(this.app.uws_instance) + } + } - listen = async (params) => { - if (process.env.lb_service) { - let pathOverrides = Object.keys(this.router.map).map((key) => { - return key.split("/")[1] - }) + listen = async (params) => { + if (process.env.lb_service) { + let pathOverrides = Object.keys(this.router.map).map((key) => { + return key.split("/")[1] + }) - // remove duplicates - pathOverrides = [...new Set(pathOverrides)] + // remove duplicates + pathOverrides = [...new Set(pathOverrides)] - // remove "" and _map - pathOverrides = pathOverrides.filter((key) => { - if (key === "" || key === "_map") { - return false - } + // remove "" and _map + pathOverrides = pathOverrides.filter((key) => { + if (key === "" || key === "_map") { + return false + } - return true - }) + return true + }) - if (params.enableWebsockets) { - process.send({ - type: "router:ws:register", - id: process.env.lb_service.id, - index: process.env.lb_service.index, - data: { - namespace: params.refName, - listen: { - ip: this.params.listen_ip, - port: this.params.listen_port, - }, - } - }) - } + if (params.enableWebsockets) { + process.send({ + type: "router:ws:register", + id: process.env.lb_service.id, + index: process.env.lb_service.index, + data: { + namespace: params.refName, + listen: { + ip: this.params.listen_ip, + port: this.params.listen_port, + }, + }, + }) + } - if (process.send) { - // try to send router map to host - process.send({ - type: "router:register", - id: process.env.lb_service.id, - index: process.env.lb_service.index, - data: { - router_map: this.router.map, - path_overrides: pathOverrides, - listen: { - ip: this.params.listen_ip, - port: this.params.listen_port, - }, - } - }) - } - } + if (process.send) { + // try to send router map to host + process.send({ + type: "router:register", + id: process.env.lb_service.id, + index: process.env.lb_service.index, + data: { + router_map: this.router.map, + path_overrides: pathOverrides, + listen: { + ip: this.params.listen_ip, + port: this.params.listen_port, + }, + }, + }) + } + } - await this.app.listen(this.params.listen_port) - } + await this.app.listen(this.params.listen_port) + } - // close should be synchronous - close = () => { - if (this.ws) { - this.ws.clear() + // close should be synchronous + close = () => { + if (this.ws) { + this.ws.clear() - if (typeof this.ws?.close === "function") { - this.ws.close() - } - } + if (typeof this.ws?.close === "function") { + this.ws.close() + } + } - if (typeof this.app?.close === "function") { - this.app.close() - } - } -} \ No newline at end of file + if (typeof this.app?.close === "function") { + this.app.close() + } + + if (typeof this.ctx.onClose === "function") { + this.ctx.onClose() + } + } +} diff --git a/src/server.js b/src/server.js index 411c3b3..ba8adf6 100755 --- a/src/server.js +++ b/src/server.js @@ -14,280 +14,333 @@ import registerWebsocketsEvents from "./initializators/registerWebsocketsEvents" import registerHttpRoutes from "./initializators/registerHttpRoutes" async function loadEngine(engine) { - const enginesPath = path.resolve(__dirname, "engines") + const enginesPath = path.resolve(__dirname, "engines") - const selectedEnginePath = path.resolve(enginesPath, engine) + const selectedEnginePath = path.resolve(enginesPath, engine) - if (!fs.existsSync(selectedEnginePath)) { - throw new Error(`Engine ${engine} not found!`) - } + if (!fs.existsSync(selectedEnginePath)) { + throw new Error(`Engine ${engine} not found!`) + } - return require(selectedEnginePath).default + return require(selectedEnginePath).default } class Server { - constructor(params = {}, controllers = {}, middlewares = {}, headers = {}) { - this.isExperimental = defaults.isExperimental ?? false + constructor(params = {}, controllers = {}, middlewares = {}, headers = {}) { + this.isExperimental = defaults.isExperimental ?? false - if (this.isExperimental) { - console.warn("\n🚧 This version of Linebridge is experimental! 🚧") - console.warn(`Version: ${defaults.version}\n`) - } + if (this.isExperimental) { + console.warn("\n🚧 This version of Linebridge is experimental! 🚧") + console.warn(`Version: ${defaults.version}\n`) + } - this.params = { - ...defaults.params, - ...params.default ?? params, - } + this.params = { + ...defaults.params, + ...(params.default ?? params), + } - this.controllers = { - ...controllers.default ?? controllers, - } + this.controllers = { + ...(controllers.default ?? controllers), + } - this.middlewares = { - ...middlewares.default ?? middlewares, - } + this.middlewares = { + ...(middlewares.default ?? middlewares), + } - this.headers = { - ...defaults.headers, - ...headers.default ?? headers, - } + this.headers = { + ...defaults.headers, + ...(headers.default ?? headers), + } - // fix and fulfill params - this.params.useMiddlewares = this.params.useMiddlewares ?? [] - this.params.name = this.constructor.refName ?? this.params.refName - this.params.useEngine = this.constructor.useEngine ?? this.params.useEngine ?? "hyper-express" - this.params.listen_ip = this.constructor.listenIp ?? this.constructor.listen_ip ?? this.params.listen_ip ?? "0.0.0.0" - this.params.listen_port = this.constructor.listenPort ?? this.constructor.listen_port ?? this.params.listen_port ?? 3000 - this.params.http_protocol = this.params.http_protocol ?? "http" - this.params.http_address = `${this.params.http_protocol}://${defaults.localhost_address}:${this.params.listen_port}` - this.params.enableWebsockets = this.constructor.enableWebsockets ?? this.params.enableWebsockets ?? false - this.params.ignoreCors = this.constructor.ignoreCors ?? this.params.ignoreCors ?? true + // fix and fulfill params + this.params.useMiddlewares = this.params.useMiddlewares ?? [] - this.params.routesPath = this.constructor.routesPath ?? this.params.routesPath ?? path.resolve(process.cwd(), "routes") - this.params.wsRoutesPath = this.constructor.wsRoutesPath ?? this.params.wsRoutesPath ?? path.resolve(process.cwd(), "routes_ws") + this.params.name = this.constructor.refName ?? this.params.refName - globalThis._linebridge = { - name: this.params.name, - useEngine: this.params.useEngine, - listenIp: this.params.listen_ip, - listenPort: this.params.listen_port, - httpProtocol: this.params.http_protocol, - httpAddress: this.params.http_address, - enableWebsockets: this.params.enableWebsockets, - ignoreCors: this.params.ignoreCors, - routesPath: this.params.routesPath, - validHttpMethods: defaults.valid_http_methods, - } + this.params.useEngine = + this.constructor.useEngine ?? + this.params.useEngine ?? + "hyper-express" - return this - } + this.params.listen_ip = + this.constructor.listenIp ?? + this.constructor.listen_ip ?? + this.params.listen_ip ?? + "0.0.0.0" - engine = null + this.params.listen_port = + this.constructor.listenPort ?? + this.constructor.listen_port ?? + this.params.listen_port ?? + 3000 - events = null + this.params.http_protocol = this.params.http_protocol ?? "http" - ipc = null + this.params.http_address = `${this.params.http_protocol}://${defaults.localhost_address}:${this.params.listen_port}` - ipcEvents = null + this.params.enableWebsockets = + this.constructor.enableWebsockets ?? + this.params.enableWebsockets ?? + false - eventBus = new EventEmitter() + this.params.ignoreCors = + this.constructor.ignoreCors ?? this.params.ignoreCors ?? true - initialize = async () => { - const startHrTime = process.hrtime() + this.params.disableBaseEndpoints = + this.constructor.disableBaseEndpoints ?? + this.params.disableBaseEndpoints ?? + false - // register events - if (this.events) { - if (this.events.default) { - this.events = this.events.default - } + this.params.routesPath = + this.constructor.routesPath ?? + this.params.routesPath ?? + path.resolve(process.cwd(), "routes") - for (const [eventName, eventHandler] of Object.entries(this.events)) { - this.eventBus.on(eventName, eventHandler) - } - } + this.params.wsRoutesPath = + this.constructor.wsRoutesPath ?? + this.params.wsRoutesPath ?? + path.resolve(process.cwd(), "routes_ws") - const engineParams = { - ...this.params, - handleWsAuth: this.handleWsAuth, - handleAuth: this.handleHttpAuth, - requireAuth: this.constructor.requireHttpAuth, - refName: this.constructor.refName ?? this.params.refName, - ssl: this.ssl, - } + globalThis._linebridge = { + name: this.params.name, + useEngine: this.params.useEngine, + listenIp: this.params.listen_ip, + listenPort: this.params.listen_port, + httpProtocol: this.params.http_protocol, + httpAddress: this.params.http_address, + enableWebsockets: this.params.enableWebsockets, + ignoreCors: this.params.ignoreCors, + routesPath: this.params.routesPath, + validHttpMethods: defaults.valid_http_methods, + } - // initialize engine - this.engine = await loadEngine(this.params.useEngine) + return this + } - this.engine = new this.engine(engineParams) + engine = null - if (typeof this.engine.initialize === "function") { - await this.engine.initialize(engineParams) - } + events = null - // check if ws events are defined - if (typeof this.wsEvents !== "undefined") { - if (!this.engine.ws) { - console.warn("`wsEvents` detected, but Websockets are not enabled! Ignoring...") - } else { - for (const [eventName, eventHandler] of Object.entries(this.wsEvents)) { - this.engine.ws.events.set(eventName, eventHandler) - } - } - } + ipc = null - // try to execute onInitialize hook - if (typeof this.onInitialize === "function") { - try { - await this.onInitialize() - } - catch (err) { - console.error(err) - process.exit(1) - } - } + ipcEvents = null - // set defaults - this.useDefaultHeaders() - this.useDefaultMiddlewares() + eventBus = new EventEmitter() - if (this.routes) { - for (const [route, endpoint] of Object.entries(this.routes)) { - this.engine.router.map[route] = new Endpoint( - this, - { - ...endpoint, - route: route, - handlers: { - [endpoint.method]: endpoint.fn, - }, - } - ) - } - } + initialize = async () => { + const startHrTime = process.hrtime() - // register http & ws routes - this.engine = await registerHttpRoutes(this.params.routesPath, this.engine, this) - this.engine = await registerWebsocketsEvents(this.params.wsRoutesPath, this.engine) + // register events + if (this.events) { + if (this.events.default) { + this.events = this.events.default + } - // register base endpoints if enabled - if (!this.params.disableBaseEndpoint) { - await registerBaseEndpoints(this) - } + for (const [eventName, eventHandler] of Object.entries( + this.events, + )) { + this.eventBus.on(eventName, eventHandler) + } + } - // use main router - await this.engine.app.use(this.engine.router) + const engineParams = { + ...this.params, + handleWsAuth: this.handleWsAuth, + handleAuth: this.handleHttpAuth, + requireAuth: this.constructor.requireHttpAuth, + refName: this.constructor.refName ?? this.params.refName, + ssl: this.ssl, + } - // if is a linebridge service then initialize IPC Channels - if (process.env.lb_service) { - await this.initializeIpc() - } + // initialize engine + this.engine = await loadEngine(this.params.useEngine) - // try to execute beforeInitialize hook. - if (typeof this.afterInitialize === "function") { - await this.afterInitialize() - } + this.engine = new this.engine(engineParams, this) - // listen - await this.engine.listen(engineParams) + if (typeof this.engine.initialize === "function") { + await this.engine.initialize(engineParams) + } - // calculate elapsed time on ms, to fixed 2 - const elapsedHrTime = process.hrtime(startHrTime) - const elapsedTimeInMs = elapsedHrTime[0] * 1e3 + elapsedHrTime[1] / 1e6 + // check if ws events are defined + if (typeof this.wsEvents !== "undefined") { + if (!this.engine.ws) { + console.warn( + "`wsEvents` detected, but Websockets are not enabled! Ignoring...", + ) + } else { + for (const [eventName, eventHandler] of Object.entries( + this.wsEvents, + )) { + this.engine.ws.events.set(eventName, eventHandler) + } + } + } - console.info(`šŸ›° Server ready!\n\t - ${this.params.http_protocol}://${this.params.listen_ip}:${this.params.listen_port} \n\t - Tooks ${elapsedTimeInMs.toFixed(2)}ms`) - } + // try to execute onInitialize hook + if (typeof this.onInitialize === "function") { + try { + await this.onInitialize() + } catch (err) { + console.error(err) + process.exit(1) + } + } - initializeIpc = async () => { - console.info("šŸš„ Starting IPC client") + // set defaults + this.useDefaultHeaders() + this.useDefaultMiddlewares() - this.ipc = global.ipc = new IPCClient(this, process) - } + if (this.routes) { + for (const [route, endpoint] of Object.entries(this.routes)) { + this.engine.router.map[route] = new Endpoint(this, { + ...endpoint, + route: route, + handlers: { + [endpoint.method]: endpoint.fn, + }, + }) + } + } - useDefaultHeaders = () => { - this.engine.app.use((req, res, next) => { - Object.keys(this.headers).forEach((key) => { - res.setHeader(key, this.headers[key]) - }) + // register http & ws routes + this.engine = await registerHttpRoutes( + this.params.routesPath, + this.engine, + this, + ) + this.engine = await registerWebsocketsEvents( + this.params.wsRoutesPath, + this.engine, + ) - next() - }) - } + // register base endpoints if enabled + if (!this.params.disableBaseEndpoints) { + await registerBaseEndpoints(this) + } - useDefaultMiddlewares = async () => { - const middlewares = await this.resolveMiddlewares([ - ...this.params.useMiddlewares, - ...this.useMiddlewares ?? [], - ...defaults.useMiddlewares, - ]) + // use main router + await this.engine.app.use(this.engine.router) - middlewares.forEach((middleware) => { - this.engine.app.use(middleware) - }) - } + // if is a linebridge service then initialize IPC Channels + if (process.env.lb_service) { + await this.initializeIpc() + } - register = { - http: (endpoint, ..._middlewares) => { - // check and fix method - endpoint.method = endpoint.method?.toLowerCase() ?? "get" + // try to execute beforeInitialize hook. + if (typeof this.afterInitialize === "function") { + await this.afterInitialize() + } - if (defaults.fixed_http_methods[endpoint.method]) { - endpoint.method = defaults.fixed_http_methods[endpoint.method] - } + // listen + await this.engine.listen(engineParams) - // check if method is supported - if (typeof this.engine.router[endpoint.method] !== "function") { - throw new Error(`Method [${endpoint.method}] is not supported!`) - } + // calculate elapsed time on ms, to fixed 2 + const elapsedHrTime = process.hrtime(startHrTime) + const elapsedTimeInMs = elapsedHrTime[0] * 1e3 + elapsedHrTime[1] / 1e6 - // grab the middlewares - let middlewares = [..._middlewares] + console.info( + `šŸ›° Server ready!\n\t - ${this.params.http_protocol}://${this.params.listen_ip}:${this.params.listen_port} \n\t - Tooks ${elapsedTimeInMs.toFixed(2)}ms`, + ) + } - if (endpoint.middlewares) { - if (!Array.isArray(endpoint.middlewares)) { - endpoint.middlewares = [endpoint.middlewares] - } + initializeIpc = async () => { + console.info("šŸš„ Starting IPC client") - middlewares = [...middlewares, ...this.resolveMiddlewares(endpoint.middlewares)] - } + this.ipc = global.ipc = new IPCClient(this, process) + } - this.engine.router.map[endpoint.route] = { - method: endpoint.method, - path: endpoint.route, - } + useDefaultHeaders = () => { + this.engine.app.use((req, res, next) => { + Object.keys(this.headers).forEach((key) => { + res.setHeader(key, this.headers[key]) + }) - // register endpoint to http interface router - this.engine.router[endpoint.method](endpoint.route, ...middlewares, endpoint.fn) - }, - } + next() + }) + } - resolveMiddlewares = (requestedMiddlewares) => { - const middlewares = { - ...this.middlewares, - ...defaults.middlewares, - } + useDefaultMiddlewares = async () => { + const middlewares = await this.resolveMiddlewares([ + ...this.params.useMiddlewares, + ...(this.useMiddlewares ?? []), + ...defaults.useMiddlewares, + ]) - if (typeof requestedMiddlewares === "string") { - requestedMiddlewares = [requestedMiddlewares] - } + middlewares.forEach((middleware) => { + this.engine.app.use(middleware) + }) + } - const execs = [] + register = { + http: (endpoint, ..._middlewares) => { + // check and fix method + endpoint.method = endpoint.method?.toLowerCase() ?? "get" - requestedMiddlewares.forEach((middlewareKey) => { - if (typeof middlewareKey === "string") { - if (typeof middlewares[middlewareKey] !== "function") { - throw new Error(`Middleware ${middlewareKey} not found!`) - } + if (defaults.fixed_http_methods[endpoint.method]) { + endpoint.method = defaults.fixed_http_methods[endpoint.method] + } - execs.push(middlewares[middlewareKey]) - } + // check if method is supported + if (typeof this.engine.router[endpoint.method] !== "function") { + throw new Error(`Method [${endpoint.method}] is not supported!`) + } - if (typeof middlewareKey === "function") { - execs.push(middlewareKey) - } - }) + // grab the middlewares + let middlewares = [..._middlewares] - return execs - } + if (endpoint.middlewares) { + if (!Array.isArray(endpoint.middlewares)) { + endpoint.middlewares = [endpoint.middlewares] + } + + middlewares = [ + ...middlewares, + ...this.resolveMiddlewares(endpoint.middlewares), + ] + } + + this.engine.router.map[endpoint.route] = { + method: endpoint.method, + path: endpoint.route, + } + + // register endpoint to http interface router + this.engine.router[endpoint.method]( + endpoint.route, + ...middlewares, + endpoint.fn, + ) + }, + } + + resolveMiddlewares = (requestedMiddlewares) => { + const middlewares = { + ...this.middlewares, + ...defaults.middlewares, + } + + if (typeof requestedMiddlewares === "string") { + requestedMiddlewares = [requestedMiddlewares] + } + + const execs = [] + + requestedMiddlewares.forEach((middlewareKey) => { + if (typeof middlewareKey === "string") { + if (typeof middlewares[middlewareKey] !== "function") { + throw new Error(`Middleware ${middlewareKey} not found!`) + } + + execs.push(middlewares[middlewareKey]) + } + + if (typeof middlewareKey === "function") { + execs.push(middlewareKey) + } + }) + + return execs + } } -module.exports = Server \ No newline at end of file +module.exports = Server