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    req: ServerRequest,
36    res: ServerResponse,
37    next: ServerNext
38  ) => Promise<void> {
39    return async (req: ServerRequest, res: ServerResponse, next: ServerNext) => {
40      if (!this._shouldHandleRequest(req)) {
41        return next();
42      }
43
44      try {
45        return await this.handleRequestAsync(req, res, next);
46      } catch (error: any) {
47        Log.exception(error);
48        // 5xx = Server Error HTTP code
49        res.statusCode = 500;
50        if (typeof error === 'object' && error !== null) {
51          res.end(
52            JSON.stringify({
53              error: error.toString(),
54            })
55          );
56        } else {
57          res.end(`Unexpected error: ${error}`);
58        }
59      }
60    };
61  }
62}
63
64export function disableResponseCache(res: ServerResponse): ServerResponse {
65  res.setHeader('Cache-Control', 'private, no-cache, no-store, must-revalidate');
66  res.setHeader('Expires', '-1');
67  res.setHeader('Pragma', 'no-cache');
68  return res;
69}
70