18d307f52SEvan Baconimport { parse } from 'url'; 28d307f52SEvan Bacon 38d307f52SEvan Baconimport { ServerNext, ServerRequest, ServerResponse } from './server.types'; 4*8a424bebSJames Ideimport * as Log from '../../../log'; 58d307f52SEvan Bacon 68d307f52SEvan Bacon/** Base middleware creator for Expo dev servers. */ 78d307f52SEvan Baconexport abstract class ExpoMiddleware { 8*8a424bebSJames Ide constructor( 9*8a424bebSJames Ide protected projectRoot: string, 10*8a424bebSJames Ide protected supportedPaths: string[] 11*8a424bebSJames Ide ) {} 128d307f52SEvan Bacon 138d307f52SEvan Bacon /** 148d307f52SEvan Bacon * Returns true when the middleware should handle the incoming server request. 158d307f52SEvan Bacon * Exposed for testing. 168d307f52SEvan Bacon */ 178d307f52SEvan Bacon _shouldHandleRequest(req: ServerRequest): boolean { 1829975bfdSEvan Bacon if (!req.url) { 1929975bfdSEvan Bacon return false; 2029975bfdSEvan Bacon } 2129975bfdSEvan Bacon const parsed = parse(req.url); 228d307f52SEvan Bacon // Strip the query params 2329975bfdSEvan Bacon if (!parsed.pathname) { 2429975bfdSEvan Bacon return false; 2529975bfdSEvan Bacon } 2629975bfdSEvan Bacon 2729975bfdSEvan Bacon return this.supportedPaths.includes(parsed.pathname); 288d307f52SEvan Bacon } 298d307f52SEvan Bacon 308d307f52SEvan Bacon abstract handleRequestAsync( 318d307f52SEvan Bacon req: ServerRequest, 328d307f52SEvan Bacon res: ServerResponse, 338d307f52SEvan Bacon next: ServerNext 348d307f52SEvan Bacon ): Promise<void>; 358d307f52SEvan Bacon 368d307f52SEvan Bacon /** Create a server middleware handler. */ 376d6b81f9SEvan Bacon public getHandler() { 386d6b81f9SEvan Bacon const internalMiddleware = async ( 398d307f52SEvan Bacon req: ServerRequest, 408d307f52SEvan Bacon res: ServerResponse, 418d307f52SEvan Bacon next: ServerNext 426d6b81f9SEvan Bacon ) => { 438d307f52SEvan Bacon try { 448d307f52SEvan Bacon return await this.handleRequestAsync(req, res, next); 4529975bfdSEvan Bacon } catch (error: any) { 4629975bfdSEvan Bacon Log.exception(error); 478d307f52SEvan Bacon // 5xx = Server Error HTTP code 488d307f52SEvan Bacon res.statusCode = 500; 4929975bfdSEvan Bacon if (typeof error === 'object' && error !== null) { 508d307f52SEvan Bacon res.end( 518d307f52SEvan Bacon JSON.stringify({ 5229975bfdSEvan Bacon error: error.toString(), 538d307f52SEvan Bacon }) 548d307f52SEvan Bacon ); 558d307f52SEvan Bacon } else { 5629975bfdSEvan Bacon res.end(`Unexpected error: ${error}`); 578d307f52SEvan Bacon } 588d307f52SEvan Bacon } 598d307f52SEvan Bacon }; 606d6b81f9SEvan Bacon const middleware = async (req: ServerRequest, res: ServerResponse, next: ServerNext) => { 616d6b81f9SEvan Bacon if (!this._shouldHandleRequest(req)) { 626d6b81f9SEvan Bacon return next(); 636d6b81f9SEvan Bacon } 646d6b81f9SEvan Bacon return internalMiddleware(req, res, next); 656d6b81f9SEvan Bacon }; 666d6b81f9SEvan Bacon 676d6b81f9SEvan Bacon middleware.internal = internalMiddleware; 686d6b81f9SEvan Bacon 696d6b81f9SEvan Bacon return middleware; 708d307f52SEvan Bacon } 718d307f52SEvan Bacon} 728d307f52SEvan Bacon 738d307f52SEvan Baconexport function disableResponseCache(res: ServerResponse): ServerResponse { 748d307f52SEvan Bacon res.setHeader('Cache-Control', 'private, no-cache, no-store, must-revalidate'); 758d307f52SEvan Bacon res.setHeader('Expires', '-1'); 768d307f52SEvan Bacon res.setHeader('Pragma', 'no-cache'); 778d307f52SEvan Bacon return res; 788d307f52SEvan Bacon} 79