1import path from 'path';
2import send from 'send';
3import { parse } from 'url';
4
5import { env } from '../../../utils/env';
6import { parsePlatformHeader } from './resolvePlatform';
7import { ServerRequest, ServerResponse } from './server.types';
8
9const debug = require('debug')('expo:start:server:middleware:serveStatic') as typeof console.log;
10
11/**
12 * Adds support for serving the files in the static `public/` folder to web apps.
13 */
14export class ServeStaticMiddleware {
15  constructor(private projectRoot: string) {}
16  getHandler() {
17    const publicPath = path.join(this.projectRoot, env.EXPO_PUBLIC_FOLDER);
18
19    debug(`Serving static files from:`, publicPath);
20    const opts = {
21      root: publicPath,
22    };
23    return (req: ServerRequest, res: ServerResponse, next: any) => {
24      if (!req?.url || (req.method !== 'GET' && req.method !== 'HEAD')) {
25        return next();
26      }
27
28      const platform = parsePlatformHeader(req);
29      // Currently this is web-only
30      if (platform && platform !== 'web') {
31        return next();
32      }
33
34      const pathname = parse(req.url).pathname;
35      if (!pathname) {
36        return next();
37      }
38
39      debug(`Maybe serve static:`, pathname);
40      const stream = send(req, pathname, opts);
41
42      // add file listener for fallthrough
43      let forwardError = false;
44      stream.on('file', function onFile() {
45        // once file is determined, always forward error
46        forwardError = true;
47      });
48
49      // forward errors
50      stream.on('error', function error(err: any) {
51        if (forwardError || !(err.statusCode < 500)) {
52          next(err);
53          return;
54        }
55
56        next();
57      });
58
59      // pipe
60      stream.pipe(res);
61    };
62  }
63}
64