mirror of
https://github.com/ragestudio/linebridge.git
synced 2025-06-09 10:34:17 +00:00
Merge from local
This commit is contained in:
parent
ff38d45b96
commit
11d8a862b4
@ -1,182 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
require("dotenv").config()
|
|
||||||
require("sucrase/register")
|
|
||||||
|
|
||||||
const path = require("node:path")
|
|
||||||
const Module = require("node:module")
|
|
||||||
const { Buffer } = require("node:buffer")
|
|
||||||
const { webcrypto: crypto } = require("node:crypto")
|
|
||||||
const { InfisicalClient } = require("@infisical/sdk")
|
|
||||||
const moduleAlias = require("module-alias")
|
|
||||||
const { onExit } = require("signal-exit")
|
|
||||||
|
|
||||||
// Override file execution arg
|
|
||||||
process.argv.splice(1, 1)
|
|
||||||
process.argv[1] = path.resolve(process.argv[1])
|
|
||||||
|
|
||||||
// Expose boot function to global
|
|
||||||
global.Boot = Boot
|
|
||||||
global.isProduction = process.env.NODE_ENV === "production"
|
|
||||||
|
|
||||||
global["__root"] = path.resolve(process.cwd())
|
|
||||||
global["__src"] = path.resolve(globalThis["__root"], path.dirname(process.argv[1]))
|
|
||||||
|
|
||||||
global["aliases"] = {
|
|
||||||
"root": global["__root"],
|
|
||||||
"src": global["__src"],
|
|
||||||
|
|
||||||
// expose shared resources
|
|
||||||
"@db": path.resolve(process.cwd(), "db_models"),
|
|
||||||
"@db_models": path.resolve(process.cwd(), "db_models"),
|
|
||||||
"@shared-utils": path.resolve(process.cwd(), "utils"),
|
|
||||||
"@shared-classes": path.resolve(process.cwd(), "classes"),
|
|
||||||
"@shared-lib": path.resolve(process.cwd(), "lib"),
|
|
||||||
"@shared-middlewares": path.resolve(process.cwd(), "middlewares"),
|
|
||||||
|
|
||||||
// expose internal resources
|
|
||||||
"@lib": path.resolve(global["__src"], "lib"),
|
|
||||||
"@middlewares": path.resolve(global["__src"], "middlewares"),
|
|
||||||
"@controllers": path.resolve(global["__src"], "controllers"),
|
|
||||||
"@config": path.resolve(global["__src"], "config"),
|
|
||||||
"@shared": path.resolve(global["__src"], "shared"),
|
|
||||||
"@classes": path.resolve(global["__src"], "classes"),
|
|
||||||
"@models": path.resolve(global["__src"], "models"),
|
|
||||||
"@services": path.resolve(global["__src"], "services"),
|
|
||||||
"@utils": path.resolve(global["__src"], "utils"),
|
|
||||||
}
|
|
||||||
|
|
||||||
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"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerPatches() {
|
|
||||||
global.b64Decode = (data) => {
|
|
||||||
return Buffer.from(data, "base64").toString("utf-8")
|
|
||||||
}
|
|
||||||
global.b64Encode = (data) => {
|
|
||||||
return Buffer.from(data, "utf-8").toString("base64")
|
|
||||||
}
|
|
||||||
|
|
||||||
global.nanoid = (t = 21) => crypto.getRandomValues(new Uint8Array(t)).reduce(((t, e) => t += (e &= 63) < 36 ? e.toString(36) : e < 62 ? (e - 26).toString(36).toUpperCase() : e > 62 ? "-" : "_"), "");
|
|
||||||
|
|
||||||
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 registerAliases() {
|
|
||||||
registerBaseAliases(global["__src"], global["aliases"])
|
|
||||||
}
|
|
||||||
|
|
||||||
async function injectEnvFromInfisical() {
|
|
||||||
const envMode = global.FORCE_ENV ?? global.isProduction ? "prod" : "dev"
|
|
||||||
|
|
||||||
console.log(`[BOOT] 🔑 Injecting env variables from INFISICAL in [${envMode}] mode...`)
|
|
||||||
|
|
||||||
const client = new InfisicalClient({
|
|
||||||
auth: {
|
|
||||||
universalAuth: {
|
|
||||||
clientId: process.env.INFISICAL_CLIENT_ID,
|
|
||||||
clientSecret: process.env.INFISICAL_CLIENT_SECRET,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const secrets = await client.listSecrets({
|
|
||||||
environment: envMode,
|
|
||||||
path: process.env.INFISICAL_PATH ?? "/",
|
|
||||||
projectId: process.env.INFISICAL_PROJECT_ID ?? null,
|
|
||||||
includeImports: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
//inject to process.env
|
|
||||||
secrets.forEach((secret) => {
|
|
||||||
if (!(process.env[secret.secretKey])) {
|
|
||||||
process.env[secret.secretKey] = secret.secretValue
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function Boot(main) {
|
|
||||||
if (!main) {
|
|
||||||
throw new Error("main class is not defined")
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[BOOT] Booting in [${global.isProduction ? "production" : "development"}] mode...`)
|
|
||||||
|
|
||||||
if (process.env.INFISICAL_CLIENT_ID && process.env.INFISICAL_CLIENT_SECRET) {
|
|
||||||
console.log(`[BOOT] INFISICAL Credentials found, injecting env variables from INFISICAL...`)
|
|
||||||
await injectEnvFromInfisical()
|
|
||||||
}
|
|
||||||
|
|
||||||
const instance = new main()
|
|
||||||
|
|
||||||
onExit((code, signal) => {
|
|
||||||
console.log(`[BOOT] Cleaning up...`)
|
|
||||||
|
|
||||||
if (typeof instance.onClose === "function") {
|
|
||||||
instance.onClose()
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.engine.close()
|
|
||||||
}, {
|
|
||||||
alwaysLast: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
await instance.initialize()
|
|
||||||
|
|
||||||
if (process.env.lb_service && process.send) {
|
|
||||||
process.send({
|
|
||||||
status: "ready"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Apply patches
|
|
||||||
registerPatches()
|
|
||||||
|
|
||||||
// Apply aliases
|
|
||||||
registerAliases()
|
|
||||||
|
|
||||||
// execute main
|
|
||||||
Module.runMain()
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[BOOT] ❌ Boot error: ", error)
|
|
||||||
}
|
|
49
server/bootloader/bin
Executable file
49
server/bootloader/bin
Executable file
@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
const path = require("node:path")
|
||||||
|
const childProcess = require("node:child_process")
|
||||||
|
const startFileWatcher = require("./startFileWatcher.js")
|
||||||
|
|
||||||
|
const bootloaderPath = path.resolve(__dirname, "boot.js")
|
||||||
|
const mainModulePath = process.argv[2]
|
||||||
|
const mainModuleSrc = path.resolve(process.cwd(), path.dirname(mainModulePath))
|
||||||
|
|
||||||
|
let childProcessInstance = null
|
||||||
|
let reloadTimeout = null
|
||||||
|
|
||||||
|
function selfReload() {
|
||||||
|
if (!childProcessInstance) {
|
||||||
|
console.error(
|
||||||
|
"[BOOT] Cannot self-reload. Missing childProcessInstance.",
|
||||||
|
)
|
||||||
|
return process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("[BOOT] Reloading...")
|
||||||
|
|
||||||
|
childProcessInstance.kill()
|
||||||
|
|
||||||
|
boot()
|
||||||
|
}
|
||||||
|
|
||||||
|
function selfReloadDebounce() {
|
||||||
|
if (reloadTimeout) {
|
||||||
|
clearTimeout(reloadTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadTimeout = setTimeout(selfReload, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
function boot() {
|
||||||
|
childProcessInstance = childProcess.fork(bootloaderPath, [mainModulePath], {
|
||||||
|
stdio: "inherit",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// if --watch flag exist, start file watcher
|
||||||
|
if (process.argv.includes("--watch")) {
|
||||||
|
startFileWatcher(mainModuleSrc, {
|
||||||
|
onReload: selfReloadDebounce,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
boot()
|
55
server/bootloader/boot.js
Normal file
55
server/bootloader/boot.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
require("dotenv").config()
|
||||||
|
require("sucrase/register")
|
||||||
|
|
||||||
|
const path = require("node:path")
|
||||||
|
const Module = require("node:module")
|
||||||
|
const registerBaseAliases = require("./registerBaseAliases")
|
||||||
|
|
||||||
|
// Override file execution arg
|
||||||
|
process.argv.splice(1, 1)
|
||||||
|
process.argv[1] = path.resolve(process.argv[1])
|
||||||
|
|
||||||
|
// Expose to global
|
||||||
|
global.paths = {
|
||||||
|
root: process.cwd(),
|
||||||
|
__src: path.resolve(process.cwd(), path.dirname(process.argv[1])),
|
||||||
|
}
|
||||||
|
|
||||||
|
global["aliases"] = {
|
||||||
|
// expose src
|
||||||
|
"@": global.paths.__src,
|
||||||
|
|
||||||
|
// expose shared resources
|
||||||
|
"@db": path.resolve(process.cwd(), "db_models"),
|
||||||
|
"@db_models": path.resolve(process.cwd(), "db_models"),
|
||||||
|
"@shared-utils": path.resolve(process.cwd(), "utils"),
|
||||||
|
"@shared-classes": path.resolve(process.cwd(), "classes"),
|
||||||
|
"@shared-lib": path.resolve(process.cwd(), "lib"),
|
||||||
|
"@shared-middlewares": path.resolve(process.cwd(), "middlewares"),
|
||||||
|
|
||||||
|
// expose internal resources
|
||||||
|
"@routes": path.resolve(paths.__src, "routes"),
|
||||||
|
"@models": path.resolve(paths.__src, "models"),
|
||||||
|
"@middlewares": path.resolve(paths.__src, "middlewares"),
|
||||||
|
"@classes": path.resolve(paths.__src, "classes"),
|
||||||
|
"@services": path.resolve(paths.__src, "services"),
|
||||||
|
"@config": path.resolve(paths.__src, "config"),
|
||||||
|
"@utils": path.resolve(paths.__src, "utils"),
|
||||||
|
"@lib": path.resolve(paths.__src, "lib"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// expose bootwrapper to global
|
||||||
|
global.Boot = require("./bootWrapper")
|
||||||
|
|
||||||
|
try {
|
||||||
|
// apply patches
|
||||||
|
require("./patches.js")
|
||||||
|
|
||||||
|
// Apply aliases
|
||||||
|
registerBaseAliases(global.paths.__src, global["aliases"])
|
||||||
|
|
||||||
|
// execute main
|
||||||
|
Module.runMain()
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[BOOT] ❌ Boot error: ", error)
|
||||||
|
}
|
48
server/bootloader/bootWrapper.js
Normal file
48
server/bootloader/bootWrapper.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
const { onExit } = require("signal-exit")
|
||||||
|
const injectEnvFromInfisical = require("./injectEnvFromInfisical")
|
||||||
|
|
||||||
|
module.exports = async function Boot(main) {
|
||||||
|
if (!main) {
|
||||||
|
throw new Error("main class is not defined")
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[BOOT] Booting in [${global.isProduction ? "production" : "development"}] mode...`,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
process.env.INFISICAL_CLIENT_ID &&
|
||||||
|
process.env.INFISICAL_CLIENT_SECRET
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
`[BOOT] INFISICAL Credentials found, injecting env variables from INFISICAL...`,
|
||||||
|
)
|
||||||
|
await injectEnvFromInfisical()
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = new main()
|
||||||
|
|
||||||
|
process.on("exit", (code) => {
|
||||||
|
console.log(`[BOOT] Closing ...`)
|
||||||
|
|
||||||
|
instance._fireClose()
|
||||||
|
})
|
||||||
|
|
||||||
|
process.on("SIGTERM", () => {
|
||||||
|
process.exit(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
process.on("SIGINT", () => {
|
||||||
|
process.exit(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
await instance.initialize()
|
||||||
|
|
||||||
|
if (process.env.lb_service && process.send) {
|
||||||
|
process.send({
|
||||||
|
status: "ready",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance
|
||||||
|
}
|
32
server/bootloader/injectEnvFromInfisical.js
Normal file
32
server/bootloader/injectEnvFromInfisical.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
const { InfisicalClient } = require("@infisical/sdk")
|
||||||
|
|
||||||
|
module.exports = async function injectEnvFromInfisical() {
|
||||||
|
const envMode = (global.FORCE_ENV ?? global.isProduction) ? "prod" : "dev"
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[BOOT] 🔑 Injecting env variables from INFISICAL in [${envMode}] mode...`,
|
||||||
|
)
|
||||||
|
|
||||||
|
const client = new InfisicalClient({
|
||||||
|
auth: {
|
||||||
|
universalAuth: {
|
||||||
|
clientId: process.env.INFISICAL_CLIENT_ID,
|
||||||
|
clientSecret: process.env.INFISICAL_CLIENT_SECRET,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const secrets = await client.listSecrets({
|
||||||
|
environment: envMode,
|
||||||
|
path: process.env.INFISICAL_PATH ?? "/",
|
||||||
|
projectId: process.env.INFISICAL_PROJECT_ID ?? null,
|
||||||
|
includeImports: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
//inject to process.env
|
||||||
|
secrets.forEach((secret) => {
|
||||||
|
if (!process.env[secret.secretKey]) {
|
||||||
|
process.env[secret.secretKey] = secret.secretValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
49
server/bootloader/patches.js
Normal file
49
server/bootloader/patches.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
const { webcrypto: crypto } = require("node:crypto")
|
||||||
|
const { Buffer } = require("node:buffer")
|
||||||
|
|
||||||
|
global.isProduction = process.env.NODE_ENV === "production"
|
||||||
|
|
||||||
|
global.b64Decode = (data) => {
|
||||||
|
return Buffer.from(data, "base64").toString("utf-8")
|
||||||
|
}
|
||||||
|
global.b64Encode = (data) => {
|
||||||
|
return Buffer.from(data, "utf-8").toString("base64")
|
||||||
|
}
|
||||||
|
|
||||||
|
global.nanoid = (t = 21) =>
|
||||||
|
crypto
|
||||||
|
.getRandomValues(new Uint8Array(t))
|
||||||
|
.reduce(
|
||||||
|
(t, e) =>
|
||||||
|
(t +=
|
||||||
|
(e &= 63) < 36
|
||||||
|
? e.toString(36)
|
||||||
|
: e < 62
|
||||||
|
? (e - 26).toString(36).toUpperCase()
|
||||||
|
: e > 62
|
||||||
|
? "-"
|
||||||
|
: "_"),
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
14
server/bootloader/registerBaseAliases.js
Normal file
14
server/bootloader/registerBaseAliases.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
const path = require("node:path")
|
||||||
|
const moduleAlias = require("module-alias")
|
||||||
|
|
||||||
|
module.exports = 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)
|
||||||
|
}
|
29
server/bootloader/startFileWatcher.js
Normal file
29
server/bootloader/startFileWatcher.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
const chokidar = require("chokidar")
|
||||||
|
const { minimatch } = require("minimatch")
|
||||||
|
|
||||||
|
const defaultIgnored = [
|
||||||
|
"**/.cache/**",
|
||||||
|
"**/node_modules/**",
|
||||||
|
"**/dist/**",
|
||||||
|
"**/build/**",
|
||||||
|
]
|
||||||
|
|
||||||
|
module.exports = async (fromPath, { onReload }) => {
|
||||||
|
console.log("[WATCHER] Starting watching path >", fromPath)
|
||||||
|
|
||||||
|
global._watcher = chokidar.watch(fromPath, {
|
||||||
|
ignored: (path) =>
|
||||||
|
defaultIgnored.some((pattern) => minimatch(path, pattern)),
|
||||||
|
persistent: true,
|
||||||
|
ignoreInitial: true,
|
||||||
|
awaitWriteFinish: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
global._watcher.on("all", (event, filePath) => {
|
||||||
|
console.log(`[WATCHER] Event [${event}] > ${filePath}`)
|
||||||
|
|
||||||
|
if (typeof onReload === "function") {
|
||||||
|
onReload()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "linebridge",
|
"name": "linebridge",
|
||||||
"version": "0.26.0",
|
"version": "1.0.0",
|
||||||
"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": {
|
||||||
@ -9,7 +9,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
"linebridge-boot": "./bin/boot.js"
|
"linebridge-boot": "./bootloader/bin"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
@ -20,32 +20,26 @@
|
|||||||
"./package.json"
|
"./package.json"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "hermes-node ./src/bin/server.js",
|
|
||||||
"build": "hermes build --parallel --clean",
|
"build": "hermes build --parallel --clean",
|
||||||
"test": "mocha"
|
"test": "mocha"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@foxify/events": "^2.1.0",
|
"@foxify/events": "^2.1.0",
|
||||||
"@gullerya/object-observer": "^6.1.3",
|
|
||||||
"@infisical/sdk": "^2.1.8",
|
"@infisical/sdk": "^2.1.8",
|
||||||
"@socket.io/cluster-adapter": "^0.2.2",
|
"@socket.io/cluster-adapter": "^0.2.2",
|
||||||
"@socket.io/redis-adapter": "^8.2.1",
|
"@socket.io/redis-adapter": "^8.2.1",
|
||||||
"@socket.io/redis-emitter": "^5.1.0",
|
"@socket.io/redis-emitter": "^5.1.0",
|
||||||
"@socket.io/sticky": "^1.0.4",
|
"@socket.io/sticky": "^1.0.4",
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.8.4",
|
||||||
"axios-retry": "3.4.0",
|
"chokidar": "^4.0.3",
|
||||||
"cors": "2.8.5",
|
"dotenv": "^16.5.0",
|
||||||
"dotenv": "^16.4.4",
|
|
||||||
"hyper-express": "^6.17.3",
|
"hyper-express": "^6.17.3",
|
||||||
"ioredis": "^5.3.2",
|
"ioredis": "^5.6.1",
|
||||||
"md5": "^2.3.0",
|
"minimatch": "^10.0.1",
|
||||||
"module-alias": "2.2.2",
|
"module-alias": "^2.2.2",
|
||||||
"morgan": "1.10.0",
|
|
||||||
"signal-exit": "^4.1.0",
|
"signal-exit": "^4.1.0",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
"socket.io-client": "^4.5.4",
|
"sucrase": "^3.35.0"
|
||||||
"sucrase": "^3.35.0",
|
|
||||||
"uuid": "^9.0.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ragestudio/hermes": "^1.0.0",
|
"@ragestudio/hermes": "^1.0.0",
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
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) => {
|
|
||||||
return {
|
|
||||||
name: globalThis._linebridge.name ?? "unknown",
|
|
||||||
version: projectPkg.version ?? "unknown",
|
|
||||||
engine: globalThis._linebridge.useEngine ?? "unknown",
|
|
||||||
request_time: new Date().getTime(),
|
|
||||||
lb_version: defaults.version ?? "unknown",
|
|
||||||
experimental: defaults.isExperimental.toString() ?? "unknown",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
import Endpoint from "../classes/endpoint"
|
|
||||||
|
|
||||||
export default class MainEndpoint extends Endpoint {
|
|
||||||
route = "/_map"
|
|
||||||
|
|
||||||
get = async (req, res) => {
|
|
||||||
const httpMap = Object.entries(this.server.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: []
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
20
server/src/baseRoutes/main.js
Executable file
20
server/src/baseRoutes/main.js
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
import path from "node:path"
|
||||||
|
|
||||||
|
import Route from "../classes/Route"
|
||||||
|
import Vars from "../vars"
|
||||||
|
|
||||||
|
export default class MainRoute extends Route {
|
||||||
|
static route = "/"
|
||||||
|
static useContexts = ["server"]
|
||||||
|
|
||||||
|
get = async (req, res, ctx) => {
|
||||||
|
return {
|
||||||
|
name: ctx.server.params.refName ?? "unknown",
|
||||||
|
version: Vars.projectPkg.version ?? "unknown",
|
||||||
|
engine: ctx.server.params.useEngine ?? "unknown",
|
||||||
|
lb_version: Vars.libPkg.version ?? "unknown",
|
||||||
|
experimental: ctx.server.isExperimental ?? "unknown",
|
||||||
|
request_time: new Date().getTime(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
server/src/baseRoutes/map.js
Executable file
27
server/src/baseRoutes/map.js
Executable file
@ -0,0 +1,27 @@
|
|||||||
|
import Route from "../classes/Route"
|
||||||
|
|
||||||
|
export default class MapRoute extends Route {
|
||||||
|
static route = "/_map"
|
||||||
|
|
||||||
|
get = async (req, res) => {
|
||||||
|
const httpMap = Array.from(this.server.engine.map.entries()).reduce(
|
||||||
|
(acc, [route, { method, path }]) => {
|
||||||
|
if (!acc[method]) {
|
||||||
|
acc[method] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
acc[method].push({
|
||||||
|
route: path,
|
||||||
|
})
|
||||||
|
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
|
||||||
|
return res.json({
|
||||||
|
http: httpMap,
|
||||||
|
websocket: [],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
17
server/src/classes/Endpoint/index.js
Normal file
17
server/src/classes/Endpoint/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { HttpRequestHandler } from "../Handler"
|
||||||
|
|
||||||
|
export default class Endpoint {
|
||||||
|
static _constructed = false
|
||||||
|
static _class = true
|
||||||
|
|
||||||
|
constructor(method, context) {
|
||||||
|
this._constructed = true
|
||||||
|
this.context = context
|
||||||
|
|
||||||
|
if (typeof method === "function") {
|
||||||
|
this.run = method
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handler = new HttpRequestHandler(this.run, this.context)
|
||||||
|
}
|
||||||
|
}
|
55
server/src/classes/Handler/index.js
Normal file
55
server/src/classes/Handler/index.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
export class Handler {
|
||||||
|
constructor(fn, ctx) {
|
||||||
|
this.fn = fn ?? (() => Promise.resolve())
|
||||||
|
this.ctx = ctx ?? {}
|
||||||
|
|
||||||
|
this.fn = this.fn.bind({
|
||||||
|
contexts: this.ctx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HttpRequestHandler extends Handler {
|
||||||
|
constructor(fn, ctx) {
|
||||||
|
super(fn, ctx)
|
||||||
|
|
||||||
|
return this.exec
|
||||||
|
}
|
||||||
|
|
||||||
|
exec = async (req, res) => {
|
||||||
|
try {
|
||||||
|
req.ctx = this.ctx
|
||||||
|
const result = await this.fn(req, res, this.ctx)
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
return res.json(result)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// handle if is a operation error
|
||||||
|
if (error instanceof OperationError) {
|
||||||
|
return res.status(error.code).json({
|
||||||
|
error: error.message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// if is not a operation error, that is a exception.
|
||||||
|
// gonna handle like a generic 500 error
|
||||||
|
console.error({
|
||||||
|
message: "Unhandled route error:",
|
||||||
|
description: error.stack,
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.status(500).json({
|
||||||
|
error: error.message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement MiddlewareHandler
|
||||||
|
export class MiddlewareHandler extends Handler {}
|
||||||
|
|
||||||
|
// TODO: Implement WebsocketRequestHandler
|
||||||
|
export class WebsocketRequestHandler extends Handler {}
|
||||||
|
|
||||||
|
export default Handler
|
@ -1,159 +1,162 @@
|
|||||||
import { EventEmitter } from "@foxify/events"
|
import { EventEmitter } from "@foxify/events"
|
||||||
|
|
||||||
export default class IPCClient {
|
export default class IPCClient {
|
||||||
constructor(self, _process) {
|
constructor(self, _process) {
|
||||||
this.self = self
|
this.self = self
|
||||||
this.process = _process
|
this.process = _process
|
||||||
|
|
||||||
this.process.on("message", (msg) => {
|
this.process.on("message", (msg) => {
|
||||||
if (typeof msg !== "object") {
|
if (typeof msg !== "object") {
|
||||||
// not an IPC message, ignore
|
// not an IPC message, ignore
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const { event, payload } = msg
|
const { event, payload } = msg
|
||||||
|
|
||||||
if (!event || !event.startsWith("ipc:")) {
|
if (!event || !event.startsWith("ipc:")) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log(`[IPC:CLIENT] Received event [${event}] from [${payload.from}]`)
|
//console.log(`[IPC:CLIENT] Received event [${event}] from [${payload.from}]`)
|
||||||
|
|
||||||
if (event.startsWith("ipc:exec")) {
|
if (event.startsWith("ipc:exec")) {
|
||||||
return this.handleExecution(payload)
|
return this.handleExecution(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.startsWith("ipc:akn")) {
|
if (event.startsWith("ipc:akn")) {
|
||||||
return this.handleAcknowledgement(payload)
|
return this.handleAcknowledgement(payload)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
eventBus = new EventEmitter()
|
eventBus = new EventEmitter()
|
||||||
|
|
||||||
handleExecution = async (payload) => {
|
handleExecution = async (payload) => {
|
||||||
let { id, command, args, from } = payload
|
if (typeof this.self.ipcEvents !== "object") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
let fn = this.self.ipcEvents[command]
|
let { id, command, args, from } = payload
|
||||||
|
|
||||||
if (!fn) {
|
let fn = this.self.ipcEvents[command]
|
||||||
this.process.send({
|
|
||||||
event: `ipc:akn:${id}`,
|
|
||||||
payload: {
|
|
||||||
target: from,
|
|
||||||
from: this.self.constructor.refName,
|
|
||||||
|
|
||||||
id: id,
|
if (typeof fn !== "function") {
|
||||||
error: `IPC: Command [${command}] not found`,
|
this.process.send({
|
||||||
}
|
event: `ipc:akn:${id}`,
|
||||||
})
|
payload: {
|
||||||
|
target: from,
|
||||||
|
from: this.self.params.refName,
|
||||||
|
|
||||||
return false
|
id: id,
|
||||||
}
|
error: `IPC: Command [${command}] not found`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
try {
|
return false
|
||||||
let result = await fn(this.self.contexts, ...args)
|
}
|
||||||
|
|
||||||
this.process.send({
|
try {
|
||||||
event: `ipc:akn:${id}`,
|
let result = await fn(this.self.contexts, ...args)
|
||||||
payload: {
|
|
||||||
target: from,
|
|
||||||
from: this.self.constructor.refName,
|
|
||||||
|
|
||||||
id: id,
|
this.process.send({
|
||||||
result: result,
|
event: `ipc:akn:${id}`,
|
||||||
}
|
payload: {
|
||||||
})
|
target: from,
|
||||||
} catch (error) {
|
from: this.self.params.refName,
|
||||||
this.process.send({
|
|
||||||
event: `ipc:akn:${id}`,
|
|
||||||
payload: {
|
|
||||||
target: from,
|
|
||||||
from: this.self.constructor.refName,
|
|
||||||
|
|
||||||
id: id,
|
id: id,
|
||||||
error: error.message,
|
result: result,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
} catch (error) {
|
||||||
}
|
this.process.send({
|
||||||
|
event: `ipc:akn:${id}`,
|
||||||
|
payload: {
|
||||||
|
target: from,
|
||||||
|
from: this.self.params.refName,
|
||||||
|
|
||||||
handleAcknowledgement = async (payload) => {
|
id: id,
|
||||||
let { id, result, error } = payload
|
error: error.message,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.eventBus.emit(`ipc:akn:${id}`, {
|
handleAcknowledgement = async (payload) => {
|
||||||
id: id,
|
let { id, result, error } = payload
|
||||||
result: result,
|
|
||||||
error: error,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// call a command on a remote service, and waits to get a response from akn (async)
|
this.eventBus.emit(`ipc:akn:${id}`, {
|
||||||
call = async (to_service_id, command, ...args) => {
|
id: id,
|
||||||
const remote_call_id = Date.now()
|
result: result,
|
||||||
|
error: error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
//console.debug(`[IPC:CLIENT] Invoking command [${command}] on service [${to_service_id}]`)
|
// 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) => {
|
//console.debug(`[IPC:CLIENT] Invoking command [${command}] on service [${to_service_id}]`)
|
||||||
try {
|
|
||||||
|
|
||||||
this.process.send({
|
const response = await new Promise((resolve, reject) => {
|
||||||
event: "ipc:exec",
|
try {
|
||||||
payload: {
|
this.process.send({
|
||||||
target: to_service_id,
|
event: "ipc:exec",
|
||||||
from: this.self.constructor.refName,
|
payload: {
|
||||||
|
target: to_service_id,
|
||||||
|
from: this.self.params.refName,
|
||||||
|
|
||||||
id: remote_call_id,
|
id: remote_call_id,
|
||||||
command,
|
command,
|
||||||
args,
|
args,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
this.eventBus.once(`ipc:akn:${remote_call_id}`, resolve)
|
this.eventBus.once(`ipc:akn:${remote_call_id}`, resolve)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
|
||||||
reject(error)
|
reject(error)
|
||||||
}
|
}
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
return {
|
return {
|
||||||
error: error
|
error: error,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
throw new OperationError(500, response.error)
|
throw new OperationError(500, response.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.result
|
return response.result
|
||||||
}
|
}
|
||||||
|
|
||||||
// call a command on a remote service, but return it immediately
|
// call a command on a remote service, but return it immediately
|
||||||
invoke = async (to_service_id, command, ...args) => {
|
invoke = async (to_service_id, command, ...args) => {
|
||||||
const remote_call_id = Date.now()
|
const remote_call_id = Date.now()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.process.send({
|
this.process.send({
|
||||||
event: "ipc:exec",
|
event: "ipc:exec",
|
||||||
payload: {
|
payload: {
|
||||||
target: to_service_id,
|
target: to_service_id,
|
||||||
from: this.self.constructor.refName,
|
from: this.self.params.refName,
|
||||||
|
|
||||||
id: remote_call_id,
|
id: remote_call_id,
|
||||||
command,
|
command,
|
||||||
args,
|
args,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: remote_call_id
|
id: remote_call_id,
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
error: error
|
error: error,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
58
server/src/classes/Route/index.js
Executable file
58
server/src/classes/Route/index.js
Executable file
@ -0,0 +1,58 @@
|
|||||||
|
import Endpoint from "../Endpoint"
|
||||||
|
|
||||||
|
export default class Route {
|
||||||
|
constructor(server, params = {}) {
|
||||||
|
if (!server) {
|
||||||
|
throw new Error("server is not defined")
|
||||||
|
}
|
||||||
|
|
||||||
|
this.server = server
|
||||||
|
this.params = {
|
||||||
|
route: this.constructor.route ?? "/",
|
||||||
|
useContexts: this.constructor.useContexts ?? [],
|
||||||
|
useMiddlewares: this.constructor.useMiddlewares ?? [],
|
||||||
|
...params,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof this.params.handlers === "object") {
|
||||||
|
for (const method of global._linebridge.params.httpMethods) {
|
||||||
|
if (typeof this.params.handlers[method] !== "function") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
this[method] = this.params.handlers[method]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.server.contexts && Array.isArray(this.params.useContexts)) {
|
||||||
|
for (const key of this.params.useContexts) {
|
||||||
|
this.ctx[key] = this.server.contexts[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = {}
|
||||||
|
|
||||||
|
register = () => {
|
||||||
|
for (const method of global._linebridge.params.httpMethods) {
|
||||||
|
if (typeof this[method] === "undefined") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(this[method] instanceof Endpoint)) {
|
||||||
|
if (this[method]._class && !this[method]._constructed) {
|
||||||
|
this[method] = new this[method](undefined, this.ctx)
|
||||||
|
} else {
|
||||||
|
this[method] = new Endpoint(this[method], this.ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.server.register.http({
|
||||||
|
method: method,
|
||||||
|
route: this.params.route,
|
||||||
|
middlewares: this.params.useMiddlewares,
|
||||||
|
fn: this[method].handler,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -167,7 +167,7 @@ class RTEngineNG {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
attach = async (engine) => {
|
attach = (engine) => {
|
||||||
this.engine = engine
|
this.engine = engine
|
||||||
|
|
||||||
this.engine.app.ws(this.config.path ?? `/`, this.handleConnection)
|
this.engine.app.ws(this.config.path ?? `/`, this.handleConnection)
|
@ -1,87 +0,0 @@
|
|||||||
export default class Endpoint {
|
|
||||||
constructor(server, params = {}, ctx = {}) {
|
|
||||||
this.server = server
|
|
||||||
this.params = params
|
|
||||||
this.ctx = ctx
|
|
||||||
|
|
||||||
if (!server) {
|
|
||||||
throw new Error("Server is not defined")
|
|
||||||
}
|
|
||||||
|
|
||||||
this.route = this.route ?? this.constructor.route ?? this.params.route
|
|
||||||
this.enabled = this.enabled ?? this.constructor.enabled ?? this.params.enabled ?? true
|
|
||||||
|
|
||||||
this.middlewares = [
|
|
||||||
...this.middlewares ?? [],
|
|
||||||
...this.params.middlewares ?? [],
|
|
||||||
]
|
|
||||||
|
|
||||||
if (this.params.handlers) {
|
|
||||||
for (const method of globalThis._linebridge.validHttpMethods) {
|
|
||||||
if (typeof this.params.handlers[method] === "function") {
|
|
||||||
this[method] = this.params.handlers[method]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.selfRegister()
|
|
||||||
|
|
||||||
if (Array.isArray(this.params.useContexts)) {
|
|
||||||
for (const contextRef of this.params.useContexts) {
|
|
||||||
this.endpointContext[contextRef] = this.server.contexts[contextRef]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
endpointContext = {}
|
|
||||||
|
|
||||||
createHandler(fn) {
|
|
||||||
fn = fn.bind(this.server)
|
|
||||||
|
|
||||||
return async (req, res) => {
|
|
||||||
try {
|
|
||||||
const result = await fn(req, res, this.endpointContext)
|
|
||||||
|
|
||||||
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 () => {
|
|
||||||
for await (const method of globalThis._linebridge.validHttpMethods) {
|
|
||||||
const methodHandler = this[method]
|
|
||||||
|
|
||||||
if (typeof methodHandler !== "undefined") {
|
|
||||||
const fn = this.createHandler(this[method].fn ?? this[method])
|
|
||||||
|
|
||||||
this.server.register.http(
|
|
||||||
{
|
|
||||||
method,
|
|
||||||
route: this.route,
|
|
||||||
middlewares: this.middlewares,
|
|
||||||
fn: fn,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
Controller: require("./controller"),
|
|
||||||
Endpoint: require("./endpoint"),
|
|
||||||
RTEngine: require("./rtengine"),
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
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",
|
|
||||||
],
|
|
||||||
}
|
|
80
server/src/engines/he-legacy/index.js
Executable file
80
server/src/engines/he-legacy/index.js
Executable file
@ -0,0 +1,80 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@ import redis from "ioredis"
|
|||||||
import SocketIO from "socket.io"
|
import SocketIO from "socket.io"
|
||||||
import { EventEmitter } from "@foxify/events"
|
import { EventEmitter } from "@foxify/events"
|
||||||
|
|
||||||
import RedisMap from "../../lib/redis_map"
|
import RedisMap from "./redis_map.js"
|
||||||
|
|
||||||
export default class RTEngineServer {
|
export default class RTEngineServer {
|
||||||
constructor(params = {}) {
|
constructor(params = {}) {
|
81
server/src/engines/he/index.js
Executable file
81
server/src/engines/he/index.js
Executable file
@ -0,0 +1,81 @@
|
|||||||
|
import he from "hyper-express"
|
||||||
|
import RtEngine from "../../classes/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({
|
||||||
|
path:
|
||||||
|
this.server.params.wsPath ??
|
||||||
|
`/${this.server.params.refName}`,
|
||||||
|
onUpgrade: this.server.handleWsUpgrade,
|
||||||
|
onConnection: this.server.handleWsConnection,
|
||||||
|
onDisconnect: this.server.handleWsDisconnect,
|
||||||
|
})
|
||||||
|
|
||||||
|
global.websockets = this.ws
|
||||||
|
|
||||||
|
this.ws.attach(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,100 +0,0 @@
|
|||||||
import he from "hyper-express"
|
|
||||||
import rtengineng from "../../classes/rtengineng"
|
|
||||||
|
|
||||||
export default class HyperExpressEngineNG {
|
|
||||||
constructor(ctx) {
|
|
||||||
this.ctx = ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
app = null
|
|
||||||
ws = null
|
|
||||||
router = null
|
|
||||||
|
|
||||||
initialize = async () => {
|
|
||||||
console.warn(
|
|
||||||
`hyper-express-ng is a experimental engine, some features may not be available or work properly!`,
|
|
||||||
)
|
|
||||||
|
|
||||||
const appParams = {
|
|
||||||
max_body_length: 50 * 1024 * 1024, //50MB in bytes,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.ctx.ssl) {
|
|
||||||
appParams.key_file_name = this.ctx.ssl?.key ?? null
|
|
||||||
appParams.cert_file_name = this.ctx.ssl?.cert ?? null
|
|
||||||
}
|
|
||||||
|
|
||||||
this.app = new he.Server(appParams)
|
|
||||||
|
|
||||||
this.router = new he.Router()
|
|
||||||
|
|
||||||
// create a router map
|
|
||||||
if (typeof this.router.map !== "object") {
|
|
||||||
this.router.map = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.router.any("*", (req, res) => {
|
|
||||||
return res.status(404).json({
|
|
||||||
code: 404,
|
|
||||||
message: "Not found",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
await this.app.use(async (req, res, next) => {
|
|
||||||
if (req.method === "OPTIONS") {
|
|
||||||
// handle cors
|
|
||||||
if (this.ctx.constructor.ignoreCors) {
|
|
||||||
res.setHeader("Access-Control-Allow-Methods", "*")
|
|
||||||
res.setHeader("Access-Control-Allow-Origin", "*")
|
|
||||||
res.setHeader("Access-Control-Allow-Headers", "*")
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (this.ctx.constructor.enableWebsockets === true) {
|
|
||||||
this.ws = global.websocket = new rtengineng({
|
|
||||||
path:
|
|
||||||
this.ctx.constructor.wsPath ??
|
|
||||||
`/${this.ctx.constructor.refName}`,
|
|
||||||
onUpgrade: this.ctx.handleWsUpgrade,
|
|
||||||
onConnection: this.ctx.handleWsConnection,
|
|
||||||
onDisconnect: this.ctx.handleWsDisconnect,
|
|
||||||
})
|
|
||||||
|
|
||||||
await this.ws.attach(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
listen = async () => {
|
|
||||||
await this.app.listen(this.ctx.constructor.listen_port)
|
|
||||||
}
|
|
||||||
|
|
||||||
// close must be synchronous
|
|
||||||
close = () => {
|
|
||||||
if (this.ws && typeof this.ws.close === "function") {
|
|
||||||
this.ws.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof this.app.close === "function") {
|
|
||||||
this.app.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof this.ctx.onClose === "function") {
|
|
||||||
this.ctx.onClose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,99 +0,0 @@
|
|||||||
import he from "hyper-express"
|
|
||||||
import rtengine from "../../classes/rtengine"
|
|
||||||
|
|
||||||
export default class Engine {
|
|
||||||
constructor(ctx) {
|
|
||||||
this.ctx = ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
app = null
|
|
||||||
router = null
|
|
||||||
ws = null
|
|
||||||
|
|
||||||
initialize = async () => {
|
|
||||||
const serverParams = {
|
|
||||||
max_body_length: 50 * 1024 * 1024, //50MB in bytes,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.ctx.ssl) {
|
|
||||||
serverParams.key_file_name = this.ctx.ssl?.key ?? null
|
|
||||||
serverParams.cert_file_name = this.ctx.ssl?.cert ?? null
|
|
||||||
}
|
|
||||||
|
|
||||||
this.app = new he.Server(serverParams)
|
|
||||||
|
|
||||||
this.router = new he.Router()
|
|
||||||
|
|
||||||
// create a router map
|
|
||||||
if (typeof this.router.map !== "object") {
|
|
||||||
this.router.map = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.router.any("*", (req, res) => {
|
|
||||||
return res.status(404).json({
|
|
||||||
code: 404,
|
|
||||||
message: "Not found",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
await this.app.use(async (req, res, next) => {
|
|
||||||
if (req.method === "OPTIONS") {
|
|
||||||
// handle cors
|
|
||||||
if (this.ctx.constructor.ignoreCors) {
|
|
||||||
res.setHeader("Access-Control-Allow-Methods", "*")
|
|
||||||
res.setHeader("Access-Control-Allow-Origin", "*")
|
|
||||||
res.setHeader("Access-Control-Allow-Headers", "*")
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (this.ctx.constructor.enableWebsockets) {
|
|
||||||
this.ws = global.websocket = new rtengine({
|
|
||||||
requireAuth: this.ctx.constructor.requiredWsAuth,
|
|
||||||
handleAuth: this.ctx.handleWsAuth,
|
|
||||||
root: `/${this.ctx.constructor.refName}`,
|
|
||||||
})
|
|
||||||
|
|
||||||
this.ws.initialize()
|
|
||||||
|
|
||||||
await this.ws.io.attachApp(this.app.uws_instance)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
listen = async () => {
|
|
||||||
await this.app.listen(this.ctx.constructor.listen_port)
|
|
||||||
}
|
|
||||||
|
|
||||||
// close should be synchronous
|
|
||||||
close = () => {
|
|
||||||
if (this.ws) {
|
|
||||||
this.ws.clear()
|
|
||||||
|
|
||||||
if (typeof this.ws?.close === "function") {
|
|
||||||
this.ws.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof this.app?.close === "function") {
|
|
||||||
this.app.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof this.ctx.onClose === "function") {
|
|
||||||
this.ctx.onClose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,9 @@
|
|||||||
import HyperExpress from "./hyper-express"
|
import HeLegacy from "./he-legacy"
|
||||||
import HyperExpressNG from "./hyper-express-ng"
|
import He from "./he"
|
||||||
import Worker from "./worker"
|
import Worker from "./worker"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
"hyper-express": HyperExpress,
|
"he-legacy": HeLegacy,
|
||||||
"hyper-express-ng": HyperExpressNG,
|
he: He,
|
||||||
worker: Worker,
|
worker: Worker,
|
||||||
}
|
}
|
||||||
|
@ -1,126 +1,118 @@
|
|||||||
import { EventEmitter } from "@foxify/events"
|
import { EventEmitter } from "@foxify/events"
|
||||||
|
|
||||||
class WorkerEngineRouter {
|
class WorkerEngineRouter {
|
||||||
routes = []
|
routes = []
|
||||||
|
|
||||||
get = (path, ...execs) => {
|
get = (path, ...execs) => {}
|
||||||
|
|
||||||
}
|
post = (path, ...execs) => {}
|
||||||
|
|
||||||
post = (path, ...execs) => {
|
delete = (path, ...execs) => {}
|
||||||
|
|
||||||
}
|
put = (path, ...execs) => {}
|
||||||
|
|
||||||
delete = (path, ...execs) => {
|
patch = (path, ...execs) => {}
|
||||||
|
|
||||||
}
|
head = (path, ...execs) => {}
|
||||||
|
|
||||||
put = (path, ...execs) => {
|
options = (path, ...execs) => {}
|
||||||
|
|
||||||
}
|
any = (path, ...execs) => {}
|
||||||
|
|
||||||
patch = (path, ...execs) => {
|
use = (path, ...execs) => {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
head = (path, ...execs) => {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
options = (path, ...execs) => {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
any = (path, ...execs) => {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
use = (path, ...execs) => {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class WorkerEngine {
|
class WorkerEngine {
|
||||||
static ipcPrefix = "rail:"
|
static ipcPrefix = "rail:"
|
||||||
|
|
||||||
selfId = process.env.lb_service.id
|
selfId = process.env.lb_service.id
|
||||||
|
|
||||||
router = new WorkerEngineRouter()
|
router = new WorkerEngineRouter()
|
||||||
|
|
||||||
eventBus = new EventEmitter()
|
eventBus = new EventEmitter()
|
||||||
|
|
||||||
perExecTail = []
|
perExecTail = []
|
||||||
|
|
||||||
initialize = async () => {
|
initialize = async () => {
|
||||||
console.error(`[WorkerEngine] Worker engine its not implemented yet...`)
|
console.error(`[WorkerEngine] Worker engine its not implemented yet...`)
|
||||||
|
|
||||||
process.on("message", this.handleIPCMessage)
|
process.on("message", this.handleIPCMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
listen = async () => {
|
listen = async () => {
|
||||||
console.log(`Sending Rail Register`)
|
console.log(`Sending Rail Register`)
|
||||||
|
|
||||||
process.send({
|
process.send({
|
||||||
type: "rail:register",
|
type: "rail:register",
|
||||||
data: {
|
data: {
|
||||||
id: process.env.lb_service.id,
|
id: process.env.lb_service.id,
|
||||||
pid: process.pid,
|
pid: process.pid,
|
||||||
routes: this.router.routes,
|
routes: this.router.routes,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleIPCMessage = async (msg) => {
|
handleIPCMessage = async (msg) => {
|
||||||
if (typeof msg !== "object") {
|
if (typeof msg !== "object") {
|
||||||
// ignore, its not for us
|
// ignore, its not for us
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!msg.event || !msg.event.startsWith(WorkerEngine.ipcPrefix)) {
|
if (!msg.event || !msg.event.startsWith(WorkerEngine.ipcPrefix)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const { event, payload } = msg
|
const { event, payload } = msg
|
||||||
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case "rail:request": {
|
case "rail:request": {
|
||||||
const { req } = payload
|
const { req } = payload
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case "rail:response": {
|
case "rail:response": {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
use = (fn) => {
|
||||||
}
|
if (fn instanceof WorkerEngineRouter) {
|
||||||
}
|
this.router = fn
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
use = (fn) => {
|
if (fn instanceof Function) {
|
||||||
if (fn instanceof WorkerEngineRouter) {
|
this.perExecTail.push(fn)
|
||||||
this.router = fn
|
return
|
||||||
return
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fn instanceof Function) {
|
|
||||||
this.perExecTail.push(fn)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Engine {
|
export default class Engine {
|
||||||
constructor(params) {
|
constructor(params) {
|
||||||
this.params = params
|
this.params = params
|
||||||
}
|
}
|
||||||
|
|
||||||
app = new WorkerEngine()
|
app = null
|
||||||
|
router = new WorkerEngineRouter()
|
||||||
|
map = new Map()
|
||||||
|
|
||||||
router = new WorkerEngineRouter()
|
initialize = async () => {
|
||||||
|
if (
|
||||||
|
!process.env.lb_service ||
|
||||||
|
process.env.lb_service?.type !== "worker"
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
"No worker environment detected!\nThis engine is only meant to be used in a worker environment\n",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
init = async () => {
|
this.app = new WorkerEngine()
|
||||||
await this.app.initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
listen = async () => {
|
await this.app.initialize()
|
||||||
await this.app.listen()
|
}
|
||||||
}
|
|
||||||
}
|
listen = async () => {
|
||||||
|
await this.app.listen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
Server: require("./server.js"),
|
Server: require("./server"),
|
||||||
Endpoint: require("./classes/endpoint"),
|
Route: require("./classes/Route"),
|
||||||
registerBaseAliases: require("./registerAliases"),
|
registerBaseAliases: require("./registerAliases"),
|
||||||
version: require("../package.json").version,
|
version: require("../package.json").version,
|
||||||
}
|
}
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
import fs from "node:fs"
|
|
||||||
import path from "node:path"
|
|
||||||
|
|
||||||
export default async (server) => {
|
|
||||||
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(server)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
import fs from "node:fs"
|
|
||||||
|
|
||||||
import Endpoint from "../../classes/endpoint"
|
|
||||||
import RecursiveRegister from "../../lib/recursiveRegister"
|
|
||||||
|
|
||||||
const parametersRegex = /\[([a-zA-Z0-9_]+)\]/g
|
|
||||||
|
|
||||||
export default async (startDir, engine, server) => {
|
|
||||||
if (!fs.existsSync(startDir)) {
|
|
||||||
return engine
|
|
||||||
}
|
|
||||||
|
|
||||||
await RecursiveRegister({
|
|
||||||
start: startDir,
|
|
||||||
match: async (filePath) => {
|
|
||||||
return filePath.endsWith(".js") || filePath.endsWith(".ts")
|
|
||||||
},
|
|
||||||
onMatch: async ({ absolutePath, relativePath }) => {
|
|
||||||
const paths = relativePath.split("/")
|
|
||||||
|
|
||||||
let method = paths[paths.length - 1].split(".")[0].toLocaleLowerCase()
|
|
||||||
let route = paths.slice(0, paths.length - 1).join("/")
|
|
||||||
|
|
||||||
// parse parametrized routes
|
|
||||||
route = route.replace(parametersRegex, ":$1")
|
|
||||||
route = route.replace("[$]", "*")
|
|
||||||
|
|
||||||
// clean up
|
|
||||||
route = route.replace(".js", "")
|
|
||||||
route = route.replace(".ts", "")
|
|
||||||
|
|
||||||
// check if route ends with index
|
|
||||||
if (route.endsWith("/index")) {
|
|
||||||
route = route.replace("/index", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// add leading slash
|
|
||||||
route = `/${route}`
|
|
||||||
|
|
||||||
// import route
|
|
||||||
let fn = require(absolutePath)
|
|
||||||
|
|
||||||
fn = fn.default ?? fn
|
|
||||||
|
|
||||||
if (typeof fn !== "function") {
|
|
||||||
if (!fn.fn) {
|
|
||||||
console.warn(`Missing fn handler in [${method}][${route}]`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(fn.useContext)) {
|
|
||||||
let contexts = {}
|
|
||||||
|
|
||||||
for (const context of fn.useContext) {
|
|
||||||
contexts[context] = server.contexts[context]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn.contexts = contexts
|
|
||||||
|
|
||||||
fn.fn.bind({ contexts })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
new Endpoint(
|
|
||||||
server,
|
|
||||||
{
|
|
||||||
route: route,
|
|
||||||
enabled: true,
|
|
||||||
middlewares: fn.middlewares,
|
|
||||||
handlers: {
|
|
||||||
[method]: fn.fn ?? fn,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return engine
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
import fs from "node:fs"
|
|
||||||
|
|
||||||
import getRouteredFunctions from "../../utils/getRouteredFunctions"
|
|
||||||
import flatRouteredFunctions from "../../utils/flatRouteredFunctions"
|
|
||||||
|
|
||||||
export default async (startDir, engine) => {
|
|
||||||
if (!engine.ws || !fs.existsSync(startDir)) {
|
|
||||||
return engine
|
|
||||||
}
|
|
||||||
|
|
||||||
let events = await getRouteredFunctions(startDir)
|
|
||||||
|
|
||||||
events = flatRouteredFunctions(events)
|
|
||||||
|
|
||||||
if (typeof events !== "object") {
|
|
||||||
return engine
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof engine.ws.registerEvents === "function") {
|
|
||||||
await engine.ws.registerEvents(events)
|
|
||||||
} else {
|
|
||||||
for (const eventKey of Object.keys(events)) {
|
|
||||||
engine.ws.events.set(eventKey, events[eventKey])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return engine
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
const { Controller } = require("../../classes/controller")
|
|
||||||
const generateEndpointsFromDir = require("../generateEndpointsFromDir")
|
|
||||||
|
|
||||||
function generateControllerFromEndpointsDir(dir, controllerName) {
|
|
||||||
const endpoints = generateEndpointsFromDir(dir)
|
|
||||||
|
|
||||||
return class extends Controller {
|
|
||||||
static refName = controllerName
|
|
||||||
|
|
||||||
get = endpoints.get
|
|
||||||
post = endpoints.post
|
|
||||||
put = endpoints.put
|
|
||||||
patch = endpoints.patch
|
|
||||||
delete = endpoints.delete
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = generateControllerFromEndpointsDir
|
|
@ -1,23 +0,0 @@
|
|||||||
const loadEndpointsFromDir = require("../loadEndpointsFromDir")
|
|
||||||
|
|
||||||
function generateEndpointsFromDir(dir) {
|
|
||||||
const loadedEndpoints = loadEndpointsFromDir(dir)
|
|
||||||
|
|
||||||
// filter by methods
|
|
||||||
const endpointsByMethods = Object()
|
|
||||||
|
|
||||||
for (const endpointKey in loadedEndpoints) {
|
|
||||||
const endpoint = loadedEndpoints[endpointKey]
|
|
||||||
const method = endpoint.method.toLowerCase()
|
|
||||||
|
|
||||||
if (!endpointsByMethods[method]) {
|
|
||||||
endpointsByMethods[method] = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
endpointsByMethods[method][endpoint.route] = loadedEndpoints[endpointKey]
|
|
||||||
}
|
|
||||||
|
|
||||||
return endpointsByMethods
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = generateEndpointsFromDir
|
|
@ -1,41 +0,0 @@
|
|||||||
const fs = require("node:fs")
|
|
||||||
const path = require("node:path")
|
|
||||||
|
|
||||||
function loadEndpointsFromDir(dir) {
|
|
||||||
if (!dir) {
|
|
||||||
throw new Error("No directory provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(dir)) {
|
|
||||||
throw new Error(`Directory [${dir}] does not exist`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// scan the directory for files
|
|
||||||
const files = fs.readdirSync(dir)
|
|
||||||
|
|
||||||
// create an object to store the endpoints
|
|
||||||
const endpoints = {}
|
|
||||||
|
|
||||||
// loop through the files
|
|
||||||
for (const file of files) {
|
|
||||||
// get the full path of the file
|
|
||||||
const filePath = path.join(dir, file)
|
|
||||||
|
|
||||||
// get the file stats
|
|
||||||
const stats = fs.statSync(filePath)
|
|
||||||
|
|
||||||
// if the file is a directory, recursively call this function
|
|
||||||
if (stats.isDirectory()) {
|
|
||||||
endpoints[file] = loadEndpointsFromDir(filePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the file is a javascript file, require it and add it to the endpoints object
|
|
||||||
if (stats.isFile() && path.extname(filePath) === ".js") {
|
|
||||||
endpoints[path.basename(file, ".js")] = require(filePath).default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return endpoints
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = loadEndpointsFromDir
|
|
@ -1,8 +0,0 @@
|
|||||||
import cors from "cors"
|
|
||||||
|
|
||||||
export default cors({
|
|
||||||
origin: "*",
|
|
||||||
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "CONNECT", "TRACE"],
|
|
||||||
preflightContinue: false,
|
|
||||||
optionsSuccessStatus: 204,
|
|
||||||
})
|
|
@ -1,3 +1,15 @@
|
|||||||
import OperationError from "./classes/operation_error"
|
import OperationError from "./classes/OperationError"
|
||||||
|
import Endpoint from "./classes/Endpoint"
|
||||||
|
import {
|
||||||
|
Handler,
|
||||||
|
HttpRequestHandler,
|
||||||
|
MiddlewareHandler,
|
||||||
|
WebsocketRequestHandler,
|
||||||
|
} from "./classes/Handler"
|
||||||
|
|
||||||
global.OperationError = OperationError
|
global.OperationError = OperationError
|
||||||
|
global.Endpoint = Endpoint
|
||||||
|
global.Handler = Handler
|
||||||
|
global.HttpRequestHandler = HttpRequestHandler
|
||||||
|
global.MiddlewareHandler = MiddlewareHandler
|
||||||
|
global.WebsocketRequestHandler = WebsocketRequestHandler
|
||||||
|
22
server/src/registers/baseHeaders.js
Normal file
22
server/src/registers/baseHeaders.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import Vars from "../vars"
|
||||||
|
|
||||||
|
export default (server) => {
|
||||||
|
if (!server || !server.headers || !server.engine || !server.engine.app) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let headers = {
|
||||||
|
...server.headers,
|
||||||
|
...Vars.baseHeaders,
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = Object.entries(headers)
|
||||||
|
|
||||||
|
server.engine.app.use((req, res, next) => {
|
||||||
|
for (let i = 0; i < headers.length; i++) {
|
||||||
|
res.setHeader(headers[i][0], headers[i][1])
|
||||||
|
}
|
||||||
|
|
||||||
|
next()
|
||||||
|
})
|
||||||
|
}
|
14
server/src/registers/baseMiddlewares.js
Normal file
14
server/src/registers/baseMiddlewares.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import composeMiddlewares from "../utils/composeMiddlewares"
|
||||||
|
import Vars from "../vars"
|
||||||
|
|
||||||
|
export default async (server) => {
|
||||||
|
const middlewares = composeMiddlewares(
|
||||||
|
{ ...server.middlewares, ...Vars.baseMiddlewares },
|
||||||
|
server.params.useMiddlewares,
|
||||||
|
"/*",
|
||||||
|
)
|
||||||
|
|
||||||
|
middlewares.forEach((middleware) => {
|
||||||
|
server.engine.app.use(middleware)
|
||||||
|
})
|
||||||
|
}
|
21
server/src/registers/baseRoutes.js
Executable file
21
server/src/registers/baseRoutes.js
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
import fs from "node:fs"
|
||||||
|
import path from "node:path"
|
||||||
|
|
||||||
|
import Vars from "../vars"
|
||||||
|
|
||||||
|
export default async (server) => {
|
||||||
|
const scanPath = path.resolve(Vars.libPath, "baseRoutes")
|
||||||
|
const files = fs.readdirSync(scanPath)
|
||||||
|
|
||||||
|
for await (const file of files) {
|
||||||
|
if (file === "index.js") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let RouteModule = await import(path.join(scanPath, file))
|
||||||
|
|
||||||
|
RouteModule = RouteModule.default
|
||||||
|
|
||||||
|
new RouteModule(server).register()
|
||||||
|
}
|
||||||
|
}
|
6
server/src/registers/bypassCorsHeaders.js
Normal file
6
server/src/registers/bypassCorsHeaders.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export default (server) => {
|
||||||
|
server.headers["Access-Control-Allow-Origin"] = "*"
|
||||||
|
server.headers["Access-Control-Allow-Methods"] = "*"
|
||||||
|
server.headers["Access-Control-Allow-Headers"] = "*"
|
||||||
|
server.headers["Access-Control-Allow-Credentials"] = "true"
|
||||||
|
}
|
64
server/src/registers/httpFileRoutes.js
Executable file
64
server/src/registers/httpFileRoutes.js
Executable file
@ -0,0 +1,64 @@
|
|||||||
|
import fs from "node:fs"
|
||||||
|
|
||||||
|
import Route from "../classes/Route"
|
||||||
|
import RecursiveRegister from "../utils/recursiveRegister"
|
||||||
|
|
||||||
|
const parametersRegex = /\[([a-zA-Z0-9_]+)\]/g
|
||||||
|
|
||||||
|
export default async (startDir, server) => {
|
||||||
|
if (!fs.existsSync(startDir)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
await RecursiveRegister({
|
||||||
|
start: startDir,
|
||||||
|
match: async (filePath) => {
|
||||||
|
return filePath.endsWith(".js") || filePath.endsWith(".ts")
|
||||||
|
},
|
||||||
|
onMatch: async ({ absolutePath, relativePath }) => {
|
||||||
|
const paths = relativePath.split("/")
|
||||||
|
|
||||||
|
let method = paths[paths.length - 1]
|
||||||
|
.split(".")[0]
|
||||||
|
.toLocaleLowerCase()
|
||||||
|
let route = paths.slice(0, paths.length - 1).join("/")
|
||||||
|
|
||||||
|
// parse parametrized routes
|
||||||
|
route = route.replace(parametersRegex, ":$1")
|
||||||
|
route = route.replace("[$]", "*")
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
route = route.replace(".js", "")
|
||||||
|
route = route.replace(".ts", "")
|
||||||
|
|
||||||
|
// check if route ends with index
|
||||||
|
if (route.endsWith("/index")) {
|
||||||
|
route = route.replace("/index", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// add leading slash
|
||||||
|
route = `/${route}`
|
||||||
|
|
||||||
|
// import endpoint
|
||||||
|
let fileObj = await import(absolutePath)
|
||||||
|
|
||||||
|
fileObj = fileObj.default ?? fileObj
|
||||||
|
|
||||||
|
if (typeof fileObj !== "function") {
|
||||||
|
if (typeof fileObj.fn !== "function") {
|
||||||
|
console.warn(`Missing fn handler in [${method}][${route}]`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new Route(server, {
|
||||||
|
route: route,
|
||||||
|
useMiddlewares: fileObj.useMiddlewares,
|
||||||
|
useContexts: fileObj.useContexts,
|
||||||
|
handlers: {
|
||||||
|
[method]: fileObj.fn ?? fileObj,
|
||||||
|
},
|
||||||
|
}).register()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
47
server/src/registers/ipcService.js
Normal file
47
server/src/registers/ipcService.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
export default async (server) => {
|
||||||
|
if (!process.env.lb_service || !process.send) {
|
||||||
|
console.error("IPC not available")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// get only the root paths
|
||||||
|
let paths = Array.from(server.engine.map.keys()).map((key) => {
|
||||||
|
const root = key.split("/")[1]
|
||||||
|
|
||||||
|
return "/" + root
|
||||||
|
})
|
||||||
|
|
||||||
|
// remove duplicates
|
||||||
|
paths = [...new Set(paths)]
|
||||||
|
|
||||||
|
// remove "" and _map
|
||||||
|
paths = paths.filter((key) => {
|
||||||
|
if (key === "/" || key === "/_map") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
process.send({
|
||||||
|
type: "service:register",
|
||||||
|
id: process.env.lb_service.id,
|
||||||
|
index: process.env.lb_service.index,
|
||||||
|
register: {
|
||||||
|
namespace: server.params.refName,
|
||||||
|
http: {
|
||||||
|
enabled: true,
|
||||||
|
paths: paths,
|
||||||
|
proto: server.hasSSL ? "https" : "http",
|
||||||
|
},
|
||||||
|
websocket: {
|
||||||
|
enabled: server.params.websockets,
|
||||||
|
path: server.params.refName ?? `/${server.params.refName}`,
|
||||||
|
},
|
||||||
|
listen: {
|
||||||
|
ip: server.params.listenIp,
|
||||||
|
port: server.params.listenPort,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
28
server/src/registers/websocketFileEvents.js
Executable file
28
server/src/registers/websocketFileEvents.js
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
import fs from "node:fs"
|
||||||
|
|
||||||
|
import getRouteredFunctions from "../utils/getRouteredFunctions"
|
||||||
|
import flatRouteredFunctions from "../utils/flatRouteredFunctions"
|
||||||
|
|
||||||
|
export default async (startDir, server) => {
|
||||||
|
if (!server.engine.ws || !fs.existsSync(startDir)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let events = await getRouteredFunctions(startDir)
|
||||||
|
|
||||||
|
events = flatRouteredFunctions(events)
|
||||||
|
|
||||||
|
if (typeof events !== "object") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof server.engine.ws.registerEvents === "function") {
|
||||||
|
await server.engine.ws.registerEvents(events)
|
||||||
|
} else {
|
||||||
|
for (const eventKey of Object.keys(events)) {
|
||||||
|
server.engine.ws.events.set(eventKey, events[eventKey])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return server
|
||||||
|
}
|
@ -1,136 +1,126 @@
|
|||||||
import("./patches")
|
import("./patches")
|
||||||
|
|
||||||
import fs from "node:fs"
|
|
||||||
import path from "node:path"
|
|
||||||
import { EventEmitter } from "@foxify/events"
|
import { EventEmitter } from "@foxify/events"
|
||||||
|
|
||||||
import defaults from "./defaults"
|
|
||||||
|
|
||||||
import IPCClient from "./classes/IPCClient"
|
import IPCClient from "./classes/IPCClient"
|
||||||
import Endpoint from "./classes/endpoint"
|
import Route from "./classes/Route"
|
||||||
|
|
||||||
import registerBaseEndpoints from "./initializators/registerBaseEndpoints"
|
import registerBaseRoutes from "./registers/baseRoutes"
|
||||||
import registerWebsocketsEvents from "./initializators/registerWebsocketsEvents"
|
import registerBaseMiddlewares from "./registers/baseMiddlewares"
|
||||||
import registerHttpRoutes from "./initializators/registerHttpRoutes"
|
import registerBaseHeaders from "./registers/baseHeaders"
|
||||||
|
import registerWebsocketsFileEvents from "./registers/websocketFileEvents"
|
||||||
|
import registerHttpFileRoutes from "./registers/httpFileRoutes"
|
||||||
|
import registerServiceToIPC from "./registers/ipcService"
|
||||||
|
import bypassCorsHeaders from "./registers/bypassCorsHeaders"
|
||||||
|
|
||||||
|
import isExperimental from "./utils/isExperimental"
|
||||||
|
import getHostAddress from "./utils/getHostAddress"
|
||||||
|
import composeMiddlewares from "./utils/composeMiddlewares"
|
||||||
|
|
||||||
|
import Vars from "./vars"
|
||||||
import Engines from "./engines"
|
import Engines from "./engines"
|
||||||
|
|
||||||
class Server {
|
class Server {
|
||||||
constructor(params = {}, controllers = {}, middlewares = {}, headers = {}) {
|
constructor(params = {}) {
|
||||||
this.isExperimental = defaults.isExperimental ?? false
|
|
||||||
|
|
||||||
if (this.isExperimental) {
|
if (this.isExperimental) {
|
||||||
console.warn("\n🚧 This version of Linebridge is experimental! 🚧")
|
console.warn("\n🚧 This version of Linebridge is experimental! 🚧")
|
||||||
console.warn(`Version: ${defaults.version}\n`)
|
console.warn(`Version: ${Vars.libPkg.version}\n`)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.params = {
|
this.params = {
|
||||||
...defaults.params,
|
...Vars.defaultParams,
|
||||||
...(params.default ?? params),
|
...params,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.controllers = {
|
// overrides some params with constructor values
|
||||||
...(controllers.default ?? controllers),
|
if (typeof this.constructor.refName === "string") {
|
||||||
|
this.params.refName = this.constructor.refName
|
||||||
}
|
}
|
||||||
|
|
||||||
this.middlewares = {
|
if (typeof this.constructor.useEngine === "string") {
|
||||||
...(middlewares.default ?? middlewares),
|
this.params.useEngine = this.constructor.useEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
this.headers = {
|
if (typeof this.constructor.listenIp === "string") {
|
||||||
...defaults.headers,
|
this.params.listenIp = this.constructor.listenIp
|
||||||
...(headers.default ?? headers),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fix and fulfill params
|
if (
|
||||||
this.params.useMiddlewares = this.params.useMiddlewares ?? []
|
typeof this.constructor.listenPort === "string" ||
|
||||||
|
typeof this.constructor.listenPort === "number"
|
||||||
|
) {
|
||||||
|
this.params.listenPort = this.constructor.listenPort
|
||||||
|
}
|
||||||
|
|
||||||
this.params.name = this.constructor.refName ?? this.params.refName
|
if (typeof this.constructor.websockets === "boolean") {
|
||||||
|
this.params.websockets = this.constructor.websockets
|
||||||
|
}
|
||||||
|
|
||||||
this.params.useEngine =
|
if (typeof this.constructor.bypassCors === "boolean") {
|
||||||
this.constructor.useEngine ??
|
this.params.bypassCors = this.constructor.bypassCors
|
||||||
this.params.useEngine ??
|
}
|
||||||
"hyper-express"
|
|
||||||
|
|
||||||
this.params.listen_ip =
|
if (typeof this.constructor.baseRoutes === "boolean") {
|
||||||
this.constructor.listenIp ??
|
this.params.baseRoutes = this.constructor.baseRoutes
|
||||||
this.constructor.listen_ip ??
|
}
|
||||||
this.params.listen_ip ??
|
|
||||||
"0.0.0.0"
|
|
||||||
|
|
||||||
this.params.listen_port =
|
if (typeof this.constructor.routesPath === "string") {
|
||||||
this.constructor.listenPort ??
|
this.params.routesPath = this.constructor.routesPath
|
||||||
this.constructor.listen_port ??
|
}
|
||||||
this.params.listen_port ??
|
|
||||||
3000
|
|
||||||
|
|
||||||
this.params.http_protocol = this.params.http_protocol ?? "http"
|
if (typeof this.constructor.wsRoutesPath === "string") {
|
||||||
|
this.params.wsRoutesPath = this.constructor.wsRoutesPath
|
||||||
|
}
|
||||||
|
|
||||||
this.params.http_address = `${this.params.http_protocol}://${defaults.localhost_address}:${this.params.listen_port}`
|
if (typeof this.constructor.useMiddlewares !== "undefined") {
|
||||||
|
if (!Array.isArray(this.constructor.useMiddlewares)) {
|
||||||
|
this.constructor.useMiddlewares = [
|
||||||
|
this.constructor.useMiddlewares,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
this.params.enableWebsockets =
|
this.params.useMiddlewares = this.constructor.useMiddlewares
|
||||||
this.constructor.enableWebsockets ??
|
}
|
||||||
this.params.enableWebsockets ??
|
|
||||||
false
|
|
||||||
|
|
||||||
this.params.ignoreCors =
|
global._linebridge = {
|
||||||
this.constructor.ignoreCors ?? this.params.ignoreCors ?? true
|
vars: Vars,
|
||||||
|
params: this.params,
|
||||||
this.params.disableBaseEndpoints =
|
|
||||||
this.constructor.disableBaseEndpoints ??
|
|
||||||
this.params.disableBaseEndpoints ??
|
|
||||||
false
|
|
||||||
|
|
||||||
this.params.routesPath =
|
|
||||||
this.constructor.routesPath ??
|
|
||||||
this.params.routesPath ??
|
|
||||||
path.resolve(process.cwd(), "routes")
|
|
||||||
|
|
||||||
this.params.wsRoutesPath =
|
|
||||||
this.constructor.wsRoutesPath ??
|
|
||||||
this.params.wsRoutesPath ??
|
|
||||||
path.resolve(process.cwd(), "routes_ws")
|
|
||||||
|
|
||||||
globalThis._linebridge = {
|
|
||||||
name: this.params.name,
|
|
||||||
useEngine: this.params.useEngine,
|
|
||||||
listenIp: this.params.listen_ip,
|
|
||||||
listenPort: this.params.listen_port,
|
|
||||||
httpProtocol: this.params.http_protocol,
|
|
||||||
httpAddress: this.params.http_address,
|
|
||||||
enableWebsockets: this.params.enableWebsockets,
|
|
||||||
ignoreCors: this.params.ignoreCors,
|
|
||||||
routesPath: this.params.routesPath,
|
|
||||||
validHttpMethods: defaults.valid_http_methods,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eventBus = new EventEmitter()
|
||||||
|
middlewares = {}
|
||||||
|
headers = {}
|
||||||
|
events = {}
|
||||||
|
contexts = {}
|
||||||
|
|
||||||
engine = null
|
engine = null
|
||||||
|
|
||||||
events = null
|
get hasSSL() {
|
||||||
|
if (!this.ssl) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
ipc = null
|
return this.ssl.key && this.ssl.cert
|
||||||
|
}
|
||||||
|
|
||||||
ipcEvents = null
|
get isExperimental() {
|
||||||
|
return isExperimental()
|
||||||
eventBus = new EventEmitter()
|
}
|
||||||
|
|
||||||
initialize = async () => {
|
initialize = async () => {
|
||||||
const startHrTime = process.hrtime()
|
const startHrTime = process.hrtime()
|
||||||
|
|
||||||
// register events
|
// resolve current local private address of the host
|
||||||
if (this.events) {
|
this.localAddress = getHostAddress()
|
||||||
if (this.events.default) {
|
|
||||||
this.events = this.events.default
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [eventName, eventHandler] of Object.entries(
|
this.contexts["server"] = this
|
||||||
this.events,
|
|
||||||
)) {
|
// register declared events to eventBus
|
||||||
this.eventBus.on(eventName, eventHandler)
|
for (const [eventName, eventHandler] of Object.entries(this.events)) {
|
||||||
}
|
this.eventBus.on(eventName, eventHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize engine
|
// initialize engine
|
||||||
@ -140,28 +130,19 @@ class Server {
|
|||||||
throw new Error(`Engine ${this.params.useEngine} not found`)
|
throw new Error(`Engine ${this.params.useEngine} not found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// construct engine instance
|
||||||
|
// important, pass this instance to the engine constructor
|
||||||
this.engine = new this.engine(this)
|
this.engine = new this.engine(this)
|
||||||
|
|
||||||
|
// fire engine initialization
|
||||||
if (typeof this.engine.initialize === "function") {
|
if (typeof this.engine.initialize === "function") {
|
||||||
await this.engine.initialize()
|
await this.engine.initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if ws events are defined
|
// at this point, we wanna to pass to onInitialize hook,
|
||||||
if (typeof this.wsEvents !== "undefined") {
|
// a simple base context, without any registers extra
|
||||||
if (!this.engine.ws) {
|
|
||||||
console.warn(
|
|
||||||
"`wsEvents` detected, but Websockets are not enabled! Ignoring...",
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
for (const [eventName, eventHandler] of Object.entries(
|
|
||||||
this.wsEvents,
|
|
||||||
)) {
|
|
||||||
this.engine.ws.events.set(eventName, eventHandler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to execute onInitialize hook
|
// fire onInitialize hook
|
||||||
if (typeof this.onInitialize === "function") {
|
if (typeof this.onInitialize === "function") {
|
||||||
try {
|
try {
|
||||||
await this.onInitialize()
|
await this.onInitialize()
|
||||||
@ -171,211 +152,123 @@ class Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set defaults
|
// Now gonna initialize the final steps & registers
|
||||||
this.useDefaultHeaders()
|
|
||||||
this.useDefaultMiddlewares()
|
|
||||||
|
|
||||||
if (this.routes) {
|
// bypassCors if needed
|
||||||
|
if (this.params.bypassCors) {
|
||||||
|
bypassCorsHeaders(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
// register base headers & middlewares
|
||||||
|
registerBaseHeaders(this)
|
||||||
|
registerBaseMiddlewares(this)
|
||||||
|
|
||||||
|
// if websocket enabled, lets do some work
|
||||||
|
if (typeof this.engine.ws === "object") {
|
||||||
|
// register declared ws events
|
||||||
|
if (typeof this.wsEvents === "object") {
|
||||||
|
for (const [eventName, eventHandler] of Object.entries(
|
||||||
|
this.wsEvents,
|
||||||
|
)) {
|
||||||
|
this.engine.ws.events.set(eventName, eventHandler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now, initialize declared routes with Endpoint class
|
||||||
|
if (typeof this.routes === "object") {
|
||||||
for (const [route, endpoint] of Object.entries(this.routes)) {
|
for (const [route, endpoint] of Object.entries(this.routes)) {
|
||||||
this.engine.router.map[route] = new Endpoint(this, {
|
new Route(this, {
|
||||||
...endpoint,
|
...endpoint,
|
||||||
route: route,
|
route: route,
|
||||||
handlers: {
|
handlers: {
|
||||||
[endpoint.method.toLowerCase()]: endpoint.fn,
|
[endpoint.method.toLowerCase()]: endpoint.fn,
|
||||||
},
|
},
|
||||||
})
|
}).register()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// register http & ws routes
|
// register http file routes
|
||||||
this.engine = await registerHttpRoutes(
|
await registerHttpFileRoutes(this.params.routesPath, this)
|
||||||
this.params.routesPath,
|
|
||||||
this.engine,
|
|
||||||
this,
|
|
||||||
)
|
|
||||||
this.engine = await registerWebsocketsEvents(
|
|
||||||
this.params.wsRoutesPath,
|
|
||||||
this.engine,
|
|
||||||
)
|
|
||||||
|
|
||||||
// register base endpoints if enabled
|
// register ws file routes
|
||||||
if (!this.params.disableBaseEndpoints) {
|
await registerWebsocketsFileEvents(this.params.wsRoutesPath, this)
|
||||||
await registerBaseEndpoints(this)
|
|
||||||
|
// register base routes if enabled
|
||||||
|
if (this.params.baseRoutes == true) {
|
||||||
|
await registerBaseRoutes(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
// use main router
|
// if is a linebridge service, then initialize IPC Channels
|
||||||
await this.engine.app.use(this.engine.router)
|
|
||||||
|
|
||||||
// if is a linebridge service then initialize IPC Channels
|
|
||||||
if (process.env.lb_service) {
|
if (process.env.lb_service) {
|
||||||
await this.initializeIpc()
|
console.info("🚄 Starting IPC client")
|
||||||
await this.registerServiceToIPC()
|
this.ipc = global.ipc = new IPCClient(this, process)
|
||||||
}
|
|
||||||
|
|
||||||
// try to execute beforeInitialize hook.
|
await registerServiceToIPC(this)
|
||||||
if (typeof this.afterInitialize === "function") {
|
|
||||||
await this.afterInitialize()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// listen
|
// listen
|
||||||
await this.engine.listen()
|
await this.engine.listen()
|
||||||
|
|
||||||
|
// execute afterInitialize hook.
|
||||||
|
if (typeof this.afterInitialize === "function") {
|
||||||
|
await this.afterInitialize()
|
||||||
|
}
|
||||||
|
|
||||||
// calculate elapsed time on ms, to fixed 2
|
// calculate elapsed time on ms, to fixed 2
|
||||||
const elapsedHrTime = process.hrtime(startHrTime)
|
const elapsedHrTime = process.hrtime(startHrTime)
|
||||||
const elapsedTimeInMs = elapsedHrTime[0] * 1e3 + elapsedHrTime[1] / 1e6
|
const elapsedTimeInMs = elapsedHrTime[0] * 1e3 + elapsedHrTime[1] / 1e6
|
||||||
|
|
||||||
console.info(
|
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 \n\t - Websocket: ${this.engine.ws ? "Enabled" : "Disabled"}`,
|
`🛰 Server ready!\n\t - ${this.hasSSL ? "https" : "http"}://${this.params.listenIp}:${this.params.listenPort} \n\t - Websocket: ${this.engine.ws ? "Enabled" : "Disabled"} \n\t - Routes: ${this.engine.map.size} \n\t - Tooks: ${elapsedTimeInMs.toFixed(2)}ms \n\t `,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeIpc = async () => {
|
|
||||||
console.info("🚄 Starting IPC client")
|
|
||||||
|
|
||||||
this.ipc = global.ipc = new IPCClient(this, process)
|
|
||||||
}
|
|
||||||
|
|
||||||
useDefaultHeaders = () => {
|
|
||||||
this.engine.app.use((req, res, next) => {
|
|
||||||
Object.keys(this.headers).forEach((key) => {
|
|
||||||
res.setHeader(key, this.headers[key])
|
|
||||||
})
|
|
||||||
|
|
||||||
next()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
useDefaultMiddlewares = async () => {
|
|
||||||
const middlewares = await this.resolveMiddlewares([
|
|
||||||
...this.params.useMiddlewares,
|
|
||||||
...(this.useMiddlewares ?? []),
|
|
||||||
...defaults.useMiddlewares,
|
|
||||||
])
|
|
||||||
|
|
||||||
middlewares.forEach((middleware) => {
|
|
||||||
this.engine.app.use(middleware)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
register = {
|
register = {
|
||||||
http: (endpoint, ..._middlewares) => {
|
http: (obj) => {
|
||||||
// check and fix method
|
// check and fix method
|
||||||
endpoint.method = endpoint.method?.toLowerCase() ?? "get"
|
obj.method = obj.method?.toLowerCase() ?? "get"
|
||||||
|
|
||||||
if (defaults.fixed_http_methods[endpoint.method]) {
|
if (Vars.fixedHttpMethods[obj.method]) {
|
||||||
endpoint.method = defaults.fixed_http_methods[endpoint.method]
|
obj.method = Vars.fixedHttpMethods[obj.method]
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if method is supported
|
// check if method is supported
|
||||||
if (typeof this.engine.router[endpoint.method] !== "function") {
|
if (typeof this.engine.router[obj.method] !== "function") {
|
||||||
throw new Error(`Method [${endpoint.method}] is not supported!`)
|
throw new Error(`Method [${obj.method}] is not supported!`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// grab the middlewares
|
// compose the middlewares
|
||||||
let middlewares = [..._middlewares]
|
obj.middlewares = composeMiddlewares(
|
||||||
|
{ ...this.middlewares, ...Vars.baseMiddlewares },
|
||||||
|
obj.middlewares,
|
||||||
|
`[${obj.method.toUpperCase()}] ${obj.route}`,
|
||||||
|
)
|
||||||
|
|
||||||
if (endpoint.middlewares) {
|
// set to the endpoints map, used by _map
|
||||||
if (!Array.isArray(endpoint.middlewares)) {
|
this.engine.map.set(obj.route, {
|
||||||
endpoint.middlewares = [endpoint.middlewares]
|
method: obj.method,
|
||||||
}
|
path: obj.route,
|
||||||
|
})
|
||||||
middlewares = [
|
|
||||||
...middlewares,
|
|
||||||
...this.resolveMiddlewares(endpoint.middlewares),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
this.engine.router.map[endpoint.route] = {
|
|
||||||
method: endpoint.method,
|
|
||||||
path: endpoint.route,
|
|
||||||
}
|
|
||||||
|
|
||||||
// register endpoint to http interface router
|
// register endpoint to http interface router
|
||||||
this.engine.router[endpoint.method](
|
this.engine.router[obj.method](
|
||||||
endpoint.route,
|
obj.route,
|
||||||
...middlewares,
|
...obj.middlewares,
|
||||||
endpoint.fn,
|
obj.fn,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
ws: (wsEndpointObj) => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveMiddlewares = (requestedMiddlewares) => {
|
_fireClose = () => {
|
||||||
const middlewares = {
|
if (typeof this.onClose === "function") {
|
||||||
...this.middlewares,
|
this.onClose()
|
||||||
...defaults.middlewares,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof requestedMiddlewares === "string") {
|
if (typeof this.engine.close === "function") {
|
||||||
requestedMiddlewares = [requestedMiddlewares]
|
this.engine.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
const execs = []
|
|
||||||
|
|
||||||
requestedMiddlewares.forEach((middlewareKey) => {
|
|
||||||
if (typeof middlewareKey === "string") {
|
|
||||||
if (typeof middlewares[middlewareKey] !== "function") {
|
|
||||||
throw new Error(`Middleware ${middlewareKey} not found!`)
|
|
||||||
}
|
|
||||||
|
|
||||||
execs.push(middlewares[middlewareKey])
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof middlewareKey === "function") {
|
|
||||||
execs.push(middlewareKey)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return execs
|
|
||||||
}
|
|
||||||
|
|
||||||
registerServiceToIPC = () => {
|
|
||||||
if (!process.env.lb_service || !process.send) {
|
|
||||||
console.error("IPC not available")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// get only the root paths
|
|
||||||
let paths = Object.keys(this.engine.router.map).map((key) => {
|
|
||||||
const root = key.split("/")[1]
|
|
||||||
|
|
||||||
return "/" + root
|
|
||||||
})
|
|
||||||
|
|
||||||
// remove duplicates
|
|
||||||
paths = [...new Set(paths)]
|
|
||||||
|
|
||||||
// remove "" and _map
|
|
||||||
paths = paths.filter((key) => {
|
|
||||||
if (key === "/" || key === "/_map") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
process.send({
|
|
||||||
type: "service:register",
|
|
||||||
id: process.env.lb_service.id,
|
|
||||||
index: process.env.lb_service.index,
|
|
||||||
register: {
|
|
||||||
namespace: this.constructor.refName,
|
|
||||||
http: {
|
|
||||||
enabled: true,
|
|
||||||
paths: paths,
|
|
||||||
proto: this.ssl?.key && this.ssl?.cert ? "https" : "http",
|
|
||||||
},
|
|
||||||
websocket: {
|
|
||||||
enabled: this.constructor.enableWebsockets,
|
|
||||||
path:
|
|
||||||
this.constructor.wsPath ??
|
|
||||||
`/${this.constructor.refName}`,
|
|
||||||
},
|
|
||||||
listen: {
|
|
||||||
ip: this.params.listen_ip,
|
|
||||||
port: this.params.listen_port,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
29
server/src/utils/composeMiddlewares.js
Normal file
29
server/src/utils/composeMiddlewares.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
export default (middlewares, selectors, endpointRef) => {
|
||||||
|
if (!middlewares || !selectors) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof selectors === "string") {
|
||||||
|
selectors = [selectors]
|
||||||
|
}
|
||||||
|
|
||||||
|
const execs = []
|
||||||
|
|
||||||
|
selectors.forEach((middlewareKey) => {
|
||||||
|
if (typeof middlewareKey === "string") {
|
||||||
|
if (typeof middlewares[middlewareKey] !== "function") {
|
||||||
|
throw new Error(
|
||||||
|
`Required middleware [${middlewareKey}] not found!\n\t- Required by endpoint > ${endpointRef}\n\n`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
execs.push(middlewares[middlewareKey])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof middlewareKey === "function") {
|
||||||
|
execs.push(middlewareKey)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return execs
|
||||||
|
}
|
23
server/src/utils/getHostAddress.js
Normal file
23
server/src/utils/getHostAddress.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import os from "node:os"
|
||||||
|
|
||||||
|
export default 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"
|
||||||
|
}
|
@ -1 +0,0 @@
|
|||||||
export { default as Schematized } from "./schematized"
|
|
8
server/src/utils/isExperimental.js
Normal file
8
server/src/utils/isExperimental.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import fs from "node:fs"
|
||||||
|
import path from "node:path"
|
||||||
|
|
||||||
|
import Vars from "../vars"
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return fs.existsSync(path.resolve(Vars.rootLibPath, ".experimental"))
|
||||||
|
}
|
48
server/src/vars.js
Executable file
48
server/src/vars.js
Executable file
@ -0,0 +1,48 @@
|
|||||||
|
import path from "node:path"
|
||||||
|
|
||||||
|
const rootLibPath = path.resolve(__dirname, "../")
|
||||||
|
const packageJSON = require(rootLibPath, "../package.json")
|
||||||
|
const projectPkg = require(path.resolve(process.cwd(), "package.json"))
|
||||||
|
|
||||||
|
export default {
|
||||||
|
libPath: __dirname,
|
||||||
|
rootLibPath: rootLibPath,
|
||||||
|
libPkg: packageJSON,
|
||||||
|
projectCwd: process.cwd(),
|
||||||
|
projectPkg: projectPkg,
|
||||||
|
defaultParams: {
|
||||||
|
refName: "linebridge",
|
||||||
|
listenIp: "0.0.0.0",
|
||||||
|
listenPort: 3000,
|
||||||
|
useEngine: "he",
|
||||||
|
websockets: false,
|
||||||
|
bypassCors: false,
|
||||||
|
baseRoutes: true,
|
||||||
|
routesPath: path.resolve(process.cwd(), "routes"),
|
||||||
|
wsRoutesPath: path.resolve(process.cwd(), "ws_routes"),
|
||||||
|
useMiddlewares: [],
|
||||||
|
httpMethods: [
|
||||||
|
"get",
|
||||||
|
"post",
|
||||||
|
"put",
|
||||||
|
"patch",
|
||||||
|
"del",
|
||||||
|
"delete",
|
||||||
|
"trace",
|
||||||
|
"head",
|
||||||
|
"any",
|
||||||
|
"options",
|
||||||
|
"ws",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
baseHeaders: {
|
||||||
|
server: "linebridge",
|
||||||
|
"lb-version": packageJSON.version,
|
||||||
|
},
|
||||||
|
baseMiddlewares: {
|
||||||
|
logs: require("./middlewares/logger").default,
|
||||||
|
},
|
||||||
|
fixedHttpMethods: {
|
||||||
|
del: "delete",
|
||||||
|
},
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user