mirror of
https://github.com/ragestudio/linebridge.git
synced 2025-06-09 02:24:17 +00:00
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:
parent
57d8b4bed1
commit
1449695714
@ -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
|
||||
|
@ -1,4 +1,3 @@
|
||||
const { onExit } = require("signal-exit")
|
||||
const injectEnvFromInfisical = require("./injectEnvFromInfisical")
|
||||
|
||||
module.exports = async function Boot(main) {
|
||||
|
@ -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 <support@ragestudio.net>",
|
||||
"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": {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
},
|
||||
}
|
||||
}
|
@ -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
|
||||
// }
|
||||
}
|
@ -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,
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user