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