xref: /expo/packages/@expo/server/build/index.js (revision 46f023fa)
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