diff --git a/.DS_Store b/.DS_Store old mode 100644 new mode 100755 diff --git a/.experimental b/.experimental old mode 100644 new mode 100755 diff --git a/.npmignore b/.npmignore old mode 100644 new mode 100755 diff --git a/bootstrap.js b/bootstrap.js old mode 100644 new mode 100755 index 2e5edff..73d6589 --- a/bootstrap.js +++ b/bootstrap.js @@ -67,7 +67,7 @@ global.toBoolean = (value) => { } async function injectEnvFromInfisical() { - const envMode = global.FORCE_ENV ?? global.isProduction ? "prod" : "dev" + const envMode = "dev" console.log(`🔑 Injecting env variables from INFISICAL in [${envMode}] mode...`) diff --git a/example_server/package.json b/example_server/package.json deleted file mode 100644 index a9cce1c..0000000 --- a/example_server/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "example_server", - "version": "1.0.0", - "main": "index.js", - "license": "MIT", - "scripts": { - "start": "hermes-node ./src/index.js" - } -} \ No newline at end of file diff --git a/example_server/src/controllers/TestController/endpoints/failEndpoint.js b/example_server/src/controllers/TestController/endpoints/failEndpoint.js deleted file mode 100644 index 536e618..0000000 --- a/example_server/src/controllers/TestController/endpoints/failEndpoint.js +++ /dev/null @@ -1,17 +0,0 @@ -export default { - method: "get", - route: "/fail", - fn: async (req, res) => { - throw new Error("Testing catch handler") - - return res.json({ - message: "This is not supposed to be here!" - }) - }, - onCatch: async (err, req, res) => { - return res.json({ - message: "Catch handler works!", - error: err.message, - }) - } -} \ No newline at end of file diff --git a/example_server/src/controllers/TestController/endpoints/indexEndpoint.js b/example_server/src/controllers/TestController/endpoints/indexEndpoint.js deleted file mode 100644 index f59f0a6..0000000 --- a/example_server/src/controllers/TestController/endpoints/indexEndpoint.js +++ /dev/null @@ -1,9 +0,0 @@ -export default { - method: "get", - route: "/", - fn: async (req, res) => { - return res.json({ - message: "Hello world!" - }) - } -} \ No newline at end of file diff --git a/example_server/src/controllers/TestController/endpoints/middlewaresTest.js b/example_server/src/controllers/TestController/endpoints/middlewaresTest.js deleted file mode 100644 index 6a2293a..0000000 --- a/example_server/src/controllers/TestController/endpoints/middlewaresTest.js +++ /dev/null @@ -1,13 +0,0 @@ -export default { - method: "get", - route: "/middlewaresTest", - middlewares: ["test", (req, res, next) => { - console.log("Hello from inline middleware 2") - next() - }], - fn: async (req, res) => { - return res.json({ - message: "Hello world! Look at the console for the middlewares!" - }) - } -} \ No newline at end of file diff --git a/example_server/src/controllers/TestController/endpoints/objectEndpoint.js b/example_server/src/controllers/TestController/endpoints/objectEndpoint.js deleted file mode 100644 index 3328691..0000000 --- a/example_server/src/controllers/TestController/endpoints/objectEndpoint.js +++ /dev/null @@ -1,9 +0,0 @@ -export default { - method: "get", - route: "/withoutClass", - fn: async (req, res) => { - return res.json({ - message: "Im an object endpoint", - }) - } -} \ No newline at end of file diff --git a/example_server/src/controllers/TestController/endpoints/testOneEndpoint.js b/example_server/src/controllers/TestController/endpoints/testOneEndpoint.js deleted file mode 100644 index e65f799..0000000 --- a/example_server/src/controllers/TestController/endpoints/testOneEndpoint.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Endpoint } from "../../../../../src/server" - -export default class TestOneEndpoint extends Endpoint { - static method = "get" - - static route = "/one" - - fn = async (req, res) => { - return res.json({ - message: "Hello world! Im using Endpoint class!" - }) - } -} \ No newline at end of file diff --git a/example_server/src/controllers/TestController/index.js b/example_server/src/controllers/TestController/index.js deleted file mode 100644 index d9c36db..0000000 --- a/example_server/src/controllers/TestController/index.js +++ /dev/null @@ -1,8 +0,0 @@ -import { Controller } from "../../../../src/server" -import generateEndpointsFromDir from "../../../../src/server/lib/generateEndpointsFromDir" - -export default class TestController extends Controller { - static useRoute = "/test" - - httpEndpoints = generateEndpointsFromDir(__dirname + "/endpoints") -} \ No newline at end of file diff --git a/example_server/src/controllers/index.js b/example_server/src/controllers/index.js deleted file mode 100644 index 0c07002..0000000 --- a/example_server/src/controllers/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default as TestController } from "./TestController" \ No newline at end of file diff --git a/example_server/src/index.js b/example_server/src/index.js deleted file mode 100644 index 262a15f..0000000 --- a/example_server/src/index.js +++ /dev/null @@ -1,13 +0,0 @@ -import { registerBaseAliases, Server } from "../../src/server" - -registerBaseAliases() - -const server = new Server({ - name: "example_server", - listen_port: 3011, -}, - require("@controllers"), - require("@middlewares"), -) - -server.initialize() \ No newline at end of file diff --git a/example_server/src/middlewares/index.js b/example_server/src/middlewares/index.js deleted file mode 100644 index 37a9019..0000000 --- a/example_server/src/middlewares/index.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - "test": (req, res, next) => { - console.log("Hello loaded middleware 1") - next() - } -} \ No newline at end of file diff --git a/package.json b/package.json index 08b4c66..9d72bcc 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "linebridge", - "version": "0.16.0", + "version": "0.18.0", "description": "A simple, fast, and powerful REST API interface library", "author": "RageStudio", "main": "./dist/client/index.js", @@ -13,12 +13,14 @@ "access": "public" }, "files": [ + "src/**/**", "dist/**/**", "./package.json" ], "license": "MIT", "dependencies": { "@foxify/events": "^2.1.0", + "@gullerya/object-observer": "^6.1.3", "@socket.io/cluster-adapter": "^0.2.2", "@socket.io/redis-adapter": "^8.2.1", "@socket.io/redis-emitter": "^5.1.0", diff --git a/src/client/bridge.js b/src/client/bridge.js old mode 100644 new mode 100755 diff --git a/src/client/classes/WSInterface/index.js b/src/client/classes/WSInterface/index.js old mode 100644 new mode 100755 diff --git a/src/client/classes/index.js b/src/client/classes/index.js old mode 100644 new mode 100755 diff --git a/src/client/controller.js b/src/client/controller.js old mode 100644 new mode 100755 diff --git a/src/client/lib/generateHTTPRequestDispatcher/index.js b/src/client/lib/generateHTTPRequestDispatcher/index.js old mode 100644 new mode 100755 diff --git a/src/client/lib/generateWSRequestDispatcher/index.js b/src/client/lib/generateWSRequestDispatcher/index.js old mode 100644 new mode 100755 diff --git a/src/client/lib/index.js b/src/client/lib/index.js old mode 100644 new mode 100755 diff --git a/src/lib/event_emitter/index.js b/src/lib/event_emitter/index.js old mode 100644 new mode 100755 diff --git a/src/server/baseEndpoints/main.js b/src/server/baseEndpoints/main.js new file mode 100644 index 0000000..74e0032 --- /dev/null +++ b/src/server/baseEndpoints/main.js @@ -0,0 +1,23 @@ +import path from "node:path" + +import Endpoint from "../classes/endpoint" +import defaults from "../defaults" + +const projectPkg = require(path.resolve(process.cwd(), "package.json")) + +export default class MainEndpoint extends Endpoint { + route = "/" + + get = async (req, res) => { + const { params } = this.ctx + + return { + name: params.name ?? "unknown", + version: projectPkg.version ?? "unknown", + engine: params.useEngine ?? "unknown", + request_time: new Date().getTime(), + lb_version: defaults.version ?? "unknown", + experimental: defaults.isExperimental.toString() ?? "unknown", + } + } +} \ No newline at end of file diff --git a/src/server/baseEndpoints/map.js b/src/server/baseEndpoints/map.js new file mode 100644 index 0000000..85cb96e --- /dev/null +++ b/src/server/baseEndpoints/map.js @@ -0,0 +1,24 @@ +import Endpoint from "../classes/endpoint" + +export default class MainEndpoint extends Endpoint { + route = "/_map" + + get = async (req, res) => { + const httpMap = Object.entries(this.ctx.engine.router.map).reduce((acc, [route, { method, path }]) => { + if (!acc[method]) { + acc[method] = [] + } + + acc[method].push({ + route: path + }) + + return acc + }, {}) + + return res.json({ + http: httpMap, + websocket: [] + }) + } +} \ No newline at end of file diff --git a/src/server/classes/IPCClient/index.js b/src/server/classes/IPCClient/index.js new file mode 100644 index 0000000..338ff8e --- /dev/null +++ b/src/server/classes/IPCClient/index.js @@ -0,0 +1,155 @@ +import { EventEmitter } from "@foxify/events" + +export default class IPCClient { + constructor(self, _process) { + this.self = self + this.process = _process + + this.process.on("message", (msg) => { + if (typeof msg !== "object") { + // not an IPC message, ignore + return false + } + + const { event, payload } = msg + + if (!event || !event.startsWith("ipc:")) { + return false + } + + if (event.startsWith("ipc:exec")) { + return this.handleExecution(payload) + } + + if (event.startsWith("ipc:akn")) { + return this.handleAcknowledgement(payload) + } + }) + } + + eventBus = new EventEmitter() + + handleExecution = async (payload) => { + let { id, command, args, from } = payload + + let fn = this.self.ipcEvents[command] + + if (!fn) { + this.process.send({ + event: `ipc:akn:${id}`, + payload: { + target: from, + from: this.self.constructor.refName, + + id: id, + error: `IPC: Command [${command}] not found`, + } + }) + + return false + } + + try { + let result = await fn(this.self.contexts, ...args) + + this.process.send({ + event: `ipc:akn:${id}`, + payload: { + target: from, + from: this.self.constructor.refName, + + id: id, + result: result, + } + }) + } catch (error) { + this.process.send({ + event: `ipc:akn:${id}`, + payload: { + target: from, + from: this.self.constructor.refName, + + id: id, + error: error.message, + } + }) + } + } + + handleAcknowledgement = async (payload) => { + let { id, result, error } = payload + + this.eventBus.emit(`ipc:akn:${id}`, { + id: id, + result: result, + error: error, + }) + } + + // call a command on a remote service, and waits to get a response from akn (async) + call = async (to_service_id, command, ...args) => { + const remote_call_id = Date.now() + + const response = await new Promise((resolve, reject) => { + try { + + this.process.send({ + event: "ipc:exec", + payload: { + target: to_service_id, + from: this.self.constructor.refName, + + id: remote_call_id, + command, + args, + } + }) + + this.eventBus.once(`ipc:akn:${remote_call_id}`, resolve) + } catch (error) { + console.error(error) + + reject(error) + } + }).catch((error) => { + return { + error: error + } + }) + + if (response.error) { + throw new OperationError(500, response.error) + } + + return response.result + } + + // call a command on a remote service, but return it immediately + invoke = async (to_service_id, command, ...args) => { + const remote_call_id = Date.now() + + try { + this.process.send({ + event: "ipc:exec", + payload: { + target: to_service_id, + from: this.self.constructor.refName, + + id: remote_call_id, + command, + args, + } + }) + + return { + id: remote_call_id + } + } catch (error) { + console.error(error) + + return { + error: error + } + } + } +} \ No newline at end of file diff --git a/src/server/classes/IPCRouter/index.js b/src/server/classes/IPCRouter/index.js new file mode 100644 index 0000000..7da8aea --- /dev/null +++ b/src/server/classes/IPCRouter/index.js @@ -0,0 +1,54 @@ +export default class IPCRouter { + processes = [] + + register = (service) => { + service.instance.on("message", (msg) => { + if (typeof msg !== "object") { + // not an IPC message, ignore + return false + } + + const { event, payload } = msg + + if (!event || !event.startsWith("ipc:")) { + // not an IPC message, ignore + return false + } + + const { target } = payload + + if (!target) { + return false + } + + if (event.startsWith("ipc:")) { + return this.route(event, payload) + } + }) + + this.processes.push(service) + } + + unregister = (service) => { + this.processes = this.processes.filter((_process) => _process.id !== service.id) + } + + route = (event, payload) => { + const { target, from } = payload + + // first search service + let targetService = this.processes.find((_process) => _process.id === target) + + if (!targetService) { + // TODO: respond with error + console.error(`[IPC:ROUTER] Service [${destinationId}] not found`) + + return false + } + + targetService.instance.send({ + event: event, + payload: payload + }) + } +} \ No newline at end of file diff --git a/src/server/classes/controller/index.js b/src/server/classes/controller/index.js old mode 100644 new mode 100755 diff --git a/src/server/classes/endpoint/index.js b/src/server/classes/endpoint/index.js old mode 100644 new mode 100755 index 722a6f6..8d2899b --- a/src/server/classes/endpoint/index.js +++ b/src/server/classes/endpoint/index.js @@ -1,3 +1,74 @@ -module.exports = class Endpoint { +export default class Endpoint { + constructor(ctx, params = {}) { + this.ctx = ctx + this.params = params + this.route = this.constructor.route ?? this.params.route + this.enabled = this.constructor.enabled ?? this.params.enabled ?? true + + this.middlewares = [ + ...this.middlewares ?? [], + ...this.params.middlewares ?? [], + ] + + if (this.params.handlers) { + for (const method of this.ctx.valid_http_methods) { + if (typeof this.params.handlers[method] === "function") { + this[method] = this.params.handlers[method] + } + } + } + + this.selfRegister() + + return this + } + + createHandler(fn) { + return async (req, res) => { + try { + const result = await fn(req, res) + + if (result) { + return res.json(result) + } + } catch (error) { + if (error instanceof OperationError) { + return res.status(error.code).json({ + "error": error.message + }) + } + + console.error({ + message: "Unhandled route error:", + description: error.stack, + }) + + return res.status(500).json({ + "error": error.message + }) + } + } + } + + selfRegister = async () => { + const validMethods = this.ctx.valid_http_methods + + for await (const method of validMethods) { + const methodHandler = this[method] + + if (typeof methodHandler !== "undefined") { + const fn = this.createHandler(this[method].fn ?? this[method]) + + this.ctx.register.http( + { + method, + route: this.route, + middlewares: this.middlewares, + fn: fn, + }, + ) + } + } + } } \ No newline at end of file diff --git a/src/server/classes/index.js b/src/server/classes/index.js old mode 100644 new mode 100755 diff --git a/src/server/classes/operation_error/index.js b/src/server/classes/operation_error/index.js new file mode 100644 index 0000000..ec8ec7c --- /dev/null +++ b/src/server/classes/operation_error/index.js @@ -0,0 +1,6 @@ +export default class OperationError { + constructor(code, message) { + this.code = code ?? 500 + this.message = message + } +} \ No newline at end of file diff --git a/src/server/classes/rtengine/index.js b/src/server/classes/rtengine/index.js old mode 100644 new mode 100755 diff --git a/src/server/defaults.js b/src/server/defaults.js new file mode 100644 index 0000000..b858392 --- /dev/null +++ b/src/server/defaults.js @@ -0,0 +1,65 @@ +const path = require("path") +const fs = require("fs") +const os = require("os") +const packageJSON = require(path.resolve(module.path, "../../package.json")) + +function getHostAddress() { + const interfaces = os.networkInterfaces() + + for (const key in interfaces) { + const iface = interfaces[key] + + for (let index = 0; index < iface.length; index++) { + const alias = iface[index] + + if (alias.family === "IPv4" && alias.address !== "127.0.0.1" && !alias.internal) { + return alias.address + } + } + } + + return "0.0.0.0" +} + +export default { + isExperimental: fs.existsSync(path.resolve(module.path, "../../.experimental")), + version: packageJSON.version, + localhost_address: getHostAddress() ?? "localhost", + params: { + urlencoded: true, + engine: "express", + http_protocol: "http", + ws_protocol: "ws", + }, + headers: { + "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization", + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, OPTIONS, PUT, PATCH, DELETE, DEL", + "Access-Control-Allow-Credentials": "true", + }, + middlewares: { + cors: require("./middlewares/cors").default, + logs: require("./middlewares/logger").default, + }, + useMiddlewares: [ + "cors", + "logs", + ], + controllers: [], + fixed_http_methods: { + "del": "delete", + }, + valid_http_methods: [ + "get", + "post", + "put", + "patch", + "del", + "delete", + "trace", + "head", + "any", + "options", + "ws", + ], +} \ No newline at end of file diff --git a/src/server/engines/express/index.js b/src/server/engines/express/index.js new file mode 100644 index 0000000..df9ef45 --- /dev/null +++ b/src/server/engines/express/index.js @@ -0,0 +1,35 @@ +import { createServer } from "node:http" +import express from "express" +import socketio from "socket.io" +import rtengine from "../../classes/rtengine" + +export default class Engine { + constructor(params) { + this.params = params + } + + http = null + app = null + io = null + ws = null + + router = express.Router() + + init = async (params) => { + this.app = express() + this.http = createServer(this.app) + this.io = new socketio.Server(this.http) + this.ws = new rtengine({ + ...params, + io: this.io, + http: false, + }) + + this.app.use(express.json()) + this.app.use(express.urlencoded({ extended: true })) + } + + listen = async () => { + await this.http.listen(this.params.listen_port) + } +} \ No newline at end of file diff --git a/src/server/engines/hyper-express/index.js b/src/server/engines/hyper-express/index.js new file mode 100644 index 0000000..0df9063 --- /dev/null +++ b/src/server/engines/hyper-express/index.js @@ -0,0 +1,30 @@ +import he from "hyper-express" + +export default class Engine { + constructor(params) { + this.params = params + } + + app = new he.Server() + + router = new he.Router() + + init = async (params) => { + // register 404 + await this.router.any("*", (req, res) => { + return res.status(404).json({ + code: 404, + message: "Not found" + }) + }) + + // register body parser + await this.app.use(async (req, res, next) => { + req.body = await req.urlencoded() + }) + } + + listen = async () => { + await this.app.listen(this.params.listen_port) + } +} \ No newline at end of file diff --git a/src/server/engines/worker/index.js b/src/server/engines/worker/index.js new file mode 100644 index 0000000..a06600f --- /dev/null +++ b/src/server/engines/worker/index.js @@ -0,0 +1,126 @@ +import { EventEmitter } from "@foxify/events" + +class WorkerEngineRouter { + routes = [] + + get = (path, ...execs) => { + + } + + post = (path, ...execs) => { + + } + + delete = (path, ...execs) => { + + } + + put = (path, ...execs) => { + + } + + patch = (path, ...execs) => { + + } + + head = (path, ...execs) => { + + } + + options = (path, ...execs) => { + + } + + any = (path, ...execs) => { + + } + + use = (path, ...execs) => { + + } +} + +class WorkerEngine { + static ipcPrefix = "rail:" + + selfId = process.env.lb_service.id + + router = new WorkerEngineRouter() + + eventBus = new EventEmitter() + + perExecTail = [] + + initialize = async () => { + console.error(`[WorkerEngine] Worker engine its not implemented yet...`) + + process.on("message", this.handleIPCMessage) + } + + listen = async () => { + console.log(`Sending Rail Register`) + + process.send({ + type: "rail:register", + data: { + id: process.env.lb_service.id, + pid: process.pid, + routes: this.router.routes, + } + }) + } + + handleIPCMessage = async (msg) => { + if (typeof msg !== "object") { + // ignore, its not for us + return false + } + + if (!msg.event || !msg.event.startsWith(WorkerEngine.ipcPrefix)) { + return false + } + + const { event, payload } = msg + + switch (event) { + case "rail:request": { + const { req } = payload + + break + } + case "rail:response": { + + } + } + } + + use = (fn) => { + if (fn instanceof WorkerEngineRouter) { + this.router = fn + return + } + + if (fn instanceof Function) { + this.perExecTail.push(fn) + return + } + } +} + +export default class Engine { + constructor(params) { + this.params = params + } + + app = new WorkerEngine() + + router = new WorkerEngineRouter() + + init = async () => { + await this.app.initialize() + } + + listen = async () => { + await this.app.listen() + } +} \ No newline at end of file diff --git a/src/server/index.js b/src/server/index.js index 06ef078..78880f8 100755 --- a/src/server/index.js +++ b/src/server/index.js @@ -1,115 +1,7 @@ -const path = require("path") -const fs = require("fs") -const net = require("corenode/net") - -const experimentalFlag = path.resolve(module.path, "../../.experimental") -const packageJSON = require(path.resolve(module.path, "../../package.json")) -const moduleAlias = require("module-alias") - -// set globals variables -global.LINEBRIDGE_SERVER_EXPERIMENTAL = fs.existsSync(experimentalFlag) -global.LINEBRIDGE_SERVER_VERSION = packageJSON.version - -global.LOCALHOST_ADDRESS = net.ip.getHostAddress() ?? "localhost" - -global.FIXED_HTTP_METHODS = { - "del": "delete" -} - -global.VALID_HTTP_METHODS = [ - "get", - "post", - "put", - "patch", - "del", - "delete", - "trace", - "head", - "any", - "options", - "ws" -] - -global.DEFAULT_HEADERS = { - "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization", - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET, POST, OPTIONS, PUT, PATCH, DELETE, DEL", - "Access-Control-Allow-Credentials": "true", -} - -global.DEFAULT_SERVER_PARAMS = { - urlencoded: true, - engine: "express", - http_protocol: "http", - ws_protocol: "ws", -} - -global.DEFAULT_MIDDLEWARES = [ - require("cors")({ - "origin": "*", - "methods": DEFAULT_HEADERS["Access-Control-Allow-Methods"], - "preflightContinue": false, - "optionsSuccessStatus": 204 - }), - require("morgan")(process.env.MORGAN_FORMAT ?? ":method :status :url - :response-time ms") -] - -// patches -const { Buffer } = require("buffer") - -global.b64Decode = (data) => { - return Buffer.from(data, "base64").toString("utf-8") -} -global.b64Encode = (data) => { - return Buffer.from(data, "utf-8").toString("base64") -} - -Array.prototype.updateFromObjectKeys = function (obj) { - this.forEach((value, index) => { - if (obj[value] !== undefined) { - this[index] = obj[value] - } - }) - - return this -} - -global.toBoolean = (value) => { - if (typeof value === "boolean") { - return value - } - - if (typeof value === "string") { - return value.toLowerCase() === "true" - } - - return false -} - -function registerBaseAliases(fromPath, customAliases = {}) { - if (typeof fromPath === "undefined") { - if (module.parent.filename.includes("dist")) { - fromPath = path.resolve(process.cwd(), "dist") - } else { - fromPath = path.resolve(process.cwd(), "src") - } - } - - moduleAlias.addAliases({ - ...customAliases, - "@controllers": path.resolve(fromPath, "controllers"), - "@middlewares": path.resolve(fromPath, "middlewares"), - "@models": path.resolve(fromPath, "models"), - "@classes": path.resolve(fromPath, "classes"), - "@lib": path.resolve(fromPath, "lib"), - "@utils": path.resolve(fromPath, "utils"), - }) -} - module.exports = { - registerBaseAliases: registerBaseAliases, Server: require("./server.js"), Controller: require("./classes/controller"), Endpoint: require("./classes/endpoint"), + registerBaseAliases: require("./registerAliases"), version: require("../../package.json").version, } diff --git a/src/server/lib/generateController/index.js b/src/server/lib/generateController/index.js old mode 100644 new mode 100755 diff --git a/src/server/lib/generateEndpointsFromDir/index.js b/src/server/lib/generateEndpointsFromDir/index.js old mode 100644 new mode 100755 diff --git a/src/server/lib/internalConsole/index.js b/src/server/lib/internalConsole/index.js old mode 100644 new mode 100755 diff --git a/src/server/lib/loadEndpointsFromDir/index.js b/src/server/lib/loadEndpointsFromDir/index.js old mode 100644 new mode 100755 diff --git a/src/server/lib/redis_map/index.js b/src/server/lib/redis_map/index.js old mode 100644 new mode 100755 diff --git a/src/server/middlewares/cors/index.js b/src/server/middlewares/cors/index.js new file mode 100755 index 0000000..7ebac0f --- /dev/null +++ b/src/server/middlewares/cors/index.js @@ -0,0 +1,8 @@ +import cors from "cors" + +export default cors({ + origin: "*", + methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "CONNECT", "TRACE"], + preflightContinue: false, + optionsSuccessStatus: 204, +}) \ No newline at end of file diff --git a/src/server/middlewares/logger/index.js b/src/server/middlewares/logger/index.js new file mode 100755 index 0000000..1d398fc --- /dev/null +++ b/src/server/middlewares/logger/index.js @@ -0,0 +1,19 @@ +export default (req, res, next) => { + const startHrTime = process.hrtime() + + res.on("finish", () => { + const elapsedHrTime = process.hrtime(startHrTime) + const elapsedTimeInMs = elapsedHrTime[0] * 1000 + elapsedHrTime[1] / 1e6 + + res._responseTimeMs = elapsedTimeInMs + + // cut req.url if is too long + if (req.url.length > 100) { + req.url = req.url.substring(0, 100) + "..." + } + + console.log(`${req.method} ${res._status_code ?? res.statusCode ?? 200} ${req.url} ${elapsedTimeInMs}ms`) + }) + + next() +} \ No newline at end of file diff --git a/src/server/patches.js b/src/server/patches.js new file mode 100644 index 0000000..953e4e7 --- /dev/null +++ b/src/server/patches.js @@ -0,0 +1,3 @@ +import OperationError from "./classes/operation_error" + +global.OperationError = OperationError \ No newline at end of file diff --git a/src/server/registerAliases.js b/src/server/registerAliases.js new file mode 100644 index 0000000..6097c4e --- /dev/null +++ b/src/server/registerAliases.js @@ -0,0 +1,21 @@ +const moduleAlias = require("module-alias") + +export default (fromPath, customAliases = {}) => { + if (typeof fromPath === "undefined") { + if (module.parent.filename.includes("dist")) { + fromPath = path.resolve(process.cwd(), "dist") + } else { + fromPath = path.resolve(process.cwd(), "src") + } + } + + moduleAlias.addAliases({ + ...customAliases, + "@controllers": path.resolve(fromPath, "controllers"), + "@middlewares": path.resolve(fromPath, "middlewares"), + "@models": path.resolve(fromPath, "models"), + "@classes": path.resolve(fromPath, "classes"), + "@lib": path.resolve(fromPath, "lib"), + "@utils": path.resolve(fromPath, "utils"), + }) +} \ No newline at end of file diff --git a/src/server/server.js b/src/server/server.js old mode 100644 new mode 100755 index 338d318..c1f3f2f --- a/src/server/server.js +++ b/src/server/server.js @@ -1,105 +1,82 @@ -const fs = require("fs") -const path = require("path") -const rtengine = require("./classes/rtengine").default +import("./patches") -const { EventEmitter } = require("@foxify/events") +import fs from "node:fs" +import path from "node:path" +import { EventEmitter } from "@foxify/events" -const tokenizer = require("corenode/libs/tokenizer") -const { serverManifest, internalConsole } = require("./lib") +import Endpoint from "./classes/endpoint" -const pkgjson = require(path.resolve(process.cwd(), "package.json")) +import defaults from "./defaults" -const Engines = { - "hyper-express": () => { - console.warn("HyperExpress is not fully supported yet!") +import IPCClient from "./classes/IPCClient" - const engine = require("hyper-express") +async function loadEngine(engine) { + const enginesPath = path.resolve(__dirname, "engines") - return new engine.Server() - }, - "express": (params) => { - const { createServer } = require("node:http") - const express = require("express") - const socketio = require("socket.io") + const selectedEnginePath = path.resolve(enginesPath, engine) - const app = express() - const http = createServer(app) + if (!fs.existsSync(selectedEnginePath)) { + throw new Error(`Engine ${engine} not found!`) + } - const io = new socketio.Server(http) - const ws = new rtengine({ - ...params, - io: io, - http: false, - }) - - app.use(express.json()) - app.use(express.urlencoded({ extended: true })) - - return { - ws, - http, - app, - } - }, + return require(selectedEnginePath).default } class Server { - eventBus = new EventEmitter() - constructor(params = {}, controllers = {}, middlewares = {}, headers = {}) { - if (global.LINEBRIDGE_SERVER_EXPERIMENTAL) { - console.warn("🚧🚧🚧 This version of Linebridge is experimental!") + this.isExperimental = defaults.isExperimental ?? false + + if (this.isExperimental) { + console.warn("🚧 This version of Linebridge is experimental! 🚧") } - // register aliases this.params = { - ...global.DEFAULT_SERVER_PARAMS, - ...params, + ...defaults.params, + ...params.default ?? params, } this.controllers = { - ...controllers + ...controllers.default ?? controllers, } + this.middlewares = { ...middlewares.default ?? middlewares, } + this.headers = { - ...global.DEFAULT_SERVER_HEADERS, - ...headers + ...defaults.headers, + ...headers.default ?? headers, } - this.endpoints_map = {} + this.valid_http_methods = defaults.valid_http_methods // 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 ?? "express" this.params.listen_ip = this.params.listen_ip ?? "0.0.0.0" this.params.listen_port = 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}://${global.LOCALHOST_ADDRESS}:${this.params.listen_port}` - - this.engine = null - - this.InternalConsole = new internalConsole({ - server_name: this.params.name - }) - - this.initializeManifest() - - // handle silent mode - global.consoleSilent = this.params.silent - - if (global.consoleSilent) { - // find morgan middleware and remove it - const morganMiddleware = global.DEFAULT_MIDDLEWARES.find(middleware => middleware.name === "logger") - - if (morganMiddleware) { - global.DEFAULT_MIDDLEWARES.splice(global.DEFAULT_MIDDLEWARES.indexOf(morganMiddleware), 1) - } - } + this.params.http_address = `${this.params.http_protocol}://${defaults.localhost_address}:${this.params.listen_port}` + this.params.routesPath = this.constructor.routesPath ?? this.params.routesPath return this } + engine = null + + events = null + + ipc = null + + ipcEvents = null + + eventBus = new EventEmitter() + initialize = async () => { + const startHrTime = process.hrtime() + + // register events if (this.events) { if (this.events.default) { this.events = this.events.default @@ -111,73 +88,80 @@ class Server { } // initialize engine - this.engine = global.engine = Engines[this.params.engine]({ + this.engine = await loadEngine(this.params.useEngine) + + this.engine = new this.engine({ ...this.params, - handleAuth: this.handleWsAuth, - requireAuth: this.constructor.requireWSAuth, + handleAuth: this.handleHttpAuth, + requireAuth: this.constructor.requireHttpAuth, }) - // before initialize headers, middlewares and controllers, try to execute onInitialize hook. + if (typeof this.engine.init === "function") { + await this.engine.init(this.params) + } + + // create a router map + if (typeof this.engine.router.map !== "object") { + this.engine.router.map = [] + } + + // try to execute onInitialize hook if (typeof this.onInitialize === "function") { await this.onInitialize() } // set server defined headers - this.initializeHeaders() + this.useDefaultHeaders() // set server defined middlewares - this.initializeRequiredMiddlewares() + this.useDefaultMiddlewares() // register controllers await this.initializeControllers() + // register routes + await this.initializeRoutes() + // register main index endpoint `/` await this.registerBaseEndpoints() - if (typeof this.engine.ws?.initialize !== "function") { - this.InternalConsole.warn("❌ WebSocket is not supported!") - } else { + // use main router + await this.engine.app.use(this.engine.router) + + // initialize websocket init hook if needed + if (typeof this.engine.ws?.initialize == "function") { await this.engine.ws.initialize({ redisInstance: this.redis }) } - if (typeof this.beforeInitialize === "function") { - await this.beforeInitialize() + // if is a linebridge service then initialize IPC Channels + if (process.env.lb_service) { + await this.initializeIpc() } - await this.engine.http.listen(this.params.listen_port) + // try to execute beforeInitialize hook. + if (typeof this.afterInitialize === "function") { + await this.afterInitialize() + } - this.InternalConsole.info(`✅ Server ready on => ${this.params.listen_ip}:${this.params.listen_port}`) + // listen + await this.engine.listen() + + // calculate elapsed time on ms, to fixed 2 + const elapsedHrTime = process.hrtime(startHrTime) + const elapsedTimeInMs = elapsedHrTime[0] * 1e3 + elapsedHrTime[1] / 1e6 + + 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`) } - initializeManifest = () => { - // check if origin.server exists - if (!fs.existsSync(serverManifest.filepath)) { - serverManifest.create() - } + initializeIpc = async () => { + console.info("🚄 Starting IPC client") - // 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") { - this.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") { - this.InternalConsole.warn("Missing server token!") - serverManifest.create() - } - - this.usid = tokenizer.generateUSID() - this.server_token = serverManifest.get("server_token") - - serverManifest.write({ last_start: Date.now() }) + this.ipc = global.ipc = new IPCClient(this, process) } - initializeHeaders = () => { + useDefaultHeaders = () => { this.engine.app.use((req, res, next) => { Object.keys(this.headers).forEach((key) => { res.setHeader(key, this.headers[key]) @@ -187,13 +171,14 @@ class Server { }) } - initializeRequiredMiddlewares = () => { - const useMiddlewares = [...this.params.useMiddlewares ?? [], ...global.DEFAULT_MIDDLEWARES] + useDefaultMiddlewares = async () => { + const middlewares = await this.resolveMiddlewares([ + ...this.params.useMiddlewares, + ...defaults.useMiddlewares, + ]) - useMiddlewares.forEach((middleware) => { - if (typeof middleware === "function") { - this.engine.app.use(middleware) - } + middlewares.forEach((middleware) => { + this.engine.app.use(middleware) }) } @@ -206,7 +191,7 @@ class Server { } if (controller.disabled) { - this.InternalConsole.warn(`⏩ Controller [${controller.name}] is disabled! Initialization skipped...`) + console.warn(`⏩ Controller [${controller.name}] is disabled! Initialization skipped...`) continue } @@ -218,115 +203,190 @@ class Server { const WSEndpoints = ControllerInstance.__get_ws_endpoints() HTTPEndpoints.forEach((endpoint) => { - this.registerHTTPEndpoint(endpoint, ...this.resolveMiddlewares(controller.useMiddlewares)) + this.register.http(endpoint, ...this.resolveMiddlewares(controller.useMiddlewares)) }) // WSEndpoints.forEach((endpoint) => { // this.registerWSEndpoint(endpoint) // }) } catch (error) { - if (!global.silentOutputServerErrors) { - this.InternalConsole.error(`\n\x1b[41m\x1b[37m🆘 [${controller.refName ?? controller.name}] Controller initialization failed:\x1b[0m ${error.stack} \n`) - } + console.error(`\n\x1b[41m\x1b[37m🆘 [${controller.refName ?? controller.name}] Controller initialization failed:\x1b[0m ${error.stack} \n`) } } } - registerHTTPEndpoint = (endpoint, ...execs) => { - // check and fix method - endpoint.method = endpoint.method?.toLowerCase() ?? "get" - - if (global.FIXED_HTTP_METHODS[endpoint.method]) { - endpoint.method = global.FIXED_HTTP_METHODS[endpoint.method] - } - - // check if method is supported - if (typeof this.engine.app[endpoint.method] !== "function") { - throw new Error(`Method [${endpoint.method}] is not supported!`) - } - - // grab the middlewares - let middlewares = [...execs] - - if (endpoint.middlewares) { - middlewares = [...middlewares, ...this.resolveMiddlewares(endpoint.middlewares)] - } - - // make sure method has root object on endpointsMap - 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.engine.app[endpoint.method](...routeModel) - - // extend to map - this.endpoints_map[endpoint.method] = { - ...this.endpoints_map[endpoint.method], - [endpoint.route]: { - route: endpoint.route, - enabled: endpoint.enabled ?? true, - }, - } - } - - registerWSEndpoint = (endpoint, ...execs) => { - endpoint.nsp = endpoint.nsp ?? "/main" - - this.websocket_instance.eventsChannels.push([endpoint.nsp, endpoint.on, endpoint.dispatch]) - - this.websocket_instance.map[endpoint.on] = { - nsp: endpoint.nsp, - channel: endpoint.on, - } - } - - registerBaseEndpoints() { - if (this.params.disableBaseEndpoint) { - this.InternalConsole.warn("‼️ [disableBaseEndpoint] Base endpoint is disabled! Endpoints mapping will not be available, so linebridge client bridges will not work! ‼️") + initializeRoutes = async (filePath) => { + if (!this.params.routesPath) { return false } - this.registerHTTPEndpoint({ - method: "get", - route: "/", - fn: (req, res) => { - return res.json({ - LINEBRIDGE_SERVER_VERSION: LINEBRIDGE_SERVER_VERSION, - version: pkgjson.version ?? "unknown", - usid: this.usid, - requestTime: new Date().getTime(), - }) - } - }) + const scanPath = filePath ?? this.params.routesPath - this.registerHTTPEndpoint({ - method: "GET", - route: "/__http_map", - fn: (req, res) => { - return res.json({ - endpointsMap: this.endpoints_map, + const files = fs.readdirSync(scanPath) + + for await (const file of files) { + const filePath = `${scanPath}/${file}` + + const stat = fs.statSync(filePath) + + if (stat.isDirectory()) { + await this.initializeRoutes(filePath) + + continue + } else if (file.endsWith(".js") || file.endsWith(".jsx") || file.endsWith(".ts") || file.endsWith(".tsx")) { + let splitedFilePath = filePath.split("/") + + splitedFilePath = splitedFilePath.slice(splitedFilePath.indexOf("routes") + 1) + + const method = splitedFilePath[splitedFilePath.length - 1].split(".")[0].toLocaleLowerCase() + + splitedFilePath = splitedFilePath.slice(0, splitedFilePath.length - 1) + + // parse parametrized routes + const parametersRegex = /\[([a-zA-Z0-9_]+)\]/g + + splitedFilePath = splitedFilePath.map((route) => { + if (route.match(parametersRegex)) { + route = route.replace(parametersRegex, ":$1") + } + + route = route.replace("[$]", "*") + + return route }) + + let route = splitedFilePath.join("/") + + route = route.replace(".jsx", "") + route = route.replace(".js", "") + route = route.replace(".ts", "") + route = route.replace(".tsx", "") + + if (route.endsWith("/index")) { + route = route.replace("/index", "") + } + + route = `/${route}` + + // import route + let routeFile = require(filePath) + + routeFile = routeFile.default ?? routeFile + + if (typeof routeFile !== "function") { + if (!routeFile.fn) { + console.warn(`Missing fn handler in [${method}][${route}]`) + continue + } + + if (Array.isArray(routeFile.useContext)) { + let contexts = {} + + for (const context of routeFile.useContext) { + contexts[context] = this.contexts[context] + } + + routeFile.contexts = contexts + + routeFile.fn.bind({ contexts }) + } + } + + new Endpoint( + this, + { + route: route, + enabled: true, + middlewares: routeFile.middlewares, + handlers: { + [method]: routeFile.fn ?? routeFile, + } + } + ) + + continue } - }) + } + } + + register = { + http: (endpoint, ..._middlewares) => { + // check and fix method + endpoint.method = endpoint.method?.toLowerCase() ?? "get" + + if (defaults.fixed_http_methods[endpoint.method]) { + endpoint.method = defaults.fixed_http_methods[endpoint.method] + } + + // check if method is supported + if (typeof this.engine.router[endpoint.method] !== "function") { + throw new Error(`Method [${endpoint.method}] is not supported!`) + } + + // grab the middlewares + let middlewares = [..._middlewares] + + if (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) + }, + ws: (endpoint, ...execs) => { + endpoint.nsp = endpoint.nsp ?? "/main" + + this.websocket_instance.eventsChannels.push([endpoint.nsp, endpoint.on, endpoint.dispatch]) + + this.websocket_instance.map[endpoint.on] = { + nsp: endpoint.nsp, + channel: endpoint.on, + } + }, + } + + async registerBaseEndpoints() { + if (this.params.disableBaseEndpoint) { + console.warn("‼️ [disableBaseEndpoint] Base endpoint is disabled! Endpoints mapping will not be available, so linebridge client bridges will not work! ‼️") + return false + } + + const scanPath = path.join(__dirname, "baseEndpoints") + const files = fs.readdirSync(scanPath) + + for await (const file of files) { + if (file === "index.js") { + continue + } + + let endpoint = require(path.join(scanPath, file)).default + + new endpoint(this) + } } - //* resolvers resolveMiddlewares = (requestedMiddlewares) => { + const middlewares = { + ...this.middlewares, + ...defaults.middlewares, + } + requestedMiddlewares = Array.isArray(requestedMiddlewares) ? requestedMiddlewares : [requestedMiddlewares] const execs = [] requestedMiddlewares.forEach((middlewareKey) => { if (typeof middlewareKey === "string") { - if (typeof this.middlewares[middlewareKey] !== "function") { + if (typeof middlewares[middlewareKey] !== "function") { throw new Error(`Middleware ${middlewareKey} not found!`) } - execs.push(this.middlewares[middlewareKey]) + execs.push(middlewares[middlewareKey]) } if (typeof middlewareKey === "function") { @@ -337,47 +397,7 @@ class Server { return execs } - cleanupProcess = () => { - this.InternalConsole.log("🛑 Stopping server...") - - if (typeof this.engine.app.close === "function") { - this.engine.app.close() - } - - this.engine.io.close() - } - - // handlers - createHTTPRequestHandler = (endpoint) => { - return async (req, res) => { - try { - // check if endpoint is disabled - if (!this.endpoints_map[endpoint.method][endpoint.route].enabled) { - throw new Error("Endpoint is disabled!") - } - - // return the returning call of the endpoint function - return await endpoint.fn(req, res) - } catch (error) { - if (typeof this.params.onRouteError === "function") { - return this.params.onRouteError(req, res, error) - } else { - if (!global.silentOutputServerErrors) { - this.InternalConsole.error({ - message: "Unhandled route error:", - description: error.stack, - ref: [endpoint.method, endpoint.route].join("|"), - }) - } - - return res.status(500).json({ - "error": error.message - }) - } - } - } - } - + // Utilities toogleEndpointReachability = (method, route, enabled) => { if (typeof this.endpoints_map[method] !== "object") { throw new Error(`Cannot toogle endpoint, method [${method}] not set!`) diff --git a/src/utils/index.js b/src/utils/index.js old mode 100644 new mode 100755 diff --git a/src/utils/linebridge_ascii.js b/src/utils/linebridge_ascii.js old mode 100644 new mode 100755 diff --git a/src/utils/nanoid/index.js b/src/utils/nanoid/index.js old mode 100644 new mode 100755 diff --git a/src/utils/schematized/index.js b/src/utils/schematized/index.js old mode 100644 new mode 100755 diff --git a/test/clientTest.js b/test/clientTest.js deleted file mode 100644 index ad5f03d..0000000 --- a/test/clientTest.js +++ /dev/null @@ -1,30 +0,0 @@ -const assert = require("assert") -const linebridgeClient = require("../dist/client/index.js") - -const exampleTestHTTPPort = 3010 - -let exampleBridge = null - -describe("[Client]", async function () { - it("exports fine", function () { - assert.equal(typeof linebridgeClient, "object") - console.log(linebridgeClient) - }) - - it("create test bridge", async () => { - exampleBridge = new linebridgeClient.Bridge({ - origin: `http://0.0.0.0:${exampleTestHTTPPort}`, - }) - - await exampleBridge.initialize() - }) - - it("bridge.endpoints is an object", async () => { - assert.equal(typeof exampleBridge.endpoints, "object") - }) - - it("test endpoint should correctly respond", async () => { - let response = await exampleBridge.endpoints.get.test() - assert.equal(response.test, "testing") - }) -}) \ No newline at end of file diff --git a/test/serverTest.js b/test/serverTest.js deleted file mode 100644 index aca480d..0000000 --- a/test/serverTest.js +++ /dev/null @@ -1,60 +0,0 @@ -const assert = require("assert") -const linebridgeServer = require("../dist/server/index.js") - -const exampleTestHTTPPort = 3010 - -const testEndpoints = [ - { - route: "/test", - method: "GET", - fn: function (req, res) { - return res.send({ test: "testing" }) - } - }, - { - route: "/disabledByDefault", - method: "GET", - fn: function (req, res) { - return res.send("This must not be sended") - }, - enabled: false, - }, - { - route: "/shouldBeDisabled", - method: "GET", - fn: function (req, res) { - return res.send("This must not be sended after use `toogleEndpointReachability`") - } - } -] - -let exampleServer = null - -describe("[Server]", async function () { - it("should export", function () { - assert.equal(typeof linebridgeServer, "function") - }) - - it("create server", async function () { - exampleServer = new linebridgeServer({ - port: exampleTestHTTPPort, - }) - }) - - it("register test controllers", async function () { - testEndpoints.forEach((endpoint) => { - exampleServer.registerHTTPEndpoint(endpoint) - }) - }) - - it("initialize server", async function () { - await exampleServer.initialize() - }) - - it("toogleEndpointReachability", async function () { - exampleServer.toogleEndpointReachability("get", "/shouldBeDisabled", false) - - // check if endpoint is disabled - assert.equal(exampleServer.endpointsMap["get"]["/shouldBeDisabled"].enabled, false) - }) -}) \ No newline at end of file diff --git a/utests/basicServer.js b/utests/basicServer.js deleted file mode 100644 index 755ce8e..0000000 --- a/utests/basicServer.js +++ /dev/null @@ -1,32 +0,0 @@ -import { Server, Controller } from "../src/server" - -const TestControllers = [ - class AController extends Controller { - get = { - "/test": (req, res) => { - return res.send("Hello World!") - } - } - }, - class BController extends Controller { - static useRoute = "/bcontroller" - - get = { - "/test": (req, res) => { - return res.send("Hello World!") - } - } - } -] - -async function _main() { - const server = new Server({ - httpEngine: "express" - }, TestControllers) - - await server.initialize() -} - -_main().catch((error) => { - console.error(`[MAIN_ERROR] ${error}`) -}) \ No newline at end of file diff --git a/utests/basicServerAndBridge.js b/utests/basicServerAndBridge.js deleted file mode 100644 index 55f2543..0000000 --- a/utests/basicServerAndBridge.js +++ /dev/null @@ -1,111 +0,0 @@ -import { Bridge } from "../src/client" -import { Server, Controller } from "../src/server" - -const Middlewares = { - "test": (req, res, next) => { - console.log("test middleware, it should run on every endpoint of a controller") - return next() - }, - "test2": (req, res, next) => { - console.log("test2 middleware, it should run on request of a endpoint") - return next() - } -} - -const Controllers = [ - class DisabledController extends Controller { - static disabled = true - - get = { - "/unreachable": (req, res) => { - return res.send("this must not be reachable") - } - } - - }, - class TestController extends Controller { - static useMiddlewares = ["test"] - - channels = { - "epicEvent": (socket, ...args) => { - console.log(`[SERVER WS EVENT] > ${socket.id} > `, ...args) - return socket.res("elo") - } - } - - get = { - "/test/:name": { - fn: (req, res) => { - const name = req.params.name - - return res.json({ - message: name ? `Hello ${name}!` : "Hello World!" - }) - }, - }, - "/crashTest": (req, res) => { - throw new Error("Boom!") - }, - "/test": (req, res) => { - return res.send("Hello World!") - } - } - - delete = { - "/test": (req, res) => { - return res.send(`Deleting ${req.body.a}`) - } - } - }, -] - -async function _main() { - const server = new Server({ - onWSClientConnection: (socket) => { - const authToken = socket.handshake.auth?.token - console.log(`AUTH TOKEN: ${authToken}`) - - if (!authToken) { - socket.emit("unauthorized", "No auth token provided!") - return socket.disconnect() - } - - if (authToken !== "123") { - socket.emit("unauthorized", "invalid auth token!") - return socket.disconnect() - } - } - }, Controllers, Middlewares) - - const clientBridge = new Bridge({ - origin: server.HTTPAddress, - wsOrigin: server.WSAddress, - wsMainSocketOptions: { - auth: { - token: "123" - } - }, - }, { - onUnauthorized: (reason) => { - console.log(reason) - } - }) - - await server.initialize() - await clientBridge.initialize() - - const test = await clientBridge.endpoints.get.test() - const crashTest = await clientBridge.endpoints.get.crashtest().catch(error => { - console.log(error) - return "Crash test passed!" - }) - const wsEpicEvent = await clientBridge.wsEndpoints.epicEvent("Hello", "World") - - console.log(`[get.test] > ${test}`) - console.log(`[get.crashtest] > ${crashTest}`) - console.log(`[ws.epicEvent] > ${wsEpicEvent}`) -} - -_main().catch((error) => { - console.error(`[MAIN_ERROR] ${error}`) -}) \ No newline at end of file