1*46f023faSEvan Bacon"use strict"; 2*46f023faSEvan Baconvar __importDefault = (this && this.__importDefault) || function (mod) { 3*46f023faSEvan Bacon return (mod && mod.__esModule) ? mod : { "default": mod }; 4*46f023faSEvan Bacon}; 5*46f023faSEvan BaconObject.defineProperty(exports, "__esModule", { value: true }); 6*46f023faSEvan Baconexports.ExpoRequest = exports.ExpoResponse = exports.createRequestHandler = exports.getRoutesManifest = void 0; 7*46f023faSEvan Baconrequire("@expo/server/install"); 8*46f023faSEvan Baconconst fs_1 = __importDefault(require("fs")); 9*46f023faSEvan Baconconst path_1 = __importDefault(require("path")); 10*46f023faSEvan Baconconst url_1 = require("url"); 11*46f023faSEvan Baconconst environment_1 = require("./environment"); 12*46f023faSEvan BaconObject.defineProperty(exports, "ExpoRequest", { enumerable: true, get: function () { return environment_1.ExpoRequest; } }); 13*46f023faSEvan BaconObject.defineProperty(exports, "ExpoResponse", { enumerable: true, get: function () { return environment_1.ExpoResponse; } }); 14*46f023faSEvan Baconconst debug = require('debug')('expo:server'); 15*46f023faSEvan Baconfunction getProcessedManifest(path) { 16*46f023faSEvan Bacon // TODO: JSON Schema for validation 17*46f023faSEvan Bacon const routesManifest = JSON.parse(fs_1.default.readFileSync(path, 'utf-8')); 18*46f023faSEvan Bacon const parsed = { 19*46f023faSEvan Bacon ...routesManifest, 20*46f023faSEvan Bacon notFoundRoutes: routesManifest.notFoundRoutes.map((value) => { 21*46f023faSEvan Bacon return { 22*46f023faSEvan Bacon ...value, 23*46f023faSEvan Bacon namedRegex: new RegExp(value.namedRegex), 24*46f023faSEvan Bacon }; 25*46f023faSEvan Bacon }), 26*46f023faSEvan Bacon apiRoutes: routesManifest.apiRoutes.map((value) => { 27*46f023faSEvan Bacon return { 28*46f023faSEvan Bacon ...value, 29*46f023faSEvan Bacon namedRegex: new RegExp(value.namedRegex), 30*46f023faSEvan Bacon }; 31*46f023faSEvan Bacon }), 32*46f023faSEvan Bacon htmlRoutes: routesManifest.htmlRoutes.map((value) => { 33*46f023faSEvan Bacon return { 34*46f023faSEvan Bacon ...value, 35*46f023faSEvan Bacon namedRegex: new RegExp(value.namedRegex), 36*46f023faSEvan Bacon }; 37*46f023faSEvan Bacon }), 38*46f023faSEvan Bacon }; 39*46f023faSEvan Bacon return parsed; 40*46f023faSEvan Bacon} 41*46f023faSEvan Baconfunction getRoutesManifest(distFolder) { 42*46f023faSEvan Bacon return getProcessedManifest(path_1.default.join(distFolder, '_expo/routes.json')); 43*46f023faSEvan Bacon} 44*46f023faSEvan Baconexports.getRoutesManifest = getRoutesManifest; 45*46f023faSEvan Bacon// TODO: Reuse this for dev as well 46*46f023faSEvan Baconfunction createRequestHandler(distFolder, { getRoutesManifest: getInternalRoutesManifest, getHtml = async (request, route) => { 47*46f023faSEvan Bacon // serve a static file 48*46f023faSEvan Bacon const filePath = path_1.default.join(distFolder, route.page + '.html'); 49*46f023faSEvan Bacon if (!fs_1.default.existsSync(filePath)) { 50*46f023faSEvan Bacon return null; 51*46f023faSEvan Bacon } 52*46f023faSEvan Bacon return fs_1.default.readFileSync(filePath, 'utf-8'); 53*46f023faSEvan Bacon}, getApiRoute = async (route) => { 54*46f023faSEvan Bacon const filePath = path_1.default.join(distFolder, '_expo/functions', route.page + '.js'); 55*46f023faSEvan Bacon debug(`Handling API route: ${route.page}: ${filePath}`); 56*46f023faSEvan Bacon // TODO: What's the standard behavior for malformed projects? 57*46f023faSEvan Bacon if (!fs_1.default.existsSync(filePath)) { 58*46f023faSEvan Bacon return null; 59*46f023faSEvan Bacon } 60*46f023faSEvan Bacon return require(filePath); 61*46f023faSEvan Bacon}, logApiRouteExecutionError = (error) => { 62*46f023faSEvan Bacon console.error(error); 63*46f023faSEvan Bacon}, } = {}) { 64*46f023faSEvan Bacon let routesManifest; 65*46f023faSEvan Bacon function updateRequestWithConfig(request, config) { 66*46f023faSEvan Bacon const params = {}; 67*46f023faSEvan Bacon const url = request.url; 68*46f023faSEvan Bacon const expoUrl = new environment_1.ExpoURL(url); 69*46f023faSEvan Bacon const match = config.namedRegex.exec(expoUrl.pathname); 70*46f023faSEvan Bacon if (match?.groups) { 71*46f023faSEvan Bacon for (const [key, value] of Object.entries(match.groups)) { 72*46f023faSEvan Bacon const namedKey = config.routeKeys[key]; 73*46f023faSEvan Bacon expoUrl.searchParams.set(namedKey, value); 74*46f023faSEvan Bacon params[namedKey] = value; 75*46f023faSEvan Bacon } 76*46f023faSEvan Bacon } 77*46f023faSEvan Bacon request[environment_1.NON_STANDARD_SYMBOL] = { 78*46f023faSEvan Bacon url: expoUrl, 79*46f023faSEvan Bacon }; 80*46f023faSEvan Bacon return params; 81*46f023faSEvan Bacon } 82*46f023faSEvan Bacon return async function handler(request) { 83*46f023faSEvan Bacon if (getInternalRoutesManifest) { 84*46f023faSEvan Bacon const manifest = await getInternalRoutesManifest(distFolder); 85*46f023faSEvan Bacon if (manifest) { 86*46f023faSEvan Bacon routesManifest = manifest; 87*46f023faSEvan Bacon } 88*46f023faSEvan Bacon else { 89*46f023faSEvan Bacon // Development error when Expo Router is not setup. 90*46f023faSEvan Bacon return new environment_1.ExpoResponse('No routes manifest found', { 91*46f023faSEvan Bacon status: 404, 92*46f023faSEvan Bacon headers: { 93*46f023faSEvan Bacon 'Content-Type': 'text/plain', 94*46f023faSEvan Bacon }, 95*46f023faSEvan Bacon }); 96*46f023faSEvan Bacon } 97*46f023faSEvan Bacon } 98*46f023faSEvan Bacon else if (!routesManifest) { 99*46f023faSEvan Bacon routesManifest = getRoutesManifest(distFolder); 100*46f023faSEvan Bacon } 101*46f023faSEvan Bacon const url = new url_1.URL(request.url, 'http://expo.dev'); 102*46f023faSEvan Bacon const sanitizedPathname = url.pathname; 103*46f023faSEvan Bacon debug('Request', sanitizedPathname); 104*46f023faSEvan Bacon if (request.method === 'GET' || request.method === 'HEAD') { 105*46f023faSEvan Bacon // First test static routes 106*46f023faSEvan Bacon for (const route of routesManifest.htmlRoutes) { 107*46f023faSEvan Bacon if (!route.namedRegex.test(sanitizedPathname)) { 108*46f023faSEvan Bacon continue; 109*46f023faSEvan Bacon } 110*46f023faSEvan Bacon // // Mutate to add the expoUrl object. 111*46f023faSEvan Bacon updateRequestWithConfig(request, route); 112*46f023faSEvan Bacon // serve a static file 113*46f023faSEvan Bacon const contents = await getHtml(request, route); 114*46f023faSEvan Bacon // TODO: What's the standard behavior for malformed projects? 115*46f023faSEvan Bacon if (!contents) { 116*46f023faSEvan Bacon return new environment_1.ExpoResponse('Not found', { 117*46f023faSEvan Bacon status: 404, 118*46f023faSEvan Bacon headers: { 119*46f023faSEvan Bacon 'Content-Type': 'text/plain', 120*46f023faSEvan Bacon }, 121*46f023faSEvan Bacon }); 122*46f023faSEvan Bacon } 123*46f023faSEvan Bacon else if (contents instanceof environment_1.ExpoResponse) { 124*46f023faSEvan Bacon return contents; 125*46f023faSEvan Bacon } 126*46f023faSEvan Bacon return new environment_1.ExpoResponse(contents, { 127*46f023faSEvan Bacon status: 200, 128*46f023faSEvan Bacon headers: { 129*46f023faSEvan Bacon 'Content-Type': 'text/html', 130*46f023faSEvan Bacon }, 131*46f023faSEvan Bacon }); 132*46f023faSEvan Bacon } 133*46f023faSEvan Bacon } 134*46f023faSEvan Bacon // Next, test API routes 135*46f023faSEvan Bacon for (const route of routesManifest.apiRoutes) { 136*46f023faSEvan Bacon if (!route.namedRegex.test(sanitizedPathname)) { 137*46f023faSEvan Bacon continue; 138*46f023faSEvan Bacon } 139*46f023faSEvan Bacon const func = await getApiRoute(route); 140*46f023faSEvan Bacon if (func instanceof environment_1.ExpoResponse) { 141*46f023faSEvan Bacon return func; 142*46f023faSEvan Bacon } 143*46f023faSEvan Bacon const routeHandler = func[request.method]; 144*46f023faSEvan Bacon if (!routeHandler) { 145*46f023faSEvan Bacon return new environment_1.ExpoResponse('Method not allowed', { 146*46f023faSEvan Bacon status: 405, 147*46f023faSEvan Bacon headers: { 148*46f023faSEvan Bacon 'Content-Type': 'text/plain', 149*46f023faSEvan Bacon }, 150*46f023faSEvan Bacon }); 151*46f023faSEvan Bacon } 152*46f023faSEvan Bacon // Mutate to add the expoUrl object. 153*46f023faSEvan Bacon const params = updateRequestWithConfig(request, route); 154*46f023faSEvan Bacon try { 155*46f023faSEvan Bacon // TODO: Handle undefined 156*46f023faSEvan Bacon return (await routeHandler(request, params)); 157*46f023faSEvan Bacon } 158*46f023faSEvan Bacon catch (error) { 159*46f023faSEvan Bacon if (error instanceof Error) { 160*46f023faSEvan Bacon logApiRouteExecutionError(error); 161*46f023faSEvan Bacon } 162*46f023faSEvan Bacon return new environment_1.ExpoResponse('Internal server error', { 163*46f023faSEvan Bacon status: 500, 164*46f023faSEvan Bacon headers: { 165*46f023faSEvan Bacon 'Content-Type': 'text/plain', 166*46f023faSEvan Bacon }, 167*46f023faSEvan Bacon }); 168*46f023faSEvan Bacon } 169*46f023faSEvan Bacon } 170*46f023faSEvan Bacon // Finally, test 404 routes 171*46f023faSEvan Bacon for (const route of routesManifest.notFoundRoutes) { 172*46f023faSEvan Bacon if (!route.namedRegex.test(sanitizedPathname)) { 173*46f023faSEvan Bacon continue; 174*46f023faSEvan Bacon } 175*46f023faSEvan Bacon // // Mutate to add the expoUrl object. 176*46f023faSEvan Bacon updateRequestWithConfig(request, route); 177*46f023faSEvan Bacon // serve a static file 178*46f023faSEvan Bacon const contents = await getHtml(request, route); 179*46f023faSEvan Bacon // TODO: What's the standard behavior for malformed projects? 180*46f023faSEvan Bacon if (!contents) { 181*46f023faSEvan Bacon return new environment_1.ExpoResponse('Not found', { 182*46f023faSEvan Bacon status: 404, 183*46f023faSEvan Bacon headers: { 184*46f023faSEvan Bacon 'Content-Type': 'text/plain', 185*46f023faSEvan Bacon }, 186*46f023faSEvan Bacon }); 187*46f023faSEvan Bacon } 188*46f023faSEvan Bacon else if (contents instanceof environment_1.ExpoResponse) { 189*46f023faSEvan Bacon return contents; 190*46f023faSEvan Bacon } 191*46f023faSEvan Bacon return new environment_1.ExpoResponse(contents, { 192*46f023faSEvan Bacon status: 404, 193*46f023faSEvan Bacon headers: { 194*46f023faSEvan Bacon 'Content-Type': 'text/html', 195*46f023faSEvan Bacon }, 196*46f023faSEvan Bacon }); 197*46f023faSEvan Bacon } 198*46f023faSEvan Bacon // 404 199*46f023faSEvan Bacon const response = new environment_1.ExpoResponse('Not found', { 200*46f023faSEvan Bacon status: 404, 201*46f023faSEvan Bacon headers: { 202*46f023faSEvan Bacon 'Content-Type': 'text/plain', 203*46f023faSEvan Bacon }, 204*46f023faSEvan Bacon }); 205*46f023faSEvan Bacon return response; 206*46f023faSEvan Bacon }; 207*46f023faSEvan Bacon} 208*46f023faSEvan Baconexports.createRequestHandler = createRequestHandler; 209*46f023faSEvan Bacon//# sourceMappingURL=index.js.map