1import { parse } from 'url';
2
3import * as Log from '../../../log';
4import { ServerNext, ServerRequest, ServerResponse } from './server.types';
5
6/** Base middleware creator for Expo dev servers. */
7export abstract class ExpoMiddleware {
8  constructor(protected projectRoot: string, protected supportedPaths: string[]) {}
9
10  /**
11   * Returns true when the middleware should handle the incoming server request.
12   * Exposed for testing.
13   */
14  _shouldHandleRequest(req: ServerRequest): boolean {
15    if (!req.url) {
16      return false;
17    }
18    const parsed = parse(req.url);
19    // Strip the query params
20    if (!parsed.pathname) {
21      return false;
22    }
23
24    return this.supportedPaths.includes(parsed.pathname);
25  }
26
27  abstract handleRequestAsync(
28    req: ServerRequest,
29    res: ServerResponse,
30    next: ServerNext
31  ): Promise<void>;
32
33  /** Create a server middleware handler. */
34  public getHandler() {
35    const internalMiddleware = async (
36      req: ServerRequest,
37      res: ServerResponse,
38      next: ServerNext
39    ) => {
40      try {
41        return await this.handleRequestAsync(req, res, next);
42      } catch (error: any) {
43        Log.exception(error);
44        // 5xx = Server Error HTTP code
45        res.statusCode = 500;
46        if (typeof error === 'object' && error !== null) {
47          res.end(
48            JSON.stringify({
49              error: error.toString(),
50            })
51          );
52        } else {
53          res.end(`Unexpected error: ${error}`);
54        }
55      }
56    };
57    const middleware = async (req: ServerRequest, res: ServerResponse, next: ServerNext) => {
58      if (!this._shouldHandleRequest(req)) {
59        return next();
60      }
61      return internalMiddleware(req, res, next);
62    };
63
64    middleware.internal = internalMiddleware;
65
66    return middleware;
67  }
68}
69
70export function disableResponseCache(res: ServerResponse): ServerResponse {
71  res.setHeader('Cache-Control', 'private, no-cache, no-store, must-revalidate');
72  res.setHeader('Expires', '-1');
73  res.setHeader('Pragma', 'no-cache');
74  return res;
75}
76