// Ignore paths for logging, metrics and docs // Fast, minimalist web framework for node. import express from 'express' // Helmet helps you secure your Express app by setting various HTTP headers import helmet from 'helmet' // Fastest HTTP logger for Node.js in town import { httpLogger, logger } from './logger.js' // Readiness and liveness checks middleware import health from '@cloudnative/health-connect' // Prometheus middleware for standard api metrics import { metricsMiddleware } from '@open-xchange/metrics' import promClient from 'prom-client' // Swagger UI for api-docs import swaggerUi from 'swagger-ui-express' import yaml from 'js-yaml' import fs from 'fs' import { getCSSDependenciesFor, getDependencies, getOxManifests, getVersion, loadViteManifests } from './manifests.js' import { fileCache } from './files.js' import { getMergedMetadata } from './meta.js' import { viteManifestToDeps } from './util.js' const swaggerDocument = yaml.load(fs.readFileSync('./src/swagger.yaml', 'utf8')) const startUpTimeGauge = new promClient.Gauge({ name: 'manifest_service_startup_time', help: 'Time to warm up cache' }) const metricsMiddlewareInstance = metricsMiddleware() export function createApp () { // The app returned by express() is in fact a JavaScript Function, designed to be passed to Node’s HTTP servers as a callback to handle requests. const app = express() app.use(express.urlencoded({ extended: true })) const healthCheck = new health.HealthChecker() const startupCheck = new health.StartupCheck('warmup cache', async function () { const stopTimer = startUpTimeGauge.startTimer() try { const viteManifests = await loadViteManifests({ warmUp: false }) // also need to load ox manifests here, to make sure the cache is warm await getOxManifests() const deps = viteManifestToDeps(viteManifests) await fileCache.warmUp(viteManifests, deps) } catch (e) { logger.error(`Failed to get dependencies: ${e.message}`) throw e } finally { stopTimer() } }) healthCheck.registerStartupCheck(startupCheck) // Application-level middleware app.use(httpLogger) app.use((req, res, next) => { const { sha256Sum } = fileCache.get(req.path) res.locals.sha256Sum = sha256Sum next() }) app.use(helmet({ contentSecurityPolicy: false, crossOriginEmbedderPolicy: false, originAgentCluster: false, crossOriginOpenerPolicy: { policy: 'same-origin-allow-popups' } })) app.use('/healthy', health.LivenessEndpoint(healthCheck)) app.use('/ready', health.ReadinessEndpoint(healthCheck)) app.use(metricsMiddlewareInstance) app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)) app.use('/swagger.json', (req, res) => res.json(swaggerDocument)) app.timeout = 30000 app.use(async (req, res, next) => { const version = await getVersion() if (version) res.setHeader('version', version) next() }) app.get('/manifests', async (req, res, next) => { try { res.json(await getOxManifests()) } catch (err) { next(err) } }) app.get('/dependencies', async (req, res, next) => { try { res.json(Object.assign({ '/': [] }, await getDependencies())) } catch (err) { next(err) } }) app.get('/meta', async (req, res, next) => { try { res.json(await getMergedMetadata()) } catch (err) { next(err) } }) app.get('/', async (req, res, next) => { const { 'content-type': contentType, content } = fileCache.get('/index.html') if (content) return res.setHeader('content-type', contentType).status(200).send(content) next() }) // backwards compatibility for 7.10.x // this should hopefully be resolved with an ingress // or proper config. But is used to be safe on all ends app.get('/ui', async (req, res, next) => { res.redirect(process.env.APP_ROOT) }) app.post('/redirect', (req, res, next) => { const location = req.body.location || '../busy.html' res.redirect(location) }) app.use(async (req, res, next) => { const { 'content-type': contentType, content } = fileCache.get(req.path) if (content) { const dependencies = await getCSSDependenciesFor(req.path.substr(1)) return res .setHeader('content-type', contentType) .setHeader('dependencies', dependencies.join(',')) .status(200).send(content) } next() }) app.use(async (req, res, next) => { try { const { 'content-type': contentType, content } = await fileCache.fetchAndStore(req.path) if (content) { const dependencies = await getCSSDependenciesFor(req.path.substr(1)) return res .setHeader('content-type', contentType) .setHeader('dependencies', dependencies.join(',')) .status(200).send(content) } } catch (e) {} next() }) app.use(function (err, req, res, next) { logger.error(err) res.status(500).end() }) return app }