UNPKG

4.62 kBJavaScriptView Raw
1// @flow
2
3import { Platform } from 'react-native';
4import qs from 'qs';
5
6import Constants from './Constants';
7import WebBrowser from './WebBrowser';
8
9type AuthSessionOptions = {
10 authUrl: string,
11 returnUrl?: string,
12};
13
14type AuthSessionResult =
15 | { type: 'cancel' | 'dismissed' | 'locked' }
16 | {
17 type: 'error' | 'success',
18 errorCode: ?string,
19 params: Object,
20 url: string,
21 };
22
23const BASE_URL = `https://auth.expo.io`;
24let _authLock = false;
25
26async function startAsync(options: AuthSessionOptions): Promise<AuthSessionResult> {
27 const returnUrl = options.returnUrl || getDefaultReturnUrl();
28 const authUrl = options.authUrl;
29 const startUrl = getStartUrl(authUrl, returnUrl);
30
31 // Prevent accidentally starting to an empty url
32 if (!authUrl) {
33 throw new Error(
34 'No authUrl provided to AuthSession.startAsync. An authUrl is required -- it points to the page where the user will be able to sign in.'
35 );
36 }
37
38 // Prevent multiple sessions from running at the same time, WebBrowser doesn't
39 // support it this makes the behavior predictable.
40 if (_authLock) {
41 if (__DEV__) {
42 console.warn(
43 'Attempted to call AuthSession.startAsync multiple times while already active. Only one AuthSession can be active at any given time'
44 );
45 }
46
47 return { type: 'locked' };
48 }
49
50 // About to start session, set lock
51 _authLock = true;
52
53 let result;
54 try {
55 result = await _openWebBrowserAsync(startUrl, returnUrl);
56 } finally {
57 // WebBrowser session complete, unset lock
58 _authLock = false;
59 }
60
61 // Handle failures
62 if (!result) {
63 throw new Error('Unexpected missing AuthSession result');
64 }
65 if (!result.url) {
66 if (result.type) {
67 return result;
68 } else {
69 throw new Error('Unexpected AuthSession result with missing type');
70 }
71 }
72
73 let { params, errorCode } = parseUrl(result.url);
74
75 return {
76 type: errorCode ? 'error' : 'success',
77 params,
78 errorCode,
79 url: result.url,
80 };
81}
82
83function dismiss() {
84 WebBrowser.dismissAuthSession();
85}
86
87async function _openWebBrowserAsync(startUrl, returnUrl) {
88 // $FlowIssue: Flow thinks the awaited result can be a promise
89 let result = await WebBrowser.openAuthSessionAsync(startUrl, returnUrl);
90 if (result.type === 'cancel' || result.type === 'dismissed') {
91 return { type: result.type };
92 }
93
94 return result;
95}
96
97function getStartUrl(authUrl: string, returnUrl: string): string {
98 let queryString = qs.stringify({
99 authUrl,
100 returnUrl,
101 });
102
103 return `${getRedirectUrl()}/start?${queryString}`;
104}
105
106function getRedirectUrl(): string {
107 const redirectUrl = `${BASE_URL}/${Constants.manifest.id}`;
108 if (__DEV__) {
109 _warnIfAnonymous(Constants.manifest.id, redirectUrl);
110 }
111 return redirectUrl;
112}
113
114function getDefaultReturnUrl(): string {
115 // TODO: remove this when we make Constants.linkingUrl consistent everywhere
116 if (Platform.OS === 'android' && Constants.appOwnership === 'standalone') {
117 return `${Constants.linkingUrl}+expo-auth-session`;
118 } else {
119 return `${Constants.linkingUrl}expo-auth-session`;
120 }
121}
122
123function parseUrl(url: string): { errorCode: ?string, params: Object } {
124 let parts = url.split('#');
125 let hash = parts[1];
126 let partsWithoutHash = parts[0].split('?');
127 let queryString = partsWithoutHash[partsWithoutHash.length - 1];
128
129 // Get query string (?hello=world)
130 let parsedSearch = qs.parse(queryString);
131
132 // Pull errorCode off of params
133 let { errorCode } = parsedSearch;
134 delete parsedSearch.errorCode;
135
136 // Get hash (#abc=example)
137 let parsedHash = {};
138 if (parts[1]) {
139 parsedHash = qs.parse(hash);
140 }
141
142 // Merge search and hash
143 let params = {
144 ...parsedSearch,
145 ...parsedHash,
146 };
147
148 return {
149 errorCode,
150 params,
151 };
152}
153
154function _warnIfAnonymous(id, url): void {
155 if (id.startsWith('@anonymous/')) {
156 console.warn(
157 `You are not currently signed in to Expo on your development machine. As a result, the redirect URL for AuthSession will be "${url}". If you are using an OAuth provider that requires whitelisting redirect URLs, we recommend that you do not whitelist this URL -- instead, you should sign in to Expo to acquired a unique redirect URL. Additionally, if you do decide to publish this app using Expo, you will need to register an account to do it.`
158 );
159 }
160}
161
162export default {
163 dismiss,
164 getRedirectUrl,
165 getStartUrl,
166 getDefaultReturnUrl,
167 get getRedirectUri() {
168 console.warn(
169 'Use AuthSession.getRedirectUrl rather than AuthSession.getRedirectUri (Url instead of Uri)'
170 );
171 return getRedirectUrl;
172 },
173 startAsync,
174};