Update server dependency and remove legacy code

Bump version to 1.0.0-a3 and remove unused websocket functionality

server/bootloader/bootWrapper.js is now included in package files

This commit updates hyper-express reference to "he" in README, removes
the unused he-legacy engine and socket.io related dependencies.
This commit is contained in:
SrGooglo 2025-04-24 12:35:02 +00:00
parent 57d8b4bed1
commit 1449695714
7 changed files with 3 additions and 588 deletions

View File

@ -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) Currently used on RageStudio's services backends, like [Comty](https://github.com/ragestudio/comty)
## Suported Engines ## 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. - worker | IPC Worker for sharding and efficient multi-threading.
## Features ## Features

View File

@ -1,4 +1,3 @@
const { onExit } = require("signal-exit")
const injectEnvFromInfisical = require("./injectEnvFromInfisical") const injectEnvFromInfisical = require("./injectEnvFromInfisical")
module.exports = async function Boot(main) { module.exports = async function Boot(main) {

View File

@ -1,6 +1,6 @@
{ {
"name": "linebridge", "name": "linebridge",
"version": "1.0.0-a1", "version": "1.0.0-a3",
"description": "Multiproposal framework to build fast, scalable, and secure servers.", "description": "Multiproposal framework to build fast, scalable, and secure servers.",
"author": "RageStudio <support@ragestudio.net>", "author": "RageStudio <support@ragestudio.net>",
"bugs": { "bugs": {
@ -15,6 +15,7 @@
"access": "public" "access": "public"
}, },
"files": [ "files": [
"bootloader/**/**",
"src/**/**", "src/**/**",
"dist/**/**", "dist/**/**",
"./package.json" "./package.json"
@ -26,10 +27,6 @@
"dependencies": { "dependencies": {
"@foxify/events": "^2.1.0", "@foxify/events": "^2.1.0",
"@infisical/sdk": "^2.1.8", "@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", "axios": "^1.8.4",
"chokidar": "^4.0.3", "chokidar": "^4.0.3",
"dotenv": "^16.5.0", "dotenv": "^16.5.0",
@ -37,8 +34,6 @@
"ioredis": "^5.6.1", "ioredis": "^5.6.1",
"minimatch": "^10.0.1", "minimatch": "^10.0.1",
"module-alias": "^2.2.2", "module-alias": "^2.2.2",
"signal-exit": "^4.1.0",
"socket.io": "^4.8.1",
"sucrase": "^3.35.0" "sucrase": "^3.35.0"
}, },
"devDependencies": { "devDependencies": {

View File

@ -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()
}
}
}

View File

@ -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
},
}
}

View File

@ -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
// }
}

View File

@ -1,9 +1,7 @@
import HeLegacy from "./he-legacy"
import He from "./he" import He from "./he"
import Worker from "./worker" import Worker from "./worker"
export default { export default {
"he-legacy": HeLegacy,
he: He, he: He,
worker: Worker, worker: Worker,
} }