diff --git a/server/README.md b/server/README.md index e0ed41f..8c9b155 100755 --- a/server/README.md +++ b/server/README.md @@ -9,7 +9,7 @@ A multiproposal framework to build fast, scalable, and secure servers. Currently used on RageStudio's services backends, like [Comty](https://github.com/ragestudio/comty) ## Suported Engines -- [hyper-express](https://github.com/kartikk221/hyper-express) (default) | High Performance Node.js Webserver. +- [he](https://github.com/kartikk221/hyper-express) (default) | High Performance Node.js Webserver. - worker | IPC Worker for sharding and efficient multi-threading. ## Features diff --git a/server/bootloader/bootWrapper.js b/server/bootloader/bootWrapper.js index aa2cb0c..b3def34 100644 --- a/server/bootloader/bootWrapper.js +++ b/server/bootloader/bootWrapper.js @@ -1,4 +1,3 @@ -const { onExit } = require("signal-exit") const injectEnvFromInfisical = require("./injectEnvFromInfisical") module.exports = async function Boot(main) { diff --git a/server/package.json b/server/package.json index d14a93f..41c79f6 100755 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "linebridge", - "version": "1.0.0-a1", + "version": "1.0.0-a3", "description": "Multiproposal framework to build fast, scalable, and secure servers.", "author": "RageStudio ", "bugs": { @@ -15,6 +15,7 @@ "access": "public" }, "files": [ + "bootloader/**/**", "src/**/**", "dist/**/**", "./package.json" @@ -26,10 +27,6 @@ "dependencies": { "@foxify/events": "^2.1.0", "@infisical/sdk": "^2.1.8", - "@socket.io/cluster-adapter": "^0.2.2", - "@socket.io/redis-adapter": "^8.2.1", - "@socket.io/redis-emitter": "^5.1.0", - "@socket.io/sticky": "^1.0.4", "axios": "^1.8.4", "chokidar": "^4.0.3", "dotenv": "^16.5.0", @@ -37,8 +34,6 @@ "ioredis": "^5.6.1", "minimatch": "^10.0.1", "module-alias": "^2.2.2", - "signal-exit": "^4.1.0", - "socket.io": "^4.8.1", "sucrase": "^3.35.0" }, "devDependencies": { diff --git a/server/src/engines/he-legacy/index.js b/server/src/engines/he-legacy/index.js deleted file mode 100755 index 9d1f46d..0000000 --- a/server/src/engines/he-legacy/index.js +++ /dev/null @@ -1,80 +0,0 @@ -import he from "hyper-express" -import rtengine from "./rtengine" - -export default class Engine { - constructor(server) { - this.server = server - } - - static heDefaultParams = { - max_body_length: 50 * 1024 * 1024, //50MB in bytes, - } - - app = null - ws = null - router = new he.Router() - map = new Map() - - initialize = async () => { - this.app = new he.Server({ - ...Engine.heDefaultParams, - key_file_name: this.server.ssl?.key ?? undefined, - cert_file_name: this.server.ssl?.cert ?? undefined, - }) - - this.router.any("*", this.defaultResponse) - this.app.use(this.mainMiddleware) - this.app.use(this.router) - - if (this.server.params.websockets === true) { - this.ws = new rtengine({ - requireAuth: this.server.constructor.requiredWsAuth, - handleAuth: this.server.handleWsAuth, - root: `/${this.server.params.refName}`, - }) - - this.ws.initialize() - - global.websockets = this.ws - - await this.ws.io.attachApp(this.app.uws_instance) - } - } - - mainMiddleware = async (req, res, next) => { - if (req.method === "OPTIONS") { - 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) - } - } - } - - defaultResponse = (req, res) => { - return res.status(404).json({ - error: "Not found", - }) - } - - listen = async () => { - await this.app.listen(this.server.params.listenPort) - } - - // close must be synchronous - close = () => { - if (this.ws && typeof this.ws.close === "function") { - this.ws.close() - } - - if (this.app && typeof this.app.close === "function") { - this.app.close() - } - } -} diff --git a/server/src/engines/he-legacy/rtengine/index.js b/server/src/engines/he-legacy/rtengine/index.js deleted file mode 100755 index 9b39671..0000000 --- a/server/src/engines/he-legacy/rtengine/index.js +++ /dev/null @@ -1,291 +0,0 @@ -import cluster from "node:cluster" -import redis from "ioredis" -import SocketIO from "socket.io" -import { EventEmitter } from "@foxify/events" - -import RedisMap from "./redis_map.js" - -export default class RTEngineServer { - 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.redis = params.redis - this.io = params.io - } - - worker_id = nanoid() - - io = null - redis = null - - connections = null - users = null - - events = new Map() - - async initialize() { - console.log("🌐 Initializing RTEngine server...") - - if (!this.io) { - this.io = new SocketIO.Server({ - path: this.params.root ?? "/", - }) - } - - 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, - }) - } - - await this.redis.connect() - - // create mappers - this.connections = new RedisMap(this.redis, { - refKey: "connections", - worker_id: this.worker_id, - }) - - this.users = new RedisMap(this.redis, { - refKey: "users", - 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) - } - } - - // handle connection - this.io.on("connection", (socket) => { - this.eventHandler(this.onConnect, socket) - }) - - console.log(`[RTEngine] Listening...`) - console.log(`[RTEngine] Universal worker id [${this.worker_id}]`) - - return true - } - - close = () => { - console.log(`Cleaning up RTEngine server...`) - - // 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.clusterMode) { - this.connections.flush(cluster.worker.id) - } - - if (this.io) { - this.io.close() - } - - if (this.redis) { - this.redis.quit() - } - } - - onConnect = async (socket) => { - console.log(`[RTEngine] new:client | id [${socket.id}]`) - - // create eventBus - socket.eventBus = new EventEmitter() - socket.pendingTimeouts = new Set() - - // register events - if (typeof this.events === "object") { - for (const [key, handler] of this.events.entries()) { - socket.on(key, (...args) => { - this.eventHandler(handler, socket, ...args) - }) - } - } - - // handle ping - socket.on("ping", () => { - socket.emit("pong") - }) - - // handle disconnect - socket.on("disconnect", () => { - this.eventHandler(this.onDisconnect, socket) - }) - - await this.connections.set(socket.id, 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, - ) - } - } - - onDisconnect = async (socket) => { - console.log(`[RTEngine] disconnect:client | id [${socket.id}]`) - - if (socket.eventBus.emit) { - socket.eventBus.emit("disconnect") - } else { - console.warn( - `[${socket.id}][@${socket.userData.username}] Cannot emit disconnect event`, - ) - } - - const conn = await this.connections.get(socket.id) - - if (conn) { - if (conn.user_id) { - await this.users.del(conn.user_id) - } - } - - await this.connections.del(socket.id) - } - - onAuth = async (socket, token, handleAuth) => { - if (typeof handleAuth !== "function") { - console.log(`[RTEngine] [${socket.id}] No auth handler provided`) - return false - } - - if (!token) { - if (socket.handshake.auth.token) { - token = socket.handshake.auth.token - } - if (socket.handshake.query.auth) { - token = socket.handshake.query.auth - } - } - - function err(code, message) { - console.log( - `[RTEngine] [${socket.id}] Auth error: ${code} >`, - message, - ) - - socket.emit("response:error", { - code, - message, - }) - - socket.disconnect() - - return false - } - - if (!token) { - return err(401, "auth:token_required") - } - - const authResult = await handleAuth(socket, token, err) - - if (authResult) { - const conn = await this.connections.has(socket.id) - - // 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 - } - - this.users.set(authResult.user_id.toString(), { - socket_id: socket.id, - ...authResult, - }) - - socket.emit("response:auth:ok") - - console.log( - `[RTEngine] client:authenticated | socket_id [${socket.id}] | user_id [${authResult.user_id}] | username [@${authResult.username}]`, - ) - } - } - - eventHandler = async (fn, socket, payload) => { - try { - await fn(socket, payload, this) - } catch (error) { - console.error(error) - - if (typeof socket.emit === "function") { - socket.emit("response:error", { - code: 500, - message: error.message, - }) - } - } - } - - find = { - manyById: async (ids) => { - if (typeof ids === "string") { - ids = [ids] - } - - const users = await this.users.getMany(ids) - - 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) - - if (!user) { - return null - } - - const socket = await this.connections.get(user.socket_id) - - return socket - }, - } -} diff --git a/server/src/engines/he-legacy/rtengine/redis_map.js b/server/src/engines/he-legacy/rtengine/redis_map.js deleted file mode 100755 index 254d873..0000000 --- a/server/src/engines/he-legacy/rtengine/redis_map.js +++ /dev/null @@ -1,206 +0,0 @@ -export default class RedisMap { - constructor(redis, params = {}) { - if (!redis) { - throw new Error("redis client is required") - } - - if (!params.refKey) { - throw new Error("refKey is required") - } - - if (!params.worker_id) { - throw new Error("worker_id is required") - } - - this.redis = redis - this.params = params - - this.refKey = this.params.refKey - this.worker_id = this.params.worker_id - } - - localMap = new Map() - - set = async (key, value) => { - if (!key) { - console.warn(`[redismap] (${this.refKey}) Failed to set entry with no key`) - return - } - - if (!value) { - console.warn(`[redismap] (${this.refKey}) Failed to set entry [${key}] with no value`) - return - } - - const redisKey = `${this.refKey}:${key}` - - this.localMap.set(key, value) - - // console.log(`[redismap] (${this.refKey}) Set entry [${key}] to [${value}]`) - - await this.redis.hset(redisKey, { - worker_id: this.worker_id, - }) - - return value - } - - get = async (key, value) => { - if (!key) { - console.warn(`[redismap] (${this.refKey}) Failed to get entry with no key`) - return - } - - const redisKey = `${this.refKey}:${key}` - - let result = null - - if (this.localMap.has(key)) { - result = this.localMap.get(key) - } else { - const remoteWorkerID = await this.redis.hget(redisKey, value) - - if (!remoteWorkerID) { - return null - } - - throw new Error("Redis stream data, not implemented...") - } - - return result - } - - del = async (key) => { - if (!key) { - console.warn(`[redismap] (${this.refKey}) Failed to delete entry with no key`) - return false - } - - const redisKey = `${this.refKey}:${key}` - - const data = await this.get(key) - - if (!data) { - return false - } - - if (this.localMap.has(key)) { - this.localMap.delete(key) - } - - await this.redis.hdel(redisKey, ["worker_id"]) - - return true - } - - update = async (key, data) => { - if (!key) { - console.warn(`[redismap] (${this.refKey}) Failed to update entry with no key`) - return - } - - const redisKey = `${this.refKey}:${key}` - - let new_data = await this.get(key) - - if (!new_data) { - console.warn(`[redismap] (${this.refKey}) Object [${key}] not exist, nothing to update`) - - return false - } - - new_data = { - ...new_data, - ...data, - } - - //console.log(`[redismap] (${this.refKey}) Object [${key}] updated`) - - this.localMap.set(key, new_data) - - await this.redis.hset(redisKey, { - worker_id: this.worker_id, - }) - - return new_data - } - - has = async (key) => { - if (!key) { - console.warn(`[redismap] (${this.refKey}) Failed to check entry with no key`) - return false - } - - const redisKey = `${this.refKey}:${key}` - - if (this.localMap.has(key)) { - return true - } - - if (await this.redis.hget(redisKey, "worker_id")) { - return true - } - - return false - } - - // flush = async (worker_id) => { - // let nextIndex = 0 - - // do { - // const [nextIndexAsStr, results] = await this.redis.scan( - // nextIndex, - // "MATCH", - // `${this.refKey}:*`, - // "COUNT", - // 100 - // ) - - // nextIndex = parseInt(nextIndexAsStr, 10) - - // const pipeline = this.redis.pipeline() - - // for await (const key of results) { - // const key_id = key.split(this.refKey + ":")[1] - - // const data = await this.get(key_id) - - // if (!data) { - // continue - // } - - // if (worker_id) { - // if (data.worker_id !== worker_id) { - // continue - // } - // } - - // pipeline.hdel(key, Object.keys(data)) - // } - - // await pipeline.exec() - // } while (nextIndex !== 0) - // } - - // size = async () => { - // let count = 0 - - // let nextIndex = 0 - - // do { - // const [nextIndexAsStr, results] = await this.redis.scan( - // nextIndex, - // "MATCH", - // `${this.refKey}:*`, - // "COUNT", - // 100 - // ) - - // nextIndex = parseInt(nextIndexAsStr, 10) - - // count = count + results.length - // } while (nextIndex !== 0) - - // return count - // } -} \ No newline at end of file diff --git a/server/src/engines/index.js b/server/src/engines/index.js index beee0fa..8fa7358 100755 --- a/server/src/engines/index.js +++ b/server/src/engines/index.js @@ -1,9 +1,7 @@ -import HeLegacy from "./he-legacy" import He from "./he" import Worker from "./worker" export default { - "he-legacy": HeLegacy, he: He, worker: Worker, }