16d6b81f9SEvan Baconimport path from 'path'; 26d6b81f9SEvan Baconimport send from 'send'; 36d6b81f9SEvan Baconimport { parse } from 'url'; 46d6b81f9SEvan Bacon 56d6b81f9SEvan Baconimport { parsePlatformHeader } from './resolvePlatform'; 66d6b81f9SEvan Baconimport { ServerRequest, ServerResponse } from './server.types'; 7*8a424bebSJames Ideimport { env } from '../../../utils/env'; 86d6b81f9SEvan Bacon 96d6b81f9SEvan Baconconst debug = require('debug')('expo:start:server:middleware:serveStatic') as typeof console.log; 106d6b81f9SEvan Bacon 116d6b81f9SEvan Bacon/** 126d6b81f9SEvan Bacon * Adds support for serving the files in the static `public/` folder to web apps. 136d6b81f9SEvan Bacon */ 146d6b81f9SEvan Baconexport class ServeStaticMiddleware { 156d6b81f9SEvan Bacon constructor(private projectRoot: string) {} 166d6b81f9SEvan Bacon getHandler() { 176d6b81f9SEvan Bacon const publicPath = path.join(this.projectRoot, env.EXPO_PUBLIC_FOLDER); 186d6b81f9SEvan Bacon 196d6b81f9SEvan Bacon debug(`Serving static files from:`, publicPath); 206d6b81f9SEvan Bacon const opts = { 216d6b81f9SEvan Bacon root: publicPath, 226d6b81f9SEvan Bacon }; 236d6b81f9SEvan Bacon return (req: ServerRequest, res: ServerResponse, next: any) => { 246d6b81f9SEvan Bacon if (!req?.url || (req.method !== 'GET' && req.method !== 'HEAD')) { 256d6b81f9SEvan Bacon return next(); 266d6b81f9SEvan Bacon } 276d6b81f9SEvan Bacon 286d6b81f9SEvan Bacon const platform = parsePlatformHeader(req); 296d6b81f9SEvan Bacon // Currently this is web-only 306d6b81f9SEvan Bacon if (platform && platform !== 'web') { 316d6b81f9SEvan Bacon return next(); 326d6b81f9SEvan Bacon } 336d6b81f9SEvan Bacon 346d6b81f9SEvan Bacon const pathname = parse(req.url).pathname; 356d6b81f9SEvan Bacon if (!pathname) { 366d6b81f9SEvan Bacon return next(); 376d6b81f9SEvan Bacon } 386d6b81f9SEvan Bacon 396d6b81f9SEvan Bacon debug(`Maybe serve static:`, pathname); 406d6b81f9SEvan Bacon const stream = send(req, pathname, opts); 416d6b81f9SEvan Bacon 426d6b81f9SEvan Bacon // add file listener for fallthrough 436d6b81f9SEvan Bacon let forwardError = false; 446d6b81f9SEvan Bacon stream.on('file', function onFile() { 456d6b81f9SEvan Bacon // once file is determined, always forward error 466d6b81f9SEvan Bacon forwardError = true; 476d6b81f9SEvan Bacon }); 486d6b81f9SEvan Bacon 496d6b81f9SEvan Bacon // forward errors 506d6b81f9SEvan Bacon stream.on('error', function error(err: any) { 516d6b81f9SEvan Bacon if (forwardError || !(err.statusCode < 500)) { 526d6b81f9SEvan Bacon next(err); 536d6b81f9SEvan Bacon return; 546d6b81f9SEvan Bacon } 556d6b81f9SEvan Bacon 566d6b81f9SEvan Bacon next(); 576d6b81f9SEvan Bacon }); 586d6b81f9SEvan Bacon 596d6b81f9SEvan Bacon // pipe 606d6b81f9SEvan Bacon stream.pipe(res); 616d6b81f9SEvan Bacon }; 626d6b81f9SEvan Bacon } 636d6b81f9SEvan Bacon} 64