1 |
|
2 |
|
3 | import { Platform } from 'react-native';
|
4 | import qs from 'qs';
|
5 |
|
6 | import Constants from './Constants';
|
7 | import WebBrowser from './WebBrowser';
|
8 |
|
9 | type AuthSessionOptions = {
|
10 | authUrl: string,
|
11 | returnUrl?: string,
|
12 | };
|
13 |
|
14 | type AuthSessionResult =
|
15 | | { type: 'cancel' | 'dismissed' | 'locked' }
|
16 | | {
|
17 | type: 'error' | 'success',
|
18 | errorCode: ?string,
|
19 | params: Object,
|
20 | url: string,
|
21 | };
|
22 |
|
23 | const BASE_URL = `https://auth.expo.io`;
|
24 | let _authLock = false;
|
25 |
|
26 | async 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 |
|
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 |
|
39 |
|
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 |
|
51 | _authLock = true;
|
52 |
|
53 | let result;
|
54 | try {
|
55 | result = await _openWebBrowserAsync(startUrl, returnUrl);
|
56 | } finally {
|
57 |
|
58 | _authLock = false;
|
59 | }
|
60 |
|
61 |
|
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 |
|
83 | function dismiss() {
|
84 | WebBrowser.dismissAuthSession();
|
85 | }
|
86 |
|
87 | async function _openWebBrowserAsync(startUrl, returnUrl) {
|
88 |
|
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 |
|
97 | function getStartUrl(authUrl: string, returnUrl: string): string {
|
98 | let queryString = qs.stringify({
|
99 | authUrl,
|
100 | returnUrl,
|
101 | });
|
102 |
|
103 | return `${getRedirectUrl()}/start?${queryString}`;
|
104 | }
|
105 |
|
106 | function 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 |
|
114 | function getDefaultReturnUrl(): string {
|
115 |
|
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 |
|
123 | function 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 |
|
130 | let parsedSearch = qs.parse(queryString);
|
131 |
|
132 |
|
133 | let { errorCode } = parsedSearch;
|
134 | delete parsedSearch.errorCode;
|
135 |
|
136 |
|
137 | let parsedHash = {};
|
138 | if (parts[1]) {
|
139 | parsedHash = qs.parse(hash);
|
140 | }
|
141 |
|
142 |
|
143 | let params = {
|
144 | ...parsedSearch,
|
145 | ...parsedHash,
|
146 | };
|
147 |
|
148 | return {
|
149 | errorCode,
|
150 | params,
|
151 | };
|
152 | }
|
153 |
|
154 | function _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 |
|
162 | export 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 | };
|