-
Notifications
You must be signed in to change notification settings - Fork 4.7k
/
WebBrowser.ts
228 lines (198 loc) · 6.76 KB
/
WebBrowser.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
import { AppState, Linking, Platform, AppStateStatus } from 'react-native';
import { UnavailabilityError } from '@unimodules/core';
import ExponentWebBrowser from './ExpoWebBrowser';
import {
RedirectEvent,
OpenBrowserOptions,
AuthSessionResult,
CustomTabsBrowsersResults,
BrowserResult,
RedirectResult,
MayInitWithUrlResult,
WarmUpResult,
CoolDownResult,
} from './WebBrowser.types';
const emptyCustomTabsPackages: CustomTabsBrowsersResults = {
defaultBrowserPackage: undefined,
preferredBrowserPackage: undefined,
browserPackages: [],
servicePackages: [],
};
export async function getCustomTabsSupportingBrowsersAsync(): Promise<CustomTabsBrowsersResults> {
if (!ExponentWebBrowser.getCustomTabsSupportingBrowsersAsync) {
throw new UnavailabilityError('WebBrowser', 'getCustomTabsSupportingBrowsersAsync');
}
if (Platform.OS !== 'android') {
return emptyCustomTabsPackages;
} else {
return await ExponentWebBrowser.getCustomTabsSupportingBrowsersAsync();
}
}
export async function warmUpAsync(browserPackage?: string): Promise<WarmUpResult> {
if (!ExponentWebBrowser.warmUpAsync) {
throw new UnavailabilityError('WebBrowser', 'warmUpAsync');
}
if (Platform.OS !== 'android') {
return {};
} else {
return await ExponentWebBrowser.warmUpAsync(browserPackage);
}
}
export async function mayInitWithUrlAsync(
url: string,
browserPackage?: string
): Promise<MayInitWithUrlResult> {
if (!ExponentWebBrowser.mayInitWithUrlAsync) {
throw new UnavailabilityError('WebBrowser', 'mayInitWithUrlAsync');
}
if (Platform.OS !== 'android') {
return {};
} else {
return await ExponentWebBrowser.mayInitWithUrlAsync(url, browserPackage);
}
}
export async function coolDownAsync(browserPackage?: string): Promise<CoolDownResult> {
if (!ExponentWebBrowser.coolDownAsync) {
throw new UnavailabilityError('WebBrowser', 'coolDownAsync');
}
if (Platform.OS !== 'android') {
return {};
} else {
return await ExponentWebBrowser.coolDownAsync(browserPackage);
}
}
export async function openBrowserAsync(
url: string,
browserParams: OpenBrowserOptions = {}
): Promise<BrowserResult> {
if (!ExponentWebBrowser.openBrowserAsync) {
throw new UnavailabilityError('WebBrowser', 'openBrowserAsync');
}
return await ExponentWebBrowser.openBrowserAsync(url, browserParams);
}
export function dismissBrowser(): void {
if (!ExponentWebBrowser.dismissBrowser) {
throw new UnavailabilityError('WebBrowser', 'dismissBrowser');
}
ExponentWebBrowser.dismissBrowser();
}
export async function openAuthSessionAsync(
url: string,
redirectUrl: string
): Promise<AuthSessionResult> {
if (_authSessionIsNativelySupported()) {
if (!ExponentWebBrowser.openAuthSessionAsync) {
throw new UnavailabilityError('WebBrowser', 'openAuthSessionAsync');
}
return ExponentWebBrowser.openAuthSessionAsync(url, redirectUrl);
} else {
return _openAuthSessionPolyfillAsync(url, redirectUrl);
}
}
export function dismissAuthSession(): void {
if (_authSessionIsNativelySupported()) {
if (!ExponentWebBrowser.dismissAuthSession) {
throw new UnavailabilityError('WebBrowser', 'dismissAuthSession');
}
ExponentWebBrowser.dismissAuthSession();
} else {
if (!ExponentWebBrowser.dismissBrowser) {
throw new UnavailabilityError('WebBrowser', 'dismissAuthSession');
}
ExponentWebBrowser.dismissBrowser();
}
}
/* iOS <= 10 and Android polyfill for SFAuthenticationSession flow */
function _authSessionIsNativelySupported(): boolean {
if (Platform.OS === 'android') {
return false;
}
const versionNumber = parseInt(String(Platform.Version), 10);
return versionNumber >= 11;
}
let _redirectHandler: ((event: RedirectEvent) => void) | null = null;
/*
* openBrowserAsync on Android doesn't wait until closed, so we need to polyfill
* it with AppState
*/
// Store the `resolve` function from a Promise to fire when the AppState
// returns to active
let _onWebBrowserCloseAndroid: null | (() => void) = null;
// Store previous app state to check whether the listener has ever been attached
let _previousAppState: null | string = AppState.currentState;
function _onAppStateChangeAndroid(state: AppStateStatus) {
// if _previousAppState is null, we assume that the first call to
// AppState#change event is not actually triggered by a real change
// (https://facebook.github.io/react-native/docs/appstate#basic-usage)
if (_previousAppState === null) {
_previousAppState = state;
return;
}
if (state === 'active' && _onWebBrowserCloseAndroid) {
_onWebBrowserCloseAndroid();
}
}
async function _openBrowserAndWaitAndroidAsync(startUrl: string): Promise<BrowserResult> {
let appStateChangedToActive = new Promise(resolve => {
_onWebBrowserCloseAndroid = resolve;
AppState.addEventListener('change', _onAppStateChangeAndroid);
});
let result: BrowserResult = { type: 'cancel' };
let { type } = await openBrowserAsync(startUrl);
if (type === 'opened') {
await appStateChangedToActive;
result = { type: 'dismiss' };
}
AppState.removeEventListener('change', _onAppStateChangeAndroid);
_onWebBrowserCloseAndroid = null;
return result;
}
async function _openAuthSessionPolyfillAsync(
startUrl: string,
returnUrl: string
): Promise<AuthSessionResult> {
if (_redirectHandler) {
throw new Error(
`The WebBrowser's auth session is in an invalid state with a redirect handler set when it should not be`
);
}
if (_onWebBrowserCloseAndroid) {
throw new Error(`WebBrowser is already open, only one can be open at a time`);
}
try {
if (Platform.OS === 'android') {
return await Promise.race([
_openBrowserAndWaitAndroidAsync(startUrl),
_waitForRedirectAsync(returnUrl),
]);
} else {
return await Promise.race([openBrowserAsync(startUrl), _waitForRedirectAsync(returnUrl)]);
}
} finally {
// We can't dismiss the browser on Android, only call this when it's available.
// Users on Android need to manually press the 'x' button in Chrome Custom Tabs, sadly.
if (ExponentWebBrowser.dismissBrowser) {
ExponentWebBrowser.dismissBrowser();
}
_stopWaitingForRedirect();
}
}
function _stopWaitingForRedirect() {
if (!_redirectHandler) {
throw new Error(
`The WebBrowser auth session is in an invalid state with no redirect handler when one should be set`
);
}
Linking.removeEventListener('url', _redirectHandler);
_redirectHandler = null;
}
function _waitForRedirectAsync(returnUrl: string): Promise<RedirectResult> {
return new Promise(resolve => {
_redirectHandler = (event: RedirectEvent) => {
if (event.url.startsWith(returnUrl)) {
resolve({ url: event.url, type: 'success' });
}
};
Linking.addEventListener('url', _redirectHandler);
});
}