xylabs/sdk-meta-server-nodejs

View on GitHub
src/modules/metaServer/contentHandlers/proxyOriginal/proxyOriginal.ts

Summary

Maintainability
A
3 hrs
Test Coverage
import { existsSync, readFileSync } from 'node:fs'
import { join } from 'node:path'

import { assertEx } from '@xylabs/assert'
import { asyncHandler, Empty, NoReqParams, NoReqQuery } from '@xylabs/sdk-api-express-ecs'
import { mergeDocumentHead } from '@xyo-network/sdk-meta'
import { RequestHandler } from 'express'
import { StatusCodes } from 'http-status-codes'
import { LRUCache } from 'lru-cache'
import serveStatic, { ServeStaticOptions } from 'serve-static'

import { getAdjustedPath, isHtmlLike } from '../../lib'
import { MetaCacheLocals } from '../../middleware'
import { ApplicationMiddlewareOptions, MountPathAndMiddleware } from '../../types'
import { exists } from './lib'

/**
 * The max-age cache control header time (in seconds)
 * to set for the index.html file
 */
const indexHtmlMaxAge = 60 * 10
const indexHtmlCacheControlHeader = `public, max-age=${indexHtmlMaxAge}`

/**
 * The max-age cache control header time (in mS)
 * to set for static files other than index.html
 */
const maxAge = 60 * 60 * 1000

const options: ServeStaticOptions = {
  cacheControl: true,
  // etag: true,
  fallthrough: false,
  index: 'index.html',
  maxAge,
}

const existingPaths = new LRUCache<string, boolean>({ max: 1000 })

const getHandler = (baseDir: string) => {
  // Ensure file containing base HTML exists
  const filePath = join(baseDir, 'index.html')
  assertEx(existsSync(filePath), 'Missing index.html')
  const html = readFileSync(filePath, { encoding: 'utf8' })
  const proxy = serveStatic(baseDir, options)
  const serveIndex: RequestHandler<NoReqParams, Empty, Empty, NoReqQuery, MetaCacheLocals> = (req, res, _next) => {
    let updated = html
    const path = getAdjustedPath(req)
    const updatedHead = res?.locals?.metaCache?.get?.(path)
    if (updatedHead) {
      updated = mergeDocumentHead(html, updatedHead)
    }
    res.type('html').set('Cache-Control', indexHtmlCacheControlHeader).send(updated)
  }
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
  const handler: RequestHandler<NoReqParams, Empty, Empty, NoReqQuery, MetaCacheLocals> = async (req, res, next) => {
    try {
      // Check if file exists on disk (cache check for performance)
      const file = getAdjustedPath(req)
      console.log(`[proxyOriginal][pageHandler][${file}]: called`)
      let pathExists = existingPaths.get(file)
      if (pathExists === undefined) {
        console.log(`[proxyOriginal][pageHandler][${file}]: path not cached`)
        const result = await exists(join(baseDir, file))
        console.log(`[proxyOriginal][pageHandler][${file}]: cache path`)
        existingPaths.set(file, result)
        pathExists = result
      }
      if (pathExists) {
        console.log(`[proxyOriginal][pageHandler][${file}]: proxy path`)
        proxy(req, res, next)
      } else {
        if (isHtmlLike(req)) {
          console.log(`[proxyOriginal][pageHandler][${file}]: serve index`)
          serveIndex(req, res, next)
        } else {
          res.sendStatus(StatusCodes.NOT_FOUND)
        }
      }
    } catch {
      res.sendStatus(StatusCodes.INTERNAL_SERVER_ERROR)
    }
  }
  return asyncHandler(handler) as unknown as RequestHandler
}

/**
 * Middleware to proxy the original response without any modification
 */
export const configureProxyOriginal = <T extends ApplicationMiddlewareOptions>(opts: T): MountPathAndMiddleware => [
  'get',
  ['*', getHandler(opts.baseDir)],
]