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' import SerialRoutes from './serial/routes.mjs' import HealthRoutes from './health/routes.mjs' 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(), serial: new SerialRoutes(), health: new HealthRoutes({ version: this.core.version }), serve: new ServeHandler({ root: localUtil.getPathFromRoot('../public'), version: this.core.version, frontend: config.get('frontend:url'), }), } } 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 } else if (ctx.url.startsWith('/assets')) { return } 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) this.io.init(this, this.flaska.server) } 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() } }