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