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