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