1import { requireNativeModule } from 'expo-modules-core'; 2import { 3 AppState, 4 EmitterSubscription, 5 Linking, 6 Platform, 7 NativeEventSubscription, 8 NativeModules, 9} from 'react-native'; 10 11const DevLauncherAuth = 12 Platform.OS === 'ios' 13 ? requireNativeModule('ExpoDevLauncherAuth') 14 : NativeModules.EXDevLauncherAuth; 15 16let appStateSubscription: NativeEventSubscription | null = null; 17let redirectSubscription: EmitterSubscription | null = null; 18let onWebBrowserCloseAndroid: null | (() => void) = null; 19let isAppStateAvailable: boolean = AppState.currentState !== null; 20 21function onAppStateChangeAndroid(state: any) { 22 if (!isAppStateAvailable) { 23 isAppStateAvailable = true; 24 return; 25 } 26 27 if (state === 'active' && onWebBrowserCloseAndroid) { 28 onWebBrowserCloseAndroid(); 29 } 30} 31 32function stopWaitingForRedirect() { 33 if (!redirectSubscription) { 34 throw new Error( 35 `The WebBrowser auth session is in an invalid state with no redirect handler when one should be set` 36 ); 37 } 38 39 redirectSubscription.remove(); 40 redirectSubscription = null; 41} 42 43async function openBrowserAndWaitAndroidAsync(startUrl: string): Promise<any> { 44 const appStateChangedToActive = new Promise<void>((resolve) => { 45 onWebBrowserCloseAndroid = resolve; 46 appStateSubscription = AppState.addEventListener('change', onAppStateChangeAndroid); 47 }); 48 49 let result = { type: 'cancel' }; 50 await DevLauncherAuth.openWebBrowserAsync(startUrl); 51 const type = 'opened'; 52 53 if (type === 'opened') { 54 await appStateChangedToActive; 55 result = { type: 'dismiss' }; 56 } 57 58 if (appStateSubscription != null) { 59 appStateSubscription.remove(); 60 appStateSubscription = null; 61 } 62 onWebBrowserCloseAndroid = null; 63 return result; 64} 65 66function waitForRedirectAsync(returnUrl: string): Promise<any> { 67 return new Promise((resolve) => { 68 const redirectHandler = (event: any) => { 69 if (event.url.startsWith(returnUrl)) { 70 resolve({ url: event.url, type: 'success' }); 71 } 72 }; 73 74 redirectSubscription = Linking.addEventListener('url', redirectHandler); 75 }); 76} 77 78async function openAuthSessionPolyfillAsync(startUrl: string, returnUrl: string): Promise<any> { 79 if (redirectSubscription) { 80 throw new Error( 81 `The WebBrowser's auth session is in an invalid state with a redirect handler set when it should not be` 82 ); 83 } 84 85 if (onWebBrowserCloseAndroid) { 86 throw new Error(`WebBrowser is already open, only one can be open at a time`); 87 } 88 89 try { 90 return await Promise.race([ 91 openBrowserAndWaitAndroidAsync(startUrl), 92 waitForRedirectAsync(returnUrl), 93 ]); 94 } finally { 95 stopWaitingForRedirect(); 96 } 97} 98 99export async function openAuthSessionAsync(url: string, returnUrl: string): Promise<any> { 100 if (DevLauncherAuth.openAuthSessionAsync) { 101 // iOS 102 return await DevLauncherAuth.openAuthSessionAsync(url, returnUrl); 103 } 104 // Android 105 return await openAuthSessionPolyfillAsync(url, returnUrl); 106} 107 108export async function getAuthSchemeAsync(): Promise<string> { 109 if (Platform.OS === 'android') { 110 return 'expo-dev-launcher'; 111 } 112 113 return await DevLauncherAuth.getAuthSchemeAsync(); 114} 115 116export async function setSessionAsync(session: string): Promise<void> { 117 return await DevLauncherAuth.setSessionAsync(session); 118} 119 120export async function restoreSessionAsync(): Promise<{ 121 [key: string]: any; 122 sessionSecret: string; 123}> { 124 return await DevLauncherAuth.restoreSessionAsync(); 125} 126