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