2024-02-13 23:54:45 +00:00
|
|
|
import { Flaska, QueryHandler, JsonHandler, HttpError } from 'flaska'
|
|
|
|
|
|
|
|
import config from './config.mjs'
|
|
|
|
import StaticRoutes from './static_routes.mjs'
|
|
|
|
import ServeHandler from './serve.mjs'
|
|
|
|
import SocketServer from './io.mjs'
|
|
|
|
import EncoderRoutes from './encoder/routes.mjs'
|
2024-02-20 04:57:49 +00:00
|
|
|
import SerialRoutes from './serial/routes.mjs'
|
|
|
|
import HealthRoutes from './health/routes.mjs'
|
2024-02-13 23:54:45 +00:00
|
|
|
|
|
|
|
export default class Server {
|
|
|
|
constructor(http, port, core, opts = {}) {
|
|
|
|
Object.assign(this, opts)
|
|
|
|
Object.assign(this, {
|
|
|
|
http, port, core,
|
|
|
|
})
|
|
|
|
let localUtil = new this.core.sc.Util(import.meta.url)
|
|
|
|
|
|
|
|
this.flaskaOptions = {
|
|
|
|
appendHeaders: {
|
|
|
|
'Content-Security-Policy': `default-src 'self'; style-src 'self' 'unsafe-inline'; img-src * data: blob:; font-src 'self' data:; object-src 'none'; frame-ancestors 'none'; connect-src 'self' https://media.nfp.is/; media-src 'self' https://cdn.nfp.is/`,
|
|
|
|
},
|
|
|
|
nonce: [],
|
|
|
|
log: this.core.log,
|
|
|
|
}
|
|
|
|
this.jsonHandler = JsonHandler
|
|
|
|
|
|
|
|
this.routes = {
|
|
|
|
static: new StaticRoutes(),
|
|
|
|
encoder: new EncoderRoutes(),
|
2024-02-20 04:57:49 +00:00
|
|
|
serial: new SerialRoutes(),
|
2024-02-25 22:01:37 +00:00
|
|
|
health: new HealthRoutes({ version: this.core.version }),
|
2024-02-20 04:57:49 +00:00
|
|
|
serve: new ServeHandler({
|
|
|
|
root: localUtil.getPathFromRoot('../public'),
|
|
|
|
version: this.core.version,
|
|
|
|
frontend: config.get('frontend:url'),
|
|
|
|
}),
|
2024-02-13 23:54:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
runCreateServer() {
|
|
|
|
// Create our server
|
|
|
|
this.flaska = new Flaska(this.flaskaOptions, this.http)
|
|
|
|
|
|
|
|
// configure our server
|
|
|
|
if (config.get('NODE_ENV') === 'development') {
|
|
|
|
this.flaska.devMode()
|
|
|
|
}
|
|
|
|
|
|
|
|
this.flaska.onerror((err, ctx) => {
|
|
|
|
if (err instanceof HttpError && err.status !== 500) {
|
|
|
|
ctx.status = err.status
|
|
|
|
ctx.log.warn(err.message)
|
|
|
|
} else {
|
|
|
|
ctx.log.error(err.inner || err)
|
|
|
|
if (err.extra) {
|
|
|
|
ctx.log.error({ extra: err.extra }, 'Database parameters')
|
|
|
|
}
|
|
|
|
ctx.status = 500
|
|
|
|
}
|
|
|
|
ctx.body = {
|
|
|
|
status: ctx.status,
|
|
|
|
message: err.message,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
this.flaska.before(function(ctx) {
|
|
|
|
ctx.state.started = new Date().getTime()
|
|
|
|
ctx.req.ip = ctx.req.headers['x-forwarded-for'] || ctx.req.connection.remoteAddress
|
|
|
|
ctx.log = ctx.log.child({
|
|
|
|
id: Math.random().toString(36).substring(2, 14),
|
|
|
|
})
|
|
|
|
ctx.db = this.pool
|
|
|
|
}.bind(this))
|
|
|
|
this.flaska.before(QueryHandler())
|
|
|
|
|
|
|
|
let healthChecks = 0
|
|
|
|
let healthCollectLimit = 60 * 60 * 12
|
|
|
|
|
|
|
|
this.flaska.after(function(ctx) {
|
|
|
|
if (ctx.aborted && ctx.status === 200) {
|
|
|
|
ctx.status = 299
|
|
|
|
}
|
|
|
|
let ended = new Date().getTime()
|
|
|
|
var requestTime = ended - ctx.state.started
|
|
|
|
|
|
|
|
let status = ''
|
|
|
|
let level = 'info'
|
|
|
|
if (ctx.status >= 400) {
|
|
|
|
status = ctx.status + ' '
|
|
|
|
level = 'warn'
|
|
|
|
}
|
|
|
|
if (ctx.status >= 500) {
|
|
|
|
level = 'error'
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ctx.url === '/health' || ctx.url === '/api/health') {
|
|
|
|
healthChecks++
|
|
|
|
if (healthChecks >= healthCollectLimit) {
|
|
|
|
ctx.log[level]({
|
|
|
|
duration: Math.round(ended),
|
|
|
|
status: ctx.status,
|
|
|
|
}, `<-- ${status}${ctx.method} ${ctx.url} {has happened ${healthChecks} times}`)
|
|
|
|
healthChecks = 0
|
|
|
|
}
|
|
|
|
return
|
2024-02-20 04:57:49 +00:00
|
|
|
} else if (ctx.url.startsWith('/assets')) {
|
|
|
|
return
|
2024-02-13 23:54:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ctx.log[level]({
|
|
|
|
duration: requestTime,
|
|
|
|
status: ctx.status,
|
|
|
|
ip: ctx.req.ip,
|
|
|
|
}, (ctx.aborted ? '[ABORT]' : '<--') + ` ${status}${ctx.method} ${ctx.url}`)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
runRegisterRoutes() {
|
|
|
|
let keys = Object.keys(this.routes)
|
|
|
|
for (let key of keys) {
|
|
|
|
if (this.routes[key].register) {
|
|
|
|
this.routes[key].register(this)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
runCreateSocket() {
|
|
|
|
this.io = new SocketServer(this.db, this.core.log, this.routes)
|
2024-02-20 04:57:49 +00:00
|
|
|
this.io.init(this, this.flaska.server)
|
2024-02-13 23:54:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
runStartListen() {
|
|
|
|
return this.flaska.listenAsync(this.port).then(() => {
|
|
|
|
this.core.log.info('Server is listening on port ' + this.port)
|
|
|
|
this.runCreateSocket()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
run() {
|
|
|
|
this.runCreateServer()
|
|
|
|
this.runRegisterRoutes()
|
|
|
|
return this.runStartListen()
|
|
|
|
}
|
|
|
|
}
|