improve gateway

This commit is contained in:
SrGooglo 2025-02-26 20:39:33 +00:00
parent 539539db34
commit 192e0e9bd8
2 changed files with 460 additions and 415 deletions

View File

@ -0,0 +1,22 @@
# Default services allocation ports
3000 -> main
3001 -> posts
3002 -> files
3003 -> music
3004 -> chats
3005 -> marketplace
3006 -> sync
3007 -> ems (External Messaging Service)
3008 -> users
3009 -> notifications
3010 -> search
3011 -> unallocated
3012 -> unallocated
3013 -> unallocated
3014 -> unallocated
3015 -> unallocated
3016 -> unallocated
3017 -> unallocated
3018 -> unallocated
3019 -> unallocated
3020 -> auth

View File

@ -22,420 +22,443 @@ import comtyAscii from "./ascii"
import pkg from "../package.json" import pkg from "../package.json"
const useLoadSpinner = process.argv.includes("--load-spinner") const useLoadSpinner = process.argv.includes("--load-spinner")
const isProduction = process.env.NODE_ENV === "production"
export default class Gateway { export default class Gateway {
spinnies = new Spinnies() spinnies = new Spinnies()
eventBus = new EventEmitter() eventBus = new EventEmitter()
state = { state = {
proxyPort: 9000, proxyPort: 9000,
internalIp: "0.0.0.0", internalIp: "0.0.0.0",
allReady: false allReady: false,
} }
selectedProcessInstance = null selectedProcessInstance = null
instancePool = [] instancePool = []
services = [] services = []
serviceRegistry = Observable.from({}) serviceRegistry = Observable.from({})
serviceFileReference = {} serviceFileReference = {}
proxy = null proxy = null
ipcRouter = null ipcRouter = null
async createServicesWatchers() { async createServicesWatchers() {
for await (let service of this.services) { for await (let service of this.services) {
const instanceFile = path.basename(service) const instanceFile = path.basename(service)
const instanceBasePath = path.dirname(service) const instanceBasePath = path.dirname(service)
const servicePkg = require(path.resolve(instanceBasePath, "package.json")) const servicePkg = require(
path.resolve(instanceBasePath, "package.json"),
this.serviceFileReference[instanceFile] = servicePkg.name )
this.serviceRegistry[servicePkg.name] = { this.serviceFileReference[instanceFile] = servicePkg.name
index: this.services.indexOf(service),
id: servicePkg.name, this.serviceRegistry[servicePkg.name] = {
version: servicePkg.version, index: this.services.indexOf(service),
file: instanceFile, id: servicePkg.name,
cwd: instanceBasePath, version: servicePkg.version,
buffer: [], file: instanceFile,
ready: false, cwd: instanceBasePath,
} buffer: [],
} ready: false,
} }
}
async createServicesProcess() { }
for await (let service of this.services) {
const { id, version, cwd } = this.serviceRegistry[this.serviceFileReference[path.basename(service)]] async createServicesProcess() {
for await (let service of this.services) {
this.serviceHandlers.onStarting(id) const { id, version, cwd } =
this.serviceRegistry[
const instance = await spawnService({ this.serviceFileReference[path.basename(service)]
id, ]
service,
cwd, this.serviceHandlers.onStarting(id)
onReload: this.serviceHandlers.onReload,
onClose: this.serviceHandlers.onClose, const instance = await spawnService({
onError: this.serviceHandlers.onError, id,
onIPCData: this.serviceHandlers.onIPCData, service,
}) cwd,
onReload: this.serviceHandlers.onReload,
if (!useLoadSpinner) { onClose: this.serviceHandlers.onClose,
instance.logs.attach() onError: this.serviceHandlers.onError,
} onIPCData: this.serviceHandlers.onIPCData,
})
const serviceInstance = {
id, if (!useLoadSpinner) {
version, instance.logs.attach()
instance }
}
const serviceInstance = {
// push to pool id,
this.instancePool.push(serviceInstance) version,
instance,
// if is NODE_ENV to development, start a file watcher for hot-reload }
if (process.env.NODE_ENV === "development") {
const ignored = [ // push to pool
...await getIgnoredFiles(cwd), this.instancePool.push(serviceInstance)
"**/.cache/**",
"**/node_modules/**", // if is development, start a file watcher for hot-reload
"**/dist/**", if (!isProduction) {
"**/build/**", const ignored = [
] ...(await getIgnoredFiles(cwd)),
"**/.cache/**",
const watcher = chokidar.watch(cwd, { "**/node_modules/**",
ignored: ignored, "**/dist/**",
persistent: true, "**/build/**",
ignoreInitial: true, ]
})
const watcher = chokidar.watch(cwd, {
watcher.on("all", (event, path) => { ignored: ignored,
// find instance from pool persistent: true,
const instanceIndex = this.instancePool.findIndex((instance) => instance.id === id) ignoreInitial: true,
})
console.log(event, path, instanceIndex)
watcher.on("all", (event, path) => {
// reload // find instance from pool
this.instancePool[instanceIndex].instance.reload() const instanceIndex = this.instancePool.findIndex(
}) (instance) => instance.id === id,
} )
}
} console.log(event, path, instanceIndex)
serviceHandlers = { // reload
onStarting: (id) => { this.instancePool[instanceIndex].instance.reload()
if (this.serviceRegistry[id].ready === false) { })
if (useLoadSpinner) { }
this.spinnies.add(id, { }
text: `📦 [${id}] Loading service...`, }
spinner: DefaultSpinner
}) serviceHandlers = {
} onStarting: (id) => {
} if (this.serviceRegistry[id].ready === false) {
}, if (useLoadSpinner) {
onStarted: (id) => { this.spinnies.add(id, {
this.serviceRegistry[id].initialized = true text: `📦 [${id}] Loading service...`,
spinner: DefaultSpinner,
if (this.serviceRegistry[id].ready === false) { })
if (useLoadSpinner) { }
if (this.spinnies.pick(id)) { }
this.spinnies.succeed(id, { text: `[${id}][${this.serviceRegistry[id].index}] Ready` }) },
} onStarted: (id) => {
} this.serviceRegistry[id].initialized = true
}
if (this.serviceRegistry[id].ready === false) {
this.serviceRegistry[id].ready = true if (useLoadSpinner) {
}, if (this.spinnies.pick(id)) {
onIPCData: async (id, msg) => { this.spinnies.succeed(id, {
if (msg.type === "log") { text: `[${id}][${this.serviceRegistry[id].index}] Ready`,
console.log(`[${id}] ${msg.message}`) })
} }
}
if (msg.status === "ready") { }
await this.serviceHandlers.onStarted(id)
} this.serviceRegistry[id].ready = true
},
if (msg.type === "router:register") { onIPCData: async (id, msg) => {
if (msg.data.path_overrides) { if (msg.type === "log") {
for await (let pathOverride of msg.data.path_overrides) { console.log(`[${id}] ${msg.message}`)
await this.proxy.register({ }
serviceId: id,
path: `/${pathOverride}`, if (msg.status === "ready") {
target: `http://${this.state.internalIp}:${msg.data.listen.port}/${pathOverride}`, await this.serviceHandlers.onStarted(id)
pathRewrite: { }
[`^/${pathOverride}`]: "",
}, if (msg.type === "router:register") {
}) if (msg.data.path_overrides) {
} for await (let pathOverride of msg.data.path_overrides) {
} else { await this.proxy.register({
await this.proxy.register({ serviceId: id,
serviceId: id, path: `/${pathOverride}`,
path: `/${id}`, target: `http://${this.state.internalIp}:${msg.data.listen.port}/${pathOverride}`,
target: `http://${msg.data.listen.ip}:${msg.data.listen.port}`, pathRewrite: {
}) [`^/${pathOverride}`]: "",
} },
} })
}
if (msg.type === "router:ws:register") { } else {
await this.proxy.register({ await this.proxy.register({
serviceId: id, serviceId: id,
path: `/${msg.data.namespace}`, path: `/${id}`,
target: `http://${this.state.internalIp}:${msg.data.listen.port}/${msg.data.namespace}`, target: `http://${msg.data.listen.ip}:${msg.data.listen.port}`,
pathRewrite: { })
[`^/${msg.data.namespace}`]: "", }
}, }
ws: true,
}) if (msg.type === "router:ws:register") {
} await this.proxy.register({
}, serviceId: id,
onReload: async ({ id, service, cwd, }) => { path: `/${msg.data.namespace}`,
console.log(`[onReload] ${id} ${service}`) target: `http://${this.state.internalIp}:${msg.data.listen.port}/${msg.data.namespace}`,
pathRewrite: {
let instance = this.instancePool.find((instance) => instance.id === id) [`^/${msg.data.namespace}`]: "",
},
if (!instance) { ws: true,
console.error(`❌ Service ${id} not found`) })
return false }
} },
onReload: async ({ id, service, cwd }) => {
// if (this.selectedProcessInstance) { console.log(`[onReload] ${id} ${service}`)
// if (this.selectedProcessInstance === "all") {
// this.std.detachAllServicesSTD() let instance = this.instancePool.find(
// } else if (this.selectedProcessInstance.id === id) { (instance) => instance.id === id,
// this.selectedProcessInstance.instance.logs.detach() )
// }
// } if (!instance) {
console.error(`❌ Service ${id} not found`)
this.ipcRouter.unregister({ id, instance }) return false
}
// try to unregister from proxy
this.proxy.unregisterAllFromService(id) // if (this.selectedProcessInstance) {
// if (this.selectedProcessInstance === "all") {
await instance.instance.kill("SIGINT") // this.std.detachAllServicesSTD()
// } else if (this.selectedProcessInstance.id === id) {
instance.instance = await spawnService({ // this.selectedProcessInstance.instance.logs.detach()
id, // }
service, // }
cwd,
onReload: this.serviceHandlers.onReload, this.ipcRouter.unregister({ id, instance })
onClose: this.serviceHandlers.onClose,
onError: this.serviceHandlers.onError, // try to unregister from proxy
onIPCData: this.serviceHandlers.onIPCData, this.proxy.unregisterAllFromService(id)
})
await instance.instance.kill("SIGINT")
const instanceIndex = this.instancePool.findIndex((_instance) => _instance.id === id)
instance.instance = await spawnService({
if (instanceIndex !== -1) { id,
this.instancePool[instanceIndex] = instance service,
} cwd,
onReload: this.serviceHandlers.onReload,
if (this.selectedProcessInstance) { onClose: this.serviceHandlers.onClose,
if (this.selectedProcessInstance === "all") { onError: this.serviceHandlers.onError,
this.std.attachAllServicesSTD() onIPCData: this.serviceHandlers.onIPCData,
} else if (this.selectedProcessInstance.id === id) { })
this.std.attachServiceSTD(id)
} const instanceIndex = this.instancePool.findIndex(
} (_instance) => _instance.id === id,
}, )
onClose: (id, code, err) => {
this.serviceRegistry[id].initialized = true if (instanceIndex !== -1) {
this.instancePool[instanceIndex] = instance
if (this.serviceRegistry[id].ready === false) { }
if (this.spinnies.pick(id)) {
this.spinnies.fail(id, { text: `[${id}][${this.serviceRegistry[id].index}] Failed with code ${code}` }) if (this.selectedProcessInstance) {
} if (this.selectedProcessInstance === "all") {
} this.std.attachAllServicesSTD()
} else if (this.selectedProcessInstance.id === id) {
console.log(`[${id}] Exit with code ${code}`) this.std.attachServiceSTD(id)
}
if (err) { }
console.error(err) },
} onClose: (id, code, err) => {
this.serviceRegistry[id].initialized = true
// try to unregister from proxy
this.proxy.unregisterAllFromService(id) if (this.serviceRegistry[id].ready === false) {
if (this.spinnies.pick(id)) {
this.serviceRegistry[id].ready = false this.spinnies.fail(id, {
}, text: `[${id}][${this.serviceRegistry[id].index}] Failed with code ${code}`,
onError: (id, err) => { })
console.error(`[${id}] Error`, err) }
}, }
}
console.log(`[${id}] Exit with code ${code}`)
onAllServicesReload = (id) => {
for (let instance of this.instancePool) { if (err) {
instance.instance.reload() console.error(err)
} }
}
// try to unregister from proxy
onAllServicesReady = async () => { this.proxy.unregisterAllFromService(id)
if (this.state.allReady) {
return false this.serviceRegistry[id].ready = false
} },
onError: (id, err) => {
console.clear() console.error(`[${id}] Error`, err)
},
this.state.allReady = true }
console.log(comtyAscii) onAllServicesReload = (id) => {
console.log(`🎉 All services[${this.services.length}] ready!\n`) for (let instance of this.instancePool) {
console.log(`USE: select <service>, reload, exit`) instance.instance.reload()
}
await this.proxy.listen(this.state.proxyPort, this.state.internalIp) }
if (useLoadSpinner) { onAllServicesReady = async () => {
if (!this.selectedProcessInstance) { if (this.state.allReady) {
this.std.detachAllServicesSTD() return false
this.std.attachAllServicesSTD() }
}
} console.clear()
}
this.state.allReady = true
onGatewayExit = (code, signal) => {
console.clear() console.log(comtyAscii)
console.log(`\n🛑 Preparing to exit...`) console.log(`🎉 All services[${this.services.length}] ready!\n`)
console.log(`USE: select <service>, reload, exit`)
console.log(`Stoping proxy...`)
await this.proxy.listen(this.state.proxyPort, this.state.internalIp)
this.proxy.close()
if (useLoadSpinner) {
console.log(`Kill all ${this.instancePool.length} instances...`) if (!this.selectedProcessInstance) {
this.std.detachAllServicesSTD()
for (let instance of this.instancePool) { this.std.attachAllServicesSTD()
console.log(`Killing ${instance.id} [${instance.instance.pid}]`) }
}
instance.instance.kill() }
treeKill(instance.instance.pid) onGatewayExit = (code, signal) => {
} console.clear()
console.log(`\n🛑 Preparing to exit...`)
treeKill(process.pid)
} console.log(`Stoping proxy...`)
std = { this.proxy.close()
reloadService: () => {
if (!this.selectedProcessInstance) { console.log(`Kill all ${this.instancePool.length} instances...`)
console.error(`No service selected`)
return false for (let instance of this.instancePool) {
} console.log(`Killing ${instance.id} [${instance.instance.pid}]`)
if (this.selectedProcessInstance === "all") { instance.instance.kill()
return this.onAllServicesReload()
} treeKill(instance.instance.pid)
}
return this.selectedProcessInstance.instance.reload()
}, treeKill(process.pid)
findServiceById: (id) => { }
if (!isNaN(parseInt(id))) {
// find by index number std = {
id = this.serviceRegistry[Object.keys(this.serviceRegistry)[id]] reloadService: () => {
} else { if (!this.selectedProcessInstance) {
// find by id console.error(`No service selected`)
id = this.serviceRegistry[id] return false
} }
return id if (this.selectedProcessInstance === "all") {
}, return this.onAllServicesReload()
attachServiceSTD: (id) => { }
console.log(`Attaching service [${id}]`)
return this.selectedProcessInstance.instance.reload()
if (id === "all") { },
console.clear() findServiceById: (id) => {
this.selectedProcessInstance = "all" if (!isNaN(parseInt(id))) {
return this.std.attachAllServicesSTD() // find by index number
} id = this.serviceRegistry[Object.keys(this.serviceRegistry)[id]]
} else {
const service = this.std.findServiceById(id) // find by id
id = this.serviceRegistry[id]
if (!service) { }
console.error(`Service [${service}] not found`)
return false return id
} },
attachServiceSTD: (id) => {
this.selectedProcessInstance = this.instancePool.find((instance) => instance.id === service.id) console.log(`Attaching service [${id}]`)
if (!this.selectedProcessInstance) { if (id === "all") {
this.selectedProcessInstance = null console.clear()
this.selectedProcessInstance = "all"
console.error(`Cannot find service [${service.id}] in the instances pool`) return this.std.attachAllServicesSTD()
}
return false
} const service = this.std.findServiceById(id)
this.std.detachAllServicesSTD() if (!service) {
console.clear() console.error(`Service [${service}] not found`)
this.selectedProcessInstance.instance.logs.attach() return false
}
return true
}, this.selectedProcessInstance = this.instancePool.find(
dettachServiceSTD: (id) => { (instance) => instance.id === service.id,
)
}, if (!this.selectedProcessInstance) {
attachAllServicesSTD: () => { this.selectedProcessInstance = null
this.std.detachAllServicesSTD()
console.error(
for (let service of this.instancePool) { `Cannot find service [${service.id}] in the instances pool`,
service.instance.logs.attach() )
}
}, return false
detachAllServicesSTD: () => { }
for (let service of this.instancePool) {
service.instance.logs.detach() this.std.detachAllServicesSTD()
} console.clear()
}, this.selectedProcessInstance.instance.logs.attach()
}
return true
async initialize() { },
onExit(this.onGatewayExit) dettachServiceSTD: (id) => {},
attachAllServicesSTD: () => {
process.stdout.setMaxListeners(150) this.std.detachAllServicesSTD()
process.stderr.setMaxListeners(150)
for (let service of this.instancePool) {
this.services = await scanServices() service.instance.logs.attach()
this.proxy = new Proxy() }
this.ipcRouter = new IPCRouter() },
detachAllServicesSTD: () => {
global.eventBus = this.eventBus for (let service of this.instancePool) {
global.ipcRouter = this.ipcRouter service.instance.logs.detach()
global.proxy = this.proxy }
},
console.clear() }
console.log(comtyAscii)
console.log(`\nRunning ${chalk.bgBlue(`${pkg.name}`)} | ${chalk.bgMagenta(`[v${pkg.version}]`)} | ${this.state.internalIp} \n\n\n`) async initialize() {
onExit(this.onGatewayExit)
if (this.services.length === 0) {
console.error("❌ No services found") process.stdout.setMaxListeners(150)
return process.exit(1) process.stderr.setMaxListeners(150)
}
this.services = await scanServices()
console.log(`📦 Found ${this.services.length} service(s)`) this.proxy = new Proxy()
this.ipcRouter = new IPCRouter()
Observable.observe(this.serviceRegistry, (changes) => {
const { type } = changes[0] global.eventBus = this.eventBus
global.ipcRouter = this.ipcRouter
switch (type) { global.proxy = this.proxy
case "update": {
if (Object.values(this.serviceRegistry).every((service) => service.initialized)) { console.clear()
this.onAllServicesReady() console.log(comtyAscii)
} console.log(
`\nRunning ${chalk.bgBlue(`${pkg.name}`)} | ${chalk.bgMagenta(`[v${pkg.version}]`)} | ${this.state.internalIp} | ${isProduction ? "production" : "development"} \n\n\n`,
break )
}
} if (this.services.length === 0) {
}) console.error("❌ No services found")
return process.exit(1)
await this.createServicesWatchers() }
await this.createServicesProcess() console.log(`📦 Found ${this.services.length} service(s)`)
new RELP({ Observable.observe(this.serviceRegistry, (changes) => {
attachAllServicesSTD: this.std.attachAllServicesSTD, const { type } = changes[0]
detachAllServicesSTD: this.std.detachAllServicesSTD,
attachServiceSTD: this.std.attachServiceSTD, switch (type) {
dettachServiceSTD: this.std.dettachServiceSTD, case "update": {
reloadService: this.std.reloadService, if (
onAllServicesReady: this.onAllServicesReady, Object.values(this.serviceRegistry).every(
}) (service) => service.initialized,
} )
} ) {
this.onAllServicesReady()
}
break
}
}
})
await this.createServicesWatchers()
await this.createServicesProcess()
new RELP({
attachAllServicesSTD: this.std.attachAllServicesSTD,
detachAllServicesSTD: this.std.detachAllServicesSTD,
attachServiceSTD: this.std.attachServiceSTD,
dettachServiceSTD: this.std.dettachServiceSTD,
reloadService: this.std.reloadService,
onAllServicesReady: this.onAllServicesReady,
})
}
}