1import Constants, { ExecutionEnvironment } from 'expo-constants';
2import * as Linking from 'expo-linking';
3import URL from 'url-parse';
4
5// This is only run on native.
6function extractExactPathFromURL(url: string): string {
7  if (
8    // If a universal link / app link / web URL is used, we should use the path
9    // from the URL, while stripping the origin.
10    url.match(/^https?:\/\//)
11  ) {
12    const { origin, href } = new URL(url);
13    return href.replace(origin, '');
14  }
15
16  // Handle special URLs used in Expo Go: `/--/pathname` -> `pathname`
17  if (
18    Constants.executionEnvironment === ExecutionEnvironment.StoreClient &&
19    // while not exhaustive, `exp` and `exps` are the only two schemes which
20    // are passed through to other apps in Expo Go.
21    url.match(/^exp(s)?:\/\//)
22  ) {
23    const pathname = url.match(/exps?:\/\/.*?\/--\/(.*)/)?.[1];
24    if (pathname) {
25      return fromDeepLink('a://' + pathname);
26    }
27
28    const res = Linking.parse(url);
29
30    const qs = !res.queryParams
31      ? ''
32      : Object.entries(res.queryParams)
33          .map(([k, v]) => `${k}=${v}`)
34          .join('&');
35    return (
36      adjustPathname({ hostname: res.hostname, pathname: res.path || '' }) + (qs ? '?' + qs : '')
37    );
38  }
39
40  // TODO: Support dev client URLs
41
42  return fromDeepLink(url);
43}
44
45/** Major hack to support the makeshift expo-development-client system. */
46function isExpoDevelopmentClient(url: URL<Record<string, string | undefined>>): boolean {
47  return !!url.hostname.match(/^expo-development-client$/);
48}
49
50function fromDeepLink(url: string): string {
51  // This is for all standard deep links, e.g. `foobar://` where everything
52  // after the `://` is the path.
53  const res = new URL(url, true);
54
55  if (isExpoDevelopmentClient(res)) {
56    if (!res.query || !res.query.url) {
57      return '';
58    }
59    const incomingUrl = res.query.url;
60    return extractExactPathFromURL(decodeURI(incomingUrl));
61  }
62
63  const qs = !res.query
64    ? ''
65    : Object.entries(res.query as Record<string, string>)
66        .map(([k, v]) => `${k}=${decodeURIComponent(v)}`)
67        .join('&');
68
69  let results = '';
70
71  if (res.host) {
72    results += res.host;
73  }
74
75  if (res.pathname) {
76    results += res.pathname;
77  }
78
79  if (qs) {
80    results += '?' + qs;
81  }
82
83  return results;
84}
85
86export function extractExpoPathFromURL(url: string = '') {
87  // TODO: We should get rid of this, dropping specificities is not good
88  return extractExactPathFromURL(url).replace(/^\//, '');
89}
90
91export function adjustPathname(url: { hostname?: string | null; pathname: string }) {
92  if (url.hostname === 'exp.host' || url.hostname === 'u.expo.dev') {
93    // drop the first two segments from pathname:
94    return url.pathname.split('/').slice(2).join('/');
95  }
96  return url.pathname;
97}
98