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