mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 10:34:17 +00:00
212 lines
5.5 KiB
JavaScript
212 lines
5.5 KiB
JavaScript
import httpProxy from "http-proxy"
|
|
import defaults from "linebridge/dist/server/defaults"
|
|
|
|
import pkg from "../package.json"
|
|
|
|
import http from "node:http"
|
|
import https from "node:https"
|
|
|
|
import fs from "node:fs"
|
|
import path from "node:path"
|
|
|
|
function getHttpServerEngine(extraOptions = {}, handler = () => { }) {
|
|
const sslKey = path.resolve(process.cwd(), "ssl", "privkey.pem")
|
|
const sslCert = path.resolve(process.cwd(), "ssl", "cert.pem")
|
|
|
|
if (fs.existsSync(sslKey) && fs.existsSync(sslCert)) {
|
|
return https.createServer(
|
|
{
|
|
key: fs.readFileSync(sslKey),
|
|
cert: fs.readFileSync(sslCert),
|
|
...extraOptions
|
|
},
|
|
handler
|
|
)
|
|
} else {
|
|
return http.createServer(extraOptions, handler)
|
|
}
|
|
}
|
|
|
|
export default class Proxy {
|
|
constructor() {
|
|
this.proxys = new Map()
|
|
this.wsProxys = new Map()
|
|
|
|
this.http = getHttpServerEngine({}, this.handleHttpRequest)
|
|
this.http.on("upgrade", this.handleHttpUpgrade)
|
|
}
|
|
|
|
http = null
|
|
|
|
register = ({ serviceId, path, target, pathRewrite, ws } = {}) => {
|
|
if (!path) {
|
|
throw new Error("Path is required")
|
|
}
|
|
|
|
if (!target) {
|
|
throw new Error("Target is required")
|
|
}
|
|
|
|
if (this.proxys.has(path)) {
|
|
console.warn(`Proxy already registered [${path}], skipping...`)
|
|
return false
|
|
}
|
|
|
|
const proxy = httpProxy.createProxyServer({
|
|
target: target,
|
|
})
|
|
|
|
proxy.on("proxyReq", (proxyReq, req, res, options) => {
|
|
proxyReq.setHeader("x-linebridge-version", pkg.version)
|
|
proxyReq.setHeader("x-forwarded-for", req.socket.remoteAddress)
|
|
})
|
|
|
|
proxy.on("error", (e) => {
|
|
console.error(e)
|
|
})
|
|
|
|
const proxyObj = {
|
|
serviceId: serviceId ?? "default_service",
|
|
path: path,
|
|
target: target,
|
|
pathRewrite: pathRewrite,
|
|
proxy: proxy,
|
|
}
|
|
|
|
if (ws) {
|
|
console.log(`🔗 Registering websocket proxy [${path}] -> [${target}]`)
|
|
this.wsProxys.set(path, proxyObj)
|
|
} else {
|
|
console.log(`🔗 Registering path proxy [${path}] -> [${target}]`)
|
|
this.proxys.set(path, proxyObj)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
unregister = (path) => {
|
|
if (!this.proxys.has(path)) {
|
|
console.warn(`Proxy not registered [${path}], skipping...`)
|
|
return false
|
|
}
|
|
|
|
console.log(`🔗 Unregistering path proxy [${path}]`)
|
|
|
|
this.proxys.get(path).proxy.close()
|
|
this.proxys.delete(path)
|
|
}
|
|
|
|
unregisterAllFromService = (serviceId) => {
|
|
this.proxys.forEach((value, key) => {
|
|
if (value.serviceId === serviceId) {
|
|
this.unregister(value.path)
|
|
}
|
|
})
|
|
}
|
|
|
|
listen = async (port = 9000, host = "0.0.0.0", cb) => {
|
|
return await new Promise((resolve, reject) => {
|
|
this.http.listen(port, host, () => {
|
|
console.log(`🔗 Proxy listening on ${host}:${port}`)
|
|
|
|
if (cb) {
|
|
cb(this)
|
|
}
|
|
|
|
resolve(this)
|
|
})
|
|
})
|
|
}
|
|
|
|
rewritePath = (rewriteConfig, path) => {
|
|
let result = path
|
|
const rules = []
|
|
|
|
for (const [key, value] of Object.entries(rewriteConfig)) {
|
|
rules.push({
|
|
regex: new RegExp(key),
|
|
value: value,
|
|
})
|
|
}
|
|
|
|
for (const rule of rules) {
|
|
if (rule.regex.test(path)) {
|
|
result = result.replace(rule.regex, rule.value)
|
|
break
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
setCorsHeaders = (res) => {
|
|
res.setHeader("Access-Control-Allow-Origin", "*")
|
|
res.setHeader("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
|
|
res.setHeader("Access-Control-Allow-Headers", "*")
|
|
|
|
return res
|
|
}
|
|
|
|
handleHttpRequest = (req, res) => {
|
|
res = this.setCorsHeaders(res)
|
|
|
|
const sanitizedUrl = req.url.split("?")[0]
|
|
|
|
// preflight continue with code 204
|
|
if (req.method === "OPTIONS") {
|
|
res.statusCode = 204
|
|
res.end()
|
|
return
|
|
}
|
|
|
|
if (sanitizedUrl === "/") {
|
|
return res.end(`
|
|
{
|
|
"name": "${pkg.name}",
|
|
"version": "${pkg.version}",
|
|
"lb_version": "${defaults.version}"
|
|
}
|
|
`)
|
|
}
|
|
|
|
const namespace = `/${sanitizedUrl.split("/")[1]}`
|
|
const route = this.proxys.get(namespace)
|
|
|
|
if (!route) {
|
|
res.statusCode = 404
|
|
res.end(`
|
|
{
|
|
"error": "404 Not found"
|
|
}
|
|
`)
|
|
return
|
|
}
|
|
|
|
if (route.pathRewrite) {
|
|
req.url = this.rewritePath(route.pathRewrite, req.url)
|
|
}
|
|
|
|
route.proxy.web(req, res)
|
|
}
|
|
|
|
handleHttpUpgrade = (req, socket, head) => {
|
|
const namespace = `/${req.url.split("/")[1]}`
|
|
const route = this.wsProxys.get(namespace)
|
|
|
|
if (!route) {
|
|
// destroy socket
|
|
socket.destroy()
|
|
return false
|
|
}
|
|
|
|
if (route.pathRewrite) {
|
|
req.url = this.rewritePath(route.pathRewrite, req.url)
|
|
}
|
|
|
|
route.proxy.ws(req, socket, head)
|
|
}
|
|
|
|
close = () => {
|
|
this.http.close()
|
|
}
|
|
} |