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