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