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