import { createReadStream } from 'fs' ; import { resolve , extname } from 'path' ; import mime from 'mime-types' ; import agent from 'elastic-apm-node' ; import { fstat , close } from './fs' ; import { RequestHandler } from '../../http' ; import { IFileHashCache } from './file_hash_cache' ; import { getFileHash } from './file_hash' ; import { selectCompressedFile } from './select_compressed_file' ; const MINUTE = 60 ; const HOUR = 60 * MINUTE ; const DAY = 24 * HOUR ; /**
 *  Serve asset for the requested path. This is designed
 *  to replicate a subset of the features provided by Hapi's Inert
 *  plugin including:
 *   - ensure path is not traversing out of the bundle directory
 *   - manage use file descriptors for file access to efficiently
 *     interact with the file multiple times in each request
 *   - generate and cache etag for the file
 *   - write correct headers to response for client-side caching
 *     and invalidation
 *   - stream file to response
 *
 *  It differs from Inert in some important ways:
 *   - cached hash/etag is based on the file on disk, but modified
 *     by the public path so that individual public paths have
 *     different etags, but can share a cache
 */ export const createDynamicAssetHandler = ( { bundlesPath , fileHashCache , isDist , publicPath , } ) : RequestHandler < { path : string ; } , { } , { } > => { return async ( ctx , req , res ) => { agent . setTransactionName ( 'GET ?/bundles/?' ) ; let fd ; let fileEncoding ; try { const path = resolve ( bundlesPath , req . params . path ) ; if ( ! path . startsWith ( bundlesPath ) ) { return res . forbidden ( { body : 'EACCES' , } ) ; } ( { fd , fileEncoding } = await selectCompressedFile ( req . headers [ 'accept-encoding' ] as string , path ) ) ; let headers ; if ( isDist ) { headers = { 'cache-control' : `max-age=${ 365 * DAY }` } ; } else { const stat = await fstat ( fd ) ; const hash = await getFileHash ( fileHashCache , path , stat , fd ) ; headers = { etag : `${ hash }-${ publicPath }` , 'cache-control' : 'must-revalidate' , } ; } if ( fileEncoding ) { headers [ 'content-encoding' ] = fileEncoding ; } const fileExt = extname ( path ) ; const contentType = mime . lookup ( fileExt ) ; const mediaType = mime . contentType ( contentType || fileExt ) ; headers [ 'content-type' ] = mediaType || '' ; const content = createReadStream ( null as any , { fd , start : 0 , autoClose : true , } ) ; return res . ok ( { body : content , headers , } ) ; } catch ( error ) { if ( fd ) { try { await close ( fd ) ; } catch ( _ ) { } } if ( error . code === 'ENOENT' ) { return res . notFound ( ) ; } throw error ; } } ; } ;