18d307f52SEvan Baconimport { parse } from 'url';
28d307f52SEvan Bacon
38d307f52SEvan Baconimport { ServerNext, ServerRequest, ServerResponse } from './server.types';
4*8a424bebSJames Ideimport * as Log from '../../../log';
58d307f52SEvan Bacon
68d307f52SEvan Bacon/** Base middleware creator for Expo dev servers. */
78d307f52SEvan Baconexport abstract class ExpoMiddleware {
8*8a424bebSJames Ide  constructor(
9*8a424bebSJames Ide    protected projectRoot: string,
10*8a424bebSJames Ide    protected supportedPaths: string[]
11*8a424bebSJames Ide  ) {}
128d307f52SEvan Bacon
138d307f52SEvan Bacon  /**
148d307f52SEvan Bacon   * Returns true when the middleware should handle the incoming server request.
158d307f52SEvan Bacon   * Exposed for testing.
168d307f52SEvan Bacon   */
178d307f52SEvan Bacon  _shouldHandleRequest(req: ServerRequest): boolean {
1829975bfdSEvan Bacon    if (!req.url) {
1929975bfdSEvan Bacon      return false;
2029975bfdSEvan Bacon    }
2129975bfdSEvan Bacon    const parsed = parse(req.url);
228d307f52SEvan Bacon    // Strip the query params
2329975bfdSEvan Bacon    if (!parsed.pathname) {
2429975bfdSEvan Bacon      return false;
2529975bfdSEvan Bacon    }
2629975bfdSEvan Bacon
2729975bfdSEvan Bacon    return this.supportedPaths.includes(parsed.pathname);
288d307f52SEvan Bacon  }
298d307f52SEvan Bacon
308d307f52SEvan Bacon  abstract handleRequestAsync(
318d307f52SEvan Bacon    req: ServerRequest,
328d307f52SEvan Bacon    res: ServerResponse,
338d307f52SEvan Bacon    next: ServerNext
348d307f52SEvan Bacon  ): Promise<void>;
358d307f52SEvan Bacon
368d307f52SEvan Bacon  /** Create a server middleware handler. */
376d6b81f9SEvan Bacon  public getHandler() {
386d6b81f9SEvan Bacon    const internalMiddleware = async (
398d307f52SEvan Bacon      req: ServerRequest,
408d307f52SEvan Bacon      res: ServerResponse,
418d307f52SEvan Bacon      next: ServerNext
426d6b81f9SEvan Bacon    ) => {
438d307f52SEvan Bacon      try {
448d307f52SEvan Bacon        return await this.handleRequestAsync(req, res, next);
4529975bfdSEvan Bacon      } catch (error: any) {
4629975bfdSEvan Bacon        Log.exception(error);
478d307f52SEvan Bacon        // 5xx = Server Error HTTP code
488d307f52SEvan Bacon        res.statusCode = 500;
4929975bfdSEvan Bacon        if (typeof error === 'object' && error !== null) {
508d307f52SEvan Bacon          res.end(
518d307f52SEvan Bacon            JSON.stringify({
5229975bfdSEvan Bacon              error: error.toString(),
538d307f52SEvan Bacon            })
548d307f52SEvan Bacon          );
558d307f52SEvan Bacon        } else {
5629975bfdSEvan Bacon          res.end(`Unexpected error: ${error}`);
578d307f52SEvan Bacon        }
588d307f52SEvan Bacon      }
598d307f52SEvan Bacon    };
606d6b81f9SEvan Bacon    const middleware = async (req: ServerRequest, res: ServerResponse, next: ServerNext) => {
616d6b81f9SEvan Bacon      if (!this._shouldHandleRequest(req)) {
626d6b81f9SEvan Bacon        return next();
636d6b81f9SEvan Bacon      }
646d6b81f9SEvan Bacon      return internalMiddleware(req, res, next);
656d6b81f9SEvan Bacon    };
666d6b81f9SEvan Bacon
676d6b81f9SEvan Bacon    middleware.internal = internalMiddleware;
686d6b81f9SEvan Bacon
696d6b81f9SEvan Bacon    return middleware;
708d307f52SEvan Bacon  }
718d307f52SEvan Bacon}
728d307f52SEvan Bacon
738d307f52SEvan Baconexport function disableResponseCache(res: ServerResponse): ServerResponse {
748d307f52SEvan Bacon  res.setHeader('Cache-Control', 'private, no-cache, no-store, must-revalidate');
758d307f52SEvan Bacon  res.setHeader('Expires', '-1');
768d307f52SEvan Bacon  res.setHeader('Pragma', 'no-cache');
778d307f52SEvan Bacon  return res;
788d307f52SEvan Bacon}
79