126ad19fcSEvan Bacon// This MUST be first to ensure that `fetch` is defined in the React Native environment. 226ad19fcSEvan Baconimport 'react-native/Libraries/Core/InitializeCore'; 326ad19fcSEvan Bacon 426ad19fcSEvan Baconimport Constants from 'expo-constants'; 526ad19fcSEvan Baconimport URL from 'url-parse'; 626ad19fcSEvan Bacon 726ad19fcSEvan Baconimport { install, setLocationHref } from './Location'; 8*8a424bebSJames Ideimport getDevServer from '../getDevServer'; 926ad19fcSEvan Bacon 1026ad19fcSEvan Baconlet hasWarned = false; 1126ad19fcSEvan Bacon 1226ad19fcSEvan Baconconst manifest = Constants.expoConfig as Record<string, any> | null; 1326ad19fcSEvan Bacon 1426ad19fcSEvan Bacon// Add a development warning for fetch requests with relative paths 1526ad19fcSEvan Bacon// to ensure developers are aware of the need to configure a production 1626ad19fcSEvan Bacon// base URL in the Expo config (app.json) under `expo.extra.router.origin`. 1726ad19fcSEvan Baconfunction warnProductionOriginNotConfigured(requestUrl: string) { 1826ad19fcSEvan Bacon if (hasWarned) { 1926ad19fcSEvan Bacon return; 2026ad19fcSEvan Bacon } 2126ad19fcSEvan Bacon hasWarned = true; 2226ad19fcSEvan Bacon if (!manifest?.extra?.router?.origin) { 2326ad19fcSEvan Bacon console.warn( 2426ad19fcSEvan Bacon `The relative fetch request "${requestUrl}" will not work in production until the Expo Router Config Plugin (app.json) is configured with the \`origin\` prop set to the base URL of your web server, e.g. \`{ plugins: [["expo-router", { origin: "..." }]] }\`. [Learn more](https://expo.github.io/router/docs/lab/runtime-location)` 2526ad19fcSEvan Bacon ); 2626ad19fcSEvan Bacon } 2726ad19fcSEvan Bacon} 2826ad19fcSEvan Bacon 2926ad19fcSEvan Bacon// TODO: This would be better if native and tied as close to the JS engine as possible, i.e. it should 3026ad19fcSEvan Bacon// reflect the exact location of the JS file that was executed. 3126ad19fcSEvan Baconfunction getBaseUrl() { 3226ad19fcSEvan Bacon if (process.env.NODE_ENV !== 'production') { 3326ad19fcSEvan Bacon // e.g. http://localhost:19006 3426ad19fcSEvan Bacon return getDevServer().url?.replace(/\/$/, ''); 3526ad19fcSEvan Bacon } 3626ad19fcSEvan Bacon 3726ad19fcSEvan Bacon // TODO: Make it official by moving out of `extra` 3826ad19fcSEvan Bacon const productionBaseUrl = manifest?.extra?.router?.origin; 3926ad19fcSEvan Bacon 4026ad19fcSEvan Bacon if (!productionBaseUrl) { 4126ad19fcSEvan Bacon return null; 4226ad19fcSEvan Bacon } 4326ad19fcSEvan Bacon 4426ad19fcSEvan Bacon // Ensure no trailing slash 4526ad19fcSEvan Bacon return productionBaseUrl?.replace(/\/$/, ''); 4626ad19fcSEvan Bacon} 4726ad19fcSEvan Bacon 4826ad19fcSEvan Baconfunction wrapFetchWithWindowLocation(fetch: Function & { __EXPO_BASE_URL_POLYFILLED?: boolean }) { 4926ad19fcSEvan Bacon if (fetch.__EXPO_BASE_URL_POLYFILLED) { 5026ad19fcSEvan Bacon return fetch; 5126ad19fcSEvan Bacon } 5226ad19fcSEvan Bacon 5326ad19fcSEvan Bacon const _fetch = (...props: any[]) => { 5426ad19fcSEvan Bacon if (props[0] && typeof props[0] === 'string' && props[0].startsWith('/')) { 5526ad19fcSEvan Bacon if (process.env.NODE_ENV !== 'production') { 5626ad19fcSEvan Bacon warnProductionOriginNotConfigured(props[0]); 5726ad19fcSEvan Bacon } 5826ad19fcSEvan Bacon 5926ad19fcSEvan Bacon props[0] = new URL(props[0], window.location?.origin).toString(); 6026ad19fcSEvan Bacon } else if (props[0] && typeof props[0] === 'object') { 6126ad19fcSEvan Bacon if (props[0].url && typeof props[0].url === 'string' && props[0].url.startsWith('/')) { 6226ad19fcSEvan Bacon if (process.env.NODE_ENV !== 'production') { 6326ad19fcSEvan Bacon warnProductionOriginNotConfigured(props[0]); 6426ad19fcSEvan Bacon } 6526ad19fcSEvan Bacon 6626ad19fcSEvan Bacon props[0].url = new URL(props[0].url, window.location?.origin).toString(); 6726ad19fcSEvan Bacon } 6826ad19fcSEvan Bacon } 6926ad19fcSEvan Bacon return fetch(...props); 7026ad19fcSEvan Bacon }; 7126ad19fcSEvan Bacon 7226ad19fcSEvan Bacon _fetch.__EXPO_BASE_URL_POLYFILLED = true; 7326ad19fcSEvan Bacon 7426ad19fcSEvan Bacon return _fetch; 7526ad19fcSEvan Bacon} 7626ad19fcSEvan Bacon 7726ad19fcSEvan Baconif (manifest?.extra?.router?.origin !== false) { 7826ad19fcSEvan Bacon // Polyfill window.location in native runtimes. 7926ad19fcSEvan Bacon if (typeof window !== 'undefined' && !window.location) { 8026ad19fcSEvan Bacon const url = getBaseUrl(); 8126ad19fcSEvan Bacon if (url) { 8226ad19fcSEvan Bacon setLocationHref(url); 8326ad19fcSEvan Bacon install(); 8426ad19fcSEvan Bacon } 8526ad19fcSEvan Bacon } 8626ad19fcSEvan Bacon // Polyfill native fetch to support relative URLs 8726ad19fcSEvan Bacon Object.defineProperty(global, 'fetch', { 8826ad19fcSEvan Bacon value: wrapFetchWithWindowLocation(fetch), 8926ad19fcSEvan Bacon }); 9026ad19fcSEvan Bacon} 91