220 lines
7.9 KiB
JavaScript
220 lines
7.9 KiB
JavaScript
|
import { UnavailabilityError } from '@unimodules/core';
|
||
|
import { AppState, Linking, Platform } from 'react-native';
|
||
|
import ExponentWebBrowser from './ExpoWebBrowser';
|
||
|
import { WebBrowserResultType, } from './WebBrowser.types';
|
||
|
export { WebBrowserResultType, };
|
||
|
const emptyCustomTabsPackages = {
|
||
|
defaultBrowserPackage: undefined,
|
||
|
preferredBrowserPackage: undefined,
|
||
|
browserPackages: [],
|
||
|
servicePackages: [],
|
||
|
};
|
||
|
export async function getCustomTabsSupportingBrowsersAsync() {
|
||
|
if (!ExponentWebBrowser.getCustomTabsSupportingBrowsersAsync) {
|
||
|
throw new UnavailabilityError('WebBrowser', 'getCustomTabsSupportingBrowsersAsync');
|
||
|
}
|
||
|
if (Platform.OS !== 'android') {
|
||
|
return emptyCustomTabsPackages;
|
||
|
}
|
||
|
else {
|
||
|
return await ExponentWebBrowser.getCustomTabsSupportingBrowsersAsync();
|
||
|
}
|
||
|
}
|
||
|
export async function warmUpAsync(browserPackage) {
|
||
|
if (!ExponentWebBrowser.warmUpAsync) {
|
||
|
throw new UnavailabilityError('WebBrowser', 'warmUpAsync');
|
||
|
}
|
||
|
if (Platform.OS !== 'android') {
|
||
|
return {};
|
||
|
}
|
||
|
else {
|
||
|
return await ExponentWebBrowser.warmUpAsync(browserPackage);
|
||
|
}
|
||
|
}
|
||
|
export async function mayInitWithUrlAsync(url, browserPackage) {
|
||
|
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) {
|
||
|
if (!ExponentWebBrowser.coolDownAsync) {
|
||
|
throw new UnavailabilityError('WebBrowser', 'coolDownAsync');
|
||
|
}
|
||
|
if (Platform.OS !== 'android') {
|
||
|
return {};
|
||
|
}
|
||
|
else {
|
||
|
return await ExponentWebBrowser.coolDownAsync(browserPackage);
|
||
|
}
|
||
|
}
|
||
|
let browserLocked = false;
|
||
|
export async function openBrowserAsync(url, browserParams = {}) {
|
||
|
if (!ExponentWebBrowser.openBrowserAsync) {
|
||
|
throw new UnavailabilityError('WebBrowser', 'openBrowserAsync');
|
||
|
}
|
||
|
if (browserLocked) {
|
||
|
// Prevent multiple sessions from running at the same time, WebBrowser doesn't
|
||
|
// support it this makes the behavior predictable.
|
||
|
if (__DEV__) {
|
||
|
console.warn('Attempted to call WebBrowser.openBrowserAsync multiple times while already active. Only one WebBrowser controller can be active at any given time.');
|
||
|
}
|
||
|
return { type: WebBrowserResultType.LOCKED };
|
||
|
}
|
||
|
browserLocked = true;
|
||
|
let result;
|
||
|
try {
|
||
|
result = await ExponentWebBrowser.openBrowserAsync(url, browserParams);
|
||
|
}
|
||
|
finally {
|
||
|
// WebBrowser session complete, unset lock
|
||
|
browserLocked = false;
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
export function dismissBrowser() {
|
||
|
if (!ExponentWebBrowser.dismissBrowser) {
|
||
|
throw new UnavailabilityError('WebBrowser', 'dismissBrowser');
|
||
|
}
|
||
|
ExponentWebBrowser.dismissBrowser();
|
||
|
}
|
||
|
export async function openAuthSessionAsync(url, redirectUrl, browserParams = {}) {
|
||
|
if (_authSessionIsNativelySupported()) {
|
||
|
if (!ExponentWebBrowser.openAuthSessionAsync) {
|
||
|
throw new UnavailabilityError('WebBrowser', 'openAuthSessionAsync');
|
||
|
}
|
||
|
if (Platform.OS === 'web') {
|
||
|
return ExponentWebBrowser.openAuthSessionAsync(url, redirectUrl, browserParams);
|
||
|
}
|
||
|
return ExponentWebBrowser.openAuthSessionAsync(url, redirectUrl);
|
||
|
}
|
||
|
else {
|
||
|
return _openAuthSessionPolyfillAsync(url, redirectUrl, browserParams);
|
||
|
}
|
||
|
}
|
||
|
export function dismissAuthSession() {
|
||
|
if (_authSessionIsNativelySupported()) {
|
||
|
if (!ExponentWebBrowser.dismissAuthSession) {
|
||
|
throw new UnavailabilityError('WebBrowser', 'dismissAuthSession');
|
||
|
}
|
||
|
ExponentWebBrowser.dismissAuthSession();
|
||
|
}
|
||
|
else {
|
||
|
if (!ExponentWebBrowser.dismissBrowser) {
|
||
|
throw new UnavailabilityError('WebBrowser', 'dismissAuthSession');
|
||
|
}
|
||
|
ExponentWebBrowser.dismissBrowser();
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* Attempts to complete an auth session in the browser.
|
||
|
*
|
||
|
* @param options
|
||
|
*/
|
||
|
export function maybeCompleteAuthSession(options = {}) {
|
||
|
if (ExponentWebBrowser.maybeCompleteAuthSession) {
|
||
|
return ExponentWebBrowser.maybeCompleteAuthSession(options);
|
||
|
}
|
||
|
return { type: 'failed', message: 'Not supported on this platform' };
|
||
|
}
|
||
|
/* iOS <= 10 and Android polyfill for SFAuthenticationSession flow */
|
||
|
function _authSessionIsNativelySupported() {
|
||
|
if (Platform.OS === 'android') {
|
||
|
return false;
|
||
|
}
|
||
|
else if (Platform.OS === 'web') {
|
||
|
return true;
|
||
|
}
|
||
|
const versionNumber = parseInt(String(Platform.Version), 10);
|
||
|
return versionNumber >= 11;
|
||
|
}
|
||
|
let _redirectHandler = 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;
|
||
|
// If the initial AppState.currentState is null, we assume that the first call to
|
||
|
// AppState#change event is not actually triggered by a real change,
|
||
|
// is triggered instead by the bridge capturing the current state
|
||
|
// (https://reactnative.dev/docs/appstate#basic-usage)
|
||
|
let _isAppStateAvailable = AppState.currentState !== null;
|
||
|
function _onAppStateChangeAndroid(state) {
|
||
|
if (!_isAppStateAvailable) {
|
||
|
_isAppStateAvailable = true;
|
||
|
return;
|
||
|
}
|
||
|
if (state === 'active' && _onWebBrowserCloseAndroid) {
|
||
|
_onWebBrowserCloseAndroid();
|
||
|
}
|
||
|
}
|
||
|
async function _openBrowserAndWaitAndroidAsync(startUrl, browserParams = {}) {
|
||
|
const appStateChangedToActive = new Promise(resolve => {
|
||
|
_onWebBrowserCloseAndroid = resolve;
|
||
|
AppState.addEventListener('change', _onAppStateChangeAndroid);
|
||
|
});
|
||
|
let result = { type: WebBrowserResultType.CANCEL };
|
||
|
const { type } = await openBrowserAsync(startUrl, browserParams);
|
||
|
if (type === 'opened') {
|
||
|
await appStateChangedToActive;
|
||
|
result = { type: WebBrowserResultType.DISMISS };
|
||
|
}
|
||
|
AppState.removeEventListener('change', _onAppStateChangeAndroid);
|
||
|
_onWebBrowserCloseAndroid = null;
|
||
|
return result;
|
||
|
}
|
||
|
async function _openAuthSessionPolyfillAsync(startUrl, returnUrl, browserParams = {}) {
|
||
|
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, browserParams),
|
||
|
_waitForRedirectAsync(returnUrl),
|
||
|
]);
|
||
|
}
|
||
|
else {
|
||
|
return await Promise.race([
|
||
|
openBrowserAsync(startUrl, browserParams),
|
||
|
_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) {
|
||
|
return new Promise(resolve => {
|
||
|
_redirectHandler = (event) => {
|
||
|
if (event.url.startsWith(returnUrl)) {
|
||
|
resolve({ url: event.url, type: 'success' });
|
||
|
}
|
||
|
};
|
||
|
Linking.addEventListener('url', _redirectHandler);
|
||
|
});
|
||
|
}
|
||
|
//# sourceMappingURL=WebBrowser.js.map
|