xref: /expo/packages/@expo/server/src/environment.ts (revision e6bad416)
1import {
2  installGlobals as installRemixGlobals,
3  Request,
4  RequestInfo,
5  RequestInit,
6  Response,
7  ResponseInit,
8  Headers,
9} from '@remix-run/node';
10import { URL } from 'node:url';
11
12import { ExpoRouterServerManifestV1FunctionRoute } from './types';
13
14// Ensure these are available for the API Routes.
15export function installGlobals() {
16  installRemixGlobals();
17
18  // @ts-expect-error
19  global.Request = ExpoRequest;
20  // @ts-expect-error
21  global.Response = ExpoResponse;
22  // @ts-expect-error
23  global.ExpoResponse = ExpoResponse;
24  // @ts-expect-error
25  global.ExpoRequest = ExpoRequest;
26}
27
28export class ExpoResponse extends Response {
29  // TODO: Drop when we upgrade to node-fetch v3
30  static json(data: any = undefined, init: ResponseInit = {}): ExpoResponse {
31    const body = JSON.stringify(data);
32
33    if (body === undefined) {
34      throw new TypeError('data is not JSON serializable');
35    }
36
37    const headers = new Headers(init?.headers);
38
39    if (!headers.has('content-type')) {
40      headers.set('content-type', 'application/json');
41    }
42
43    return new ExpoResponse(body, {
44      ...init,
45      headers,
46    });
47  }
48}
49
50export const NON_STANDARD_SYMBOL = Symbol('non-standard');
51
52export class ExpoURL extends URL {
53  static from(url: string, config: ExpoRouterServerManifestV1FunctionRoute): ExpoURL {
54    const expoUrl = new ExpoURL(url);
55    const match = config.namedRegex.exec(expoUrl.pathname);
56    if (match?.groups) {
57      for (const [key, value] of Object.entries(match.groups)) {
58        const namedKey = config.routeKeys[key];
59        expoUrl.searchParams.set(namedKey, value);
60      }
61    }
62
63    return expoUrl;
64  }
65}
66
67export class ExpoRequest extends Request {
68  [NON_STANDARD_SYMBOL]: {
69    url: ExpoURL;
70  };
71
72  constructor(info: RequestInfo, init?: RequestInit) {
73    super(info, init);
74
75    this[NON_STANDARD_SYMBOL] = {
76      url: new ExpoURL(typeof info !== 'string' && 'url' in info ? info.url : String(info)),
77    };
78  }
79
80  public get expoUrl() {
81    return this[NON_STANDARD_SYMBOL].url;
82  }
83}
84