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