1// This MUST be first to ensure that `fetch` is defined in the React Native environment. 2import 'react-native/Libraries/Core/InitializeCore'; 3 4import Constants from 'expo-constants'; 5import URL from 'url-parse'; 6 7import { install, setLocationHref } from './Location'; 8import getDevServer from '../getDevServer'; 9 10let hasWarned = false; 11 12const manifest = Constants.expoConfig as Record<string, any> | null; 13 14// Add a development warning for fetch requests with relative paths 15// to ensure developers are aware of the need to configure a production 16// base URL in the Expo config (app.json) under `expo.extra.router.origin`. 17function warnProductionOriginNotConfigured(requestUrl: string) { 18 if (hasWarned) { 19 return; 20 } 21 hasWarned = true; 22 if (!manifest?.extra?.router?.origin) { 23 console.warn( 24 `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)` 25 ); 26 } 27} 28 29// TODO: This would be better if native and tied as close to the JS engine as possible, i.e. it should 30// reflect the exact location of the JS file that was executed. 31function getBaseUrl() { 32 if (process.env.NODE_ENV !== 'production') { 33 // e.g. http://localhost:19006 34 return getDevServer().url?.replace(/\/$/, ''); 35 } 36 37 // TODO: Make it official by moving out of `extra` 38 const productionBaseUrl = manifest?.extra?.router?.origin; 39 40 if (!productionBaseUrl) { 41 return null; 42 } 43 44 // Ensure no trailing slash 45 return productionBaseUrl?.replace(/\/$/, ''); 46} 47 48function wrapFetchWithWindowLocation(fetch: Function & { __EXPO_BASE_URL_POLYFILLED?: boolean }) { 49 if (fetch.__EXPO_BASE_URL_POLYFILLED) { 50 return fetch; 51 } 52 53 const _fetch = (...props: any[]) => { 54 if (props[0] && typeof props[0] === 'string' && props[0].startsWith('/')) { 55 if (process.env.NODE_ENV !== 'production') { 56 warnProductionOriginNotConfigured(props[0]); 57 } 58 59 props[0] = new URL(props[0], window.location?.origin).toString(); 60 } else if (props[0] && typeof props[0] === 'object') { 61 if (props[0].url && typeof props[0].url === 'string' && props[0].url.startsWith('/')) { 62 if (process.env.NODE_ENV !== 'production') { 63 warnProductionOriginNotConfigured(props[0]); 64 } 65 66 props[0].url = new URL(props[0].url, window.location?.origin).toString(); 67 } 68 } 69 return fetch(...props); 70 }; 71 72 _fetch.__EXPO_BASE_URL_POLYFILLED = true; 73 74 return _fetch; 75} 76 77if (manifest?.extra?.router?.origin !== false) { 78 // Polyfill window.location in native runtimes. 79 if (typeof window !== 'undefined' && !window.location) { 80 const url = getBaseUrl(); 81 if (url) { 82 setLocationHref(url); 83 install(); 84 } 85 } 86 // Polyfill native fetch to support relative URLs 87 Object.defineProperty(global, 'fetch', { 88 value: wrapFetchWithWindowLocation(fetch), 89 }); 90} 91