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