1*46f023faSEvan Bacon/** 2*46f023faSEvan Bacon * Copyright © 2022 650 Industries. 3*46f023faSEvan Bacon * 4*46f023faSEvan Bacon * This source code is licensed under the MIT license found in the 5*46f023faSEvan Bacon * LICENSE file in the root directory of this source tree. 6*46f023faSEvan Bacon */ 7*46f023faSEvan Baconimport { ExpoResponse } from '@expo/server'; 8*46f023faSEvan Baconimport { createRequestHandler } from '@expo/server/build/vendor/http'; 9*46f023faSEvan Baconimport requireString from 'require-from-string'; 10*46f023faSEvan Baconimport resolve from 'resolve'; 11*46f023faSEvan Baconimport { promisify } from 'util'; 12*46f023faSEvan Bacon 13*46f023faSEvan Baconimport { ForwardHtmlError } from './MetroBundlerDevServer'; 14*46f023faSEvan Baconimport { bundleApiRoute } from './bundleApiRoutes'; 15*46f023faSEvan Baconimport { fetchManifest } from './fetchRouterManifest'; 16*46f023faSEvan Baconimport { getErrorOverlayHtmlAsync, logMetroError, logMetroErrorAsync } from './metroErrorInterface'; 17*46f023faSEvan Baconimport { Log } from '../../../log'; 18*46f023faSEvan Bacon 19*46f023faSEvan Baconconst debug = require('debug')('expo:start:server:metro') as typeof console.log; 20*46f023faSEvan Bacon 21*46f023faSEvan Baconconst resolveAsync = promisify(resolve) as any as ( 22*46f023faSEvan Bacon id: string, 23*46f023faSEvan Bacon opts: resolve.AsyncOpts 24*46f023faSEvan Bacon) => Promise<string | null>; 25*46f023faSEvan Bacon 26*46f023faSEvan Baconexport function createRouteHandlerMiddleware( 27*46f023faSEvan Bacon projectRoot: string, 28*46f023faSEvan Bacon options: { 29*46f023faSEvan Bacon mode?: string; 30*46f023faSEvan Bacon appDir: string; 31*46f023faSEvan Bacon port?: number; 32*46f023faSEvan Bacon getWebBundleUrl: () => string; 33*46f023faSEvan Bacon getStaticPageAsync: (pathname: string) => Promise<{ content: string }>; 34*46f023faSEvan Bacon } 35*46f023faSEvan Bacon) { 36*46f023faSEvan Bacon return createRequestHandler( 37*46f023faSEvan Bacon { build: '' }, 38*46f023faSEvan Bacon { 39*46f023faSEvan Bacon async getRoutesManifest() { 40*46f023faSEvan Bacon const manifest = await fetchManifest<RegExp>(projectRoot, options); 41*46f023faSEvan Bacon debug('manifest', manifest); 42*46f023faSEvan Bacon // NOTE: no app dir if null 43*46f023faSEvan Bacon // TODO: Redirect to 404 page 44*46f023faSEvan Bacon return manifest; 45*46f023faSEvan Bacon }, 46*46f023faSEvan Bacon async getHtml(request) { 47*46f023faSEvan Bacon try { 48*46f023faSEvan Bacon const { content } = await options.getStaticPageAsync(request.url); 49*46f023faSEvan Bacon return content; 50*46f023faSEvan Bacon } catch (error: any) { 51*46f023faSEvan Bacon // Forward the Metro server response as-is. It won't be pretty, but at least it will be accurate. 52*46f023faSEvan Bacon if (error instanceof ForwardHtmlError) { 53*46f023faSEvan Bacon return new ExpoResponse(error.html, { 54*46f023faSEvan Bacon status: error.statusCode, 55*46f023faSEvan Bacon headers: { 56*46f023faSEvan Bacon 'Content-Type': 'text/html', 57*46f023faSEvan Bacon }, 58*46f023faSEvan Bacon }); 59*46f023faSEvan Bacon } 60*46f023faSEvan Bacon 61*46f023faSEvan Bacon try { 62*46f023faSEvan Bacon return new ExpoResponse( 63*46f023faSEvan Bacon await getErrorOverlayHtmlAsync({ 64*46f023faSEvan Bacon error, 65*46f023faSEvan Bacon projectRoot, 66*46f023faSEvan Bacon }), 67*46f023faSEvan Bacon { 68*46f023faSEvan Bacon status: 500, 69*46f023faSEvan Bacon headers: { 70*46f023faSEvan Bacon 'Content-Type': 'text/html', 71*46f023faSEvan Bacon }, 72*46f023faSEvan Bacon } 73*46f023faSEvan Bacon ); 74*46f023faSEvan Bacon } catch (staticError: any) { 75*46f023faSEvan Bacon // Fallback error for when Expo Router is misconfigured in the project. 76*46f023faSEvan Bacon return new ExpoResponse( 77*46f023faSEvan Bacon '<span><h3>Internal Error:</h3><b>Project is not setup correctly for static rendering (check terminal for more info):</b><br/>' + 78*46f023faSEvan Bacon error.message + 79*46f023faSEvan Bacon '<br/><br/>' + 80*46f023faSEvan Bacon staticError.message + 81*46f023faSEvan Bacon '</span>', 82*46f023faSEvan Bacon { 83*46f023faSEvan Bacon status: 500, 84*46f023faSEvan Bacon headers: { 85*46f023faSEvan Bacon 'Content-Type': 'text/html', 86*46f023faSEvan Bacon }, 87*46f023faSEvan Bacon } 88*46f023faSEvan Bacon ); 89*46f023faSEvan Bacon } 90*46f023faSEvan Bacon } 91*46f023faSEvan Bacon }, 92*46f023faSEvan Bacon logApiRouteExecutionError(error) { 93*46f023faSEvan Bacon logMetroError(projectRoot, { error }); 94*46f023faSEvan Bacon }, 95*46f023faSEvan Bacon async getApiRoute(route) { 96*46f023faSEvan Bacon const resolvedFunctionPath = await resolveAsync(route.page, { 97*46f023faSEvan Bacon extensions: ['.js', '.jsx', '.ts', '.tsx'], 98*46f023faSEvan Bacon basedir: options.appDir, 99*46f023faSEvan Bacon }); 100*46f023faSEvan Bacon 101*46f023faSEvan Bacon const middlewareContents = await bundleApiRoute( 102*46f023faSEvan Bacon projectRoot, 103*46f023faSEvan Bacon resolvedFunctionPath!, 104*46f023faSEvan Bacon options 105*46f023faSEvan Bacon ); 106*46f023faSEvan Bacon if (!middlewareContents) { 107*46f023faSEvan Bacon // TODO: Error handling 108*46f023faSEvan Bacon return null; 109*46f023faSEvan Bacon } 110*46f023faSEvan Bacon 111*46f023faSEvan Bacon try { 112*46f023faSEvan Bacon debug(`Bundling middleware at: ${resolvedFunctionPath}`); 113*46f023faSEvan Bacon return requireString(middlewareContents); 114*46f023faSEvan Bacon } catch (error: any) { 115*46f023faSEvan Bacon if (error instanceof Error) { 116*46f023faSEvan Bacon await logMetroErrorAsync({ projectRoot, error }); 117*46f023faSEvan Bacon } else { 118*46f023faSEvan Bacon Log.error('Failed to load middleware: ' + error); 119*46f023faSEvan Bacon } 120*46f023faSEvan Bacon return new ExpoResponse( 121*46f023faSEvan Bacon 'Failed to load middleware: ' + resolvedFunctionPath + '\n\n' + error.message, 122*46f023faSEvan Bacon { 123*46f023faSEvan Bacon status: 500, 124*46f023faSEvan Bacon headers: { 125*46f023faSEvan Bacon 'Content-Type': 'text/html', 126*46f023faSEvan Bacon }, 127*46f023faSEvan Bacon } 128*46f023faSEvan Bacon ); 129*46f023faSEvan Bacon } 130*46f023faSEvan Bacon }, 131*46f023faSEvan Bacon } 132*46f023faSEvan Bacon ); 133*46f023faSEvan Bacon} 134