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