react-native-oauth-essentials 1.0.12 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/OauthEssentials.podspec +17 -2
- package/README.md +385 -361
- package/android/build.gradle +15 -10
- package/android/src/main/java/com/oauthessentials/CredentialError.kt +4 -1
- package/android/src/main/java/com/oauthessentials/CredentialFactory.kt +24 -2
- package/android/src/main/java/com/oauthessentials/CustomTabsActivity.kt +14 -1
- package/android/src/main/java/com/oauthessentials/OauthEssentialsModule.kt +445 -187
- package/app.plugin.js +1 -0
- package/ios/AppleIdRetriever.swift +25 -26
- package/ios/CredentialError.swift +3 -0
- package/ios/CredentialFactory.swift +38 -20
- package/ios/CredentialsType.swift +1 -0
- package/ios/GoogleIdRetriever.swift +132 -56
- package/ios/HybridRetriever.swift +12 -18
- package/ios/OauthEssentials.h +0 -3
- package/ios/OauthEssentials.mm +45 -2
- package/ios/OauthEssentialsModule.swift +266 -57
- package/ios/PasskeyRetriever.swift +31 -49
- package/ios/PasswordRetriever.swift +10 -30
- package/ios/RCTPromiseSettler.swift +125 -0
- package/jest/index.js +149 -0
- package/lib/module/NativeOauthEssentials.js +10 -1
- package/lib/module/NativeOauthEssentials.js.map +1 -1
- package/lib/module/errors.js +38 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/index.js +432 -58
- package/lib/module/index.js.map +1 -1
- package/lib/module/oauth.js +97 -0
- package/lib/module/oauth.js.map +1 -0
- package/lib/module/platform.js +108 -0
- package/lib/module/platform.js.map +1 -0
- package/lib/module/types.js +46 -1
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/src/NativeOauthEssentials.d.ts +70 -16
- package/lib/typescript/src/NativeOauthEssentials.d.ts.map +1 -1
- package/lib/typescript/src/errors.d.ts +18 -0
- package/lib/typescript/src/errors.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +235 -40
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/oauth.d.ts +32 -0
- package/lib/typescript/src/oauth.d.ts.map +1 -0
- package/lib/typescript/src/platform.d.ts +39 -0
- package/lib/typescript/src/platform.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +190 -2
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +9 -5
- package/plugin/withOauthEssentials.js +291 -0
- package/src/NativeOauthEssentials.ts +91 -17
- package/src/errors.ts +49 -0
- package/src/index.tsx +587 -80
- package/src/oauth.ts +140 -0
- package/src/platform.ts +121 -0
- package/src/types.ts +200 -1
package/src/index.tsx
CHANGED
|
@@ -1,89 +1,264 @@
|
|
|
1
1
|
import OauthEssentials from './NativeOauthEssentials';
|
|
2
|
-
import { Platform } from 'react-native';
|
|
2
|
+
import { type EventSubscription, Platform } from 'react-native';
|
|
3
3
|
import {
|
|
4
4
|
type AppleIdCredentialResult,
|
|
5
|
+
AppleIdCredentialState,
|
|
6
|
+
type AppleSignInOptions,
|
|
5
7
|
type CancelledCredentialResult,
|
|
8
|
+
type CredentialFailureEvent,
|
|
9
|
+
type CredentialSuccessEvent,
|
|
10
|
+
CredentialError,
|
|
6
11
|
CredentialsType,
|
|
12
|
+
type GoogleClientId,
|
|
7
13
|
type GoogleIdCredentialResult,
|
|
8
14
|
type GoogleSignInOptions,
|
|
15
|
+
type HybridSignInOptions,
|
|
16
|
+
type OAuthCredentialResult,
|
|
17
|
+
type OAuthSignInOptions,
|
|
9
18
|
type PassKeyCredentialResult,
|
|
10
19
|
type PasskeyOptions,
|
|
11
20
|
type PasswordCredentialResult,
|
|
12
21
|
type WebAppleIdCredentialResult,
|
|
13
22
|
} from './types';
|
|
23
|
+
import {
|
|
24
|
+
buildAuthorizationUrl,
|
|
25
|
+
buildTokenRequestBody,
|
|
26
|
+
getUrlScheme,
|
|
27
|
+
parseRedirectParams,
|
|
28
|
+
type PkcePair,
|
|
29
|
+
} from './oauth';
|
|
30
|
+
import { createCredentialError } from './errors';
|
|
31
|
+
import {
|
|
32
|
+
resolveGoogleClientId,
|
|
33
|
+
splitGoogleArgs,
|
|
34
|
+
warnIgnored,
|
|
35
|
+
warnOnce,
|
|
36
|
+
} from './platform';
|
|
14
37
|
|
|
15
38
|
export * from './types';
|
|
39
|
+
export { isCredentialError } from './errors';
|
|
16
40
|
const APPLE_ID_CACHED_RESULTS = new Map<
|
|
17
41
|
string,
|
|
18
42
|
AppleIdCredentialResult['data']
|
|
19
43
|
>();
|
|
20
44
|
|
|
21
45
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
46
|
+
* Shows the Google sign in UI and resolves with the signed in account.
|
|
47
|
+
*
|
|
48
|
+
* iOS: configures GoogleSignIn with the iOS client id (+ `serverClientId`, `hostedDomain`) and
|
|
49
|
+
* presents it from the top most view controller, passing `hint`, `scopes` and `nonce`. If the app is
|
|
50
|
+
* not active it waits up to 2 seconds for it to become active. The client id falls back to
|
|
51
|
+
* `GIDClientID` and `serverClientId` to `webClientId`, then to `GIDServerClientID` (Info.plist).
|
|
52
|
+
* Android: shows the Credential Manager bottom sheet (`mode: 'bottomSheet'`, falls back to the
|
|
53
|
+
* "Sign in with Google" flow when there is no credential and `authorizedAccounts` is not set) or
|
|
54
|
+
* the "Sign in with Google" flow (`mode: 'button'`). When `scopes` or `offlineAccess` are set it
|
|
55
|
+
* then asks for authorization, which may show a consent screen.
|
|
25
56
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
57
|
+
* Resolves `GOOGLE_ID`, or `CANCELLED` when the user dismisses the UI (or declines the consent
|
|
58
|
+
* screen on Android).
|
|
59
|
+
* Rejects with `CONFIGURATION_ERROR` (no client id; iOS: the reversed client id URL scheme is not in
|
|
60
|
+
* Info.plist, the message has the exact scheme), `NOT_SUPPORTED_ERROR` (Android, no Play Services),
|
|
61
|
+
* `NO_ACTIVITY_ERROR`,
|
|
62
|
+
* `NO_SIGN_IN_AVAILABLE` (Android), `BROWSER_UNAVAILABLE` (iOS: app not active, its window not in
|
|
63
|
+
* the foreground, Guided Access or Safari could not be opened), `SIMULTANEOUS_CALL_ERROR` (iOS: a Google sign in is pending) or
|
|
64
|
+
* `INVALID_RESULT_ERROR`.
|
|
65
|
+
* Emits `onCredentialSuccess` / `onCredentialFailure`.
|
|
66
|
+
*
|
|
67
|
+
* @param clientId - `{ iosClientId, webClientId }` (see {@link GoogleClientIds}), or one client id
|
|
68
|
+
* string used as is (the iOS client id on iOS, the web client id on Android). Optional: without it
|
|
69
|
+
* the ids are read from the app config (`googleSignIn()` or `googleSignIn(options)`), see
|
|
70
|
+
* {@link GoogleClientIds}.
|
|
71
|
+
* @param options - See {@link GoogleSignInOptions}.
|
|
28
72
|
*/
|
|
29
73
|
export async function googleSignIn(
|
|
30
|
-
webClientId: string,
|
|
31
74
|
options?: GoogleSignInOptions
|
|
75
|
+
): Promise<GoogleIdCredentialResult | CancelledCredentialResult>;
|
|
76
|
+
export async function googleSignIn(
|
|
77
|
+
clientId: GoogleClientId | null | undefined,
|
|
78
|
+
options?: GoogleSignInOptions
|
|
79
|
+
): Promise<GoogleIdCredentialResult | CancelledCredentialResult>;
|
|
80
|
+
export async function googleSignIn(
|
|
81
|
+
clientIdOrOptions?: GoogleClientId | GoogleSignInOptions | null,
|
|
82
|
+
maybeOptions?: GoogleSignInOptions
|
|
32
83
|
): Promise<GoogleIdCredentialResult | CancelledCredentialResult> {
|
|
84
|
+
const { clientId, options } = splitGoogleArgs(
|
|
85
|
+
clientIdOrOptions,
|
|
86
|
+
maybeOptions
|
|
87
|
+
);
|
|
88
|
+
warnIgnored(
|
|
89
|
+
'googleSignIn',
|
|
90
|
+
Platform.OS,
|
|
91
|
+
options,
|
|
92
|
+
Platform.OS === 'ios' ? GOOGLE_IOS_IGNORED : ['hint']
|
|
93
|
+
);
|
|
94
|
+
const resolved = resolveGoogleClientId(Platform.OS, clientId, {
|
|
95
|
+
...options,
|
|
96
|
+
});
|
|
33
97
|
return (await OauthEssentials.googleSignIn(
|
|
34
|
-
|
|
35
|
-
parseGoogleIdOptions(options)
|
|
36
|
-
)) as GoogleIdCredentialResult;
|
|
98
|
+
resolved.clientId,
|
|
99
|
+
parseGoogleIdOptions(resolved.options)
|
|
100
|
+
)) as GoogleIdCredentialResult | CancelledCredentialResult;
|
|
37
101
|
}
|
|
38
102
|
|
|
103
|
+
const GOOGLE_IOS_IGNORED = [
|
|
104
|
+
'authorizedAccounts',
|
|
105
|
+
'autoSelectEnabled',
|
|
106
|
+
'mode',
|
|
107
|
+
'offlineAccess',
|
|
108
|
+
] as const;
|
|
109
|
+
|
|
39
110
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
111
|
+
* Restores a previous Google sign in.
|
|
112
|
+
*
|
|
113
|
+
* iOS: calls GoogleSignIn `restorePreviousSignIn`, which never shows UI and refreshes the tokens.
|
|
114
|
+
* `hint`, `scopes`, `nonce` and `offlineAccess` are ignored and `serverAuthCode` is always null.
|
|
115
|
+
* Android: shows the Credential Manager bottom sheet filtered to accounts that already signed in to
|
|
116
|
+
* the app with auto select on, so it signs in without UI when there is a single one. `mode` and
|
|
117
|
+
* `hint` are ignored. When `scopes`/`offlineAccess` are set, authorization is only requested without UI:
|
|
118
|
+
* if consent is needed the result has `accessToken: null`.
|
|
42
119
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
120
|
+
* Resolves `GOOGLE_ID`, or `CANCELLED` when the user dismisses the Android sheet.
|
|
121
|
+
* Rejects with `NO_SIGN_IN_AVAILABLE` when there is nothing to restore, plus the same errors as
|
|
122
|
+
* {@link googleSignIn} (it shares the iOS simultaneous call lock with it). Takes the same
|
|
123
|
+
* `clientId` as {@link googleSignIn} (also optional).
|
|
124
|
+
* Emits `onCredentialSuccess` / `onCredentialFailure`.
|
|
45
125
|
*/
|
|
46
|
-
export async function
|
|
47
|
-
|
|
48
|
-
|
|
126
|
+
export async function googleSilentSignIn(
|
|
127
|
+
options?: GoogleSignInOptions
|
|
128
|
+
): Promise<GoogleIdCredentialResult | CancelledCredentialResult>;
|
|
129
|
+
export async function googleSilentSignIn(
|
|
130
|
+
clientId: GoogleClientId | null | undefined,
|
|
131
|
+
options?: GoogleSignInOptions
|
|
132
|
+
): Promise<GoogleIdCredentialResult | CancelledCredentialResult>;
|
|
133
|
+
export async function googleSilentSignIn(
|
|
134
|
+
clientIdOrOptions?: GoogleClientId | GoogleSignInOptions | null,
|
|
135
|
+
maybeOptions?: GoogleSignInOptions
|
|
136
|
+
): Promise<GoogleIdCredentialResult | CancelledCredentialResult> {
|
|
137
|
+
const { clientId, options } = splitGoogleArgs(
|
|
138
|
+
clientIdOrOptions,
|
|
139
|
+
maybeOptions
|
|
140
|
+
);
|
|
141
|
+
warnIgnored(
|
|
142
|
+
'googleSilentSignIn',
|
|
143
|
+
Platform.OS,
|
|
144
|
+
options,
|
|
145
|
+
Platform.OS === 'ios'
|
|
146
|
+
? [...GOOGLE_IOS_IGNORED, 'hint', 'scopes', 'nonce']
|
|
147
|
+
: ['hint', 'mode']
|
|
148
|
+
);
|
|
149
|
+
const resolved = resolveGoogleClientId(Platform.OS, clientId, {
|
|
150
|
+
...options,
|
|
151
|
+
});
|
|
152
|
+
return (await OauthEssentials.googleSilentSignIn(
|
|
153
|
+
resolved.clientId,
|
|
154
|
+
parseGoogleIdOptions(resolved.options)
|
|
155
|
+
)) as GoogleIdCredentialResult | CancelledCredentialResult;
|
|
49
156
|
}
|
|
50
157
|
|
|
51
158
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* @
|
|
159
|
+
* Clears the stored sign in state.
|
|
160
|
+
*
|
|
161
|
+
* iOS: calls GoogleSignIn `signOut` (removes the Google session from the keychain, so
|
|
162
|
+
* {@link googleSilentSignIn} rejects afterwards). It does not affect Apple ID or passwords.
|
|
163
|
+
* Android: calls Credential Manager `clearCredentialState`, which turns off auto select for every
|
|
164
|
+
* provider until the user picks an account again.
|
|
56
165
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
166
|
+
* Resolves `true`. Rejects with `INVALID_RESULT_ERROR` (Android only) when clearing fails.
|
|
167
|
+
* Does not emit events.
|
|
168
|
+
*/
|
|
169
|
+
export async function signOut(): Promise<boolean> {
|
|
170
|
+
return await OauthEssentials.signOut();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Shows the saved passwords of the app and resolves with the one the user picks.
|
|
175
|
+
*
|
|
176
|
+
* iOS: `ASAuthorizationPasswordProvider` request (iOS 16+ only offers credentials already saved on
|
|
177
|
+
* the device). Resolves `PASSWORD`, or the `CANCELLED` result when the user dismisses it or there
|
|
178
|
+
* is no saved password. Rejects with `INVALID_RESULT_ERROR` on other errors and
|
|
179
|
+
* `SIMULTANEOUS_CALL_ERROR` while a previous call is pending.
|
|
180
|
+
* Android: Credential Manager `GetPasswordOption`. Resolves `PASSWORD`, or `CANCELLED` when the user
|
|
181
|
+
* dismisses it or there is no saved password. Rejects with `NOT_SUPPORTED_ERROR` without Play
|
|
182
|
+
* Services, `NO_ACTIVITY_ERROR` and `INVALID_RESULT_ERROR` on other errors.
|
|
183
|
+
*
|
|
184
|
+
* Emits `onCredentialSuccess` / `onCredentialFailure`.
|
|
185
|
+
*/
|
|
186
|
+
export async function getPassword(): Promise<
|
|
187
|
+
PasswordCredentialResult | CancelledCredentialResult
|
|
188
|
+
> {
|
|
189
|
+
return (await OauthEssentials.getPassword()) as
|
|
190
|
+
| PasswordCredentialResult
|
|
191
|
+
| CancelledCredentialResult;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Saves a username and password in the platform password manager. It does not authenticate the
|
|
196
|
+
* user against anything.
|
|
197
|
+
*
|
|
198
|
+
* iOS: does nothing and resolves `false` (iOS has no API to save a password without an associated
|
|
199
|
+
* domain; use the `textContentType` props of your inputs so iOS offers to save it).
|
|
200
|
+
* Android: Credential Manager `createCredential(CreatePasswordRequest)`, which shows the save
|
|
201
|
+
* prompt. Resolves `true` when saved, `false` on any error (including the user declining).
|
|
202
|
+
* Rejects with `NOT_SUPPORTED_ERROR` without Play Services and `NO_ACTIVITY_ERROR`.
|
|
203
|
+
*
|
|
204
|
+
* Does not emit events, except `onCredentialFailure` for the Android rejections.
|
|
59
205
|
*/
|
|
60
206
|
export async function passwordSignIn(
|
|
61
207
|
username: string,
|
|
62
208
|
password: string
|
|
63
209
|
): Promise<boolean> {
|
|
210
|
+
if (Platform.OS === 'ios') {
|
|
211
|
+
warnOnce(
|
|
212
|
+
'passwordSignIn',
|
|
213
|
+
'ios',
|
|
214
|
+
'does nothing on iOS: set textContentType on your inputs so iOS offers to save the password.'
|
|
215
|
+
);
|
|
216
|
+
}
|
|
64
217
|
return await OauthEssentials.passwordSignIn(username, password);
|
|
65
218
|
}
|
|
66
219
|
|
|
67
220
|
/**
|
|
68
|
-
*
|
|
69
|
-
* On IOS devices it handles it smoothly using the latest UI workflow.
|
|
70
|
-
* On Android it dispatches the androidWebUrl to device default browser for handling it.
|
|
71
|
-
* you need to implement on your backend the redirect logic and validation accordingly to the link below
|
|
72
|
-
* @see [Apple Sign In REST API Documentation](https://developer.apple.com/documentation/signinwithapplerestapi)
|
|
221
|
+
* Signs in with Apple.
|
|
73
222
|
*
|
|
74
|
-
*
|
|
223
|
+
* iOS: native Sign in with Apple sheet requesting `fullName` and `email`, with `options.nonce` and
|
|
224
|
+
* `options.state`. Resolves `APPLE_ID`, or `CANCELLED` when the user dismisses it. Rejects with
|
|
225
|
+
* `INVALID_RESULT_ERROR` on other errors and `SIMULTANEOUS_CALL_ERROR` while a previous call is
|
|
226
|
+
* pending. Apple only sends `email`/`fullName` on the first authorization: this function keeps the
|
|
227
|
+
* first result of each `user` in memory (until the app is killed) and fills empty `email`/`fullName`
|
|
228
|
+
* with it.
|
|
229
|
+
* Android: opens `androidWebUrl` in a Custom Tab; `options` is ignored (put the nonce in your url).
|
|
230
|
+
* Your backend must run the Apple web flow and redirect to `<applicationId>://oauth/callback`.
|
|
231
|
+
* Resolves `WEB_APPLE_ID` with the redirect url, or `CANCELLED` when the user closes the tab or
|
|
232
|
+
* another browser session (`appleSignIn`/`oauthSignIn`) starts. Rejects with `INVALID_RESULT_ERROR`
|
|
233
|
+
* (invalid url), `NO_ACTIVITY_ERROR` or `BROWSER_UNAVAILABLE` (no browser installed).
|
|
75
234
|
*
|
|
76
|
-
*
|
|
235
|
+
* Emits `onCredentialSuccess` / `onCredentialFailure`.
|
|
236
|
+
* @see [Sign in with Apple REST API](https://developer.apple.com/documentation/signinwithapplerestapi)
|
|
237
|
+
*
|
|
238
|
+
* @param androidWebUrl - Web url that starts the Apple flow. Required on Android, ignored on iOS.
|
|
239
|
+
* @param options - See {@link AppleSignInOptions}. iOS only.
|
|
77
240
|
*/
|
|
78
241
|
export async function appleSignIn(
|
|
79
|
-
androidWebUrl?: string
|
|
80
|
-
|
|
242
|
+
androidWebUrl?: string,
|
|
243
|
+
options?: AppleSignInOptions
|
|
244
|
+
): Promise<
|
|
245
|
+
| AppleIdCredentialResult
|
|
246
|
+
| WebAppleIdCredentialResult
|
|
247
|
+
| CancelledCredentialResult
|
|
248
|
+
> {
|
|
249
|
+
if (Platform.OS !== 'ios') {
|
|
250
|
+
warnIgnored('appleSignIn', Platform.OS, options, ['nonce', 'state']);
|
|
251
|
+
}
|
|
81
252
|
const result = (await OauthEssentials.appleSignIn(
|
|
82
253
|
Platform.select({
|
|
83
|
-
ios:
|
|
84
|
-
|
|
85
|
-
})
|
|
86
|
-
|
|
254
|
+
ios: null,
|
|
255
|
+
default: androidWebUrl ?? null,
|
|
256
|
+
}),
|
|
257
|
+
options ?? {}
|
|
258
|
+
)) as
|
|
259
|
+
| AppleIdCredentialResult
|
|
260
|
+
| WebAppleIdCredentialResult
|
|
261
|
+
| CancelledCredentialResult;
|
|
87
262
|
if (result.type === CredentialsType.APPLE_ID) {
|
|
88
263
|
if (APPLE_ID_CACHED_RESULTS.has(result.data.user)) {
|
|
89
264
|
// Reuse the fullName and email provided
|
|
@@ -101,63 +276,387 @@ export async function appleSignIn(
|
|
|
101
276
|
} else {
|
|
102
277
|
APPLE_ID_CACHED_RESULTS.set(result.data.user, result.data);
|
|
103
278
|
}
|
|
104
|
-
return result as AppleIdCredentialResult;
|
|
105
|
-
} else {
|
|
106
|
-
return result as WebAppleIdCredentialResult;
|
|
107
279
|
}
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Asks Apple whether the user is still signed in to the app with Apple ID, without UI
|
|
285
|
+
* (`ASAuthorizationAppleIDProvider.getCredentialState`).
|
|
286
|
+
* Call it on app start and sign the user out when it is not `AUTHORIZED`.
|
|
287
|
+
*
|
|
288
|
+
* Resolves `AUTHORIZED`, `REVOKED`, `TRANSFERRED` or `NOT_FOUND`. Rejects with
|
|
289
|
+
* `INVALID_RESULT_ERROR` when the check fails, and always with `NOT_SUPPORTED_ERROR` on Android.
|
|
290
|
+
* Does not emit events.
|
|
291
|
+
*
|
|
292
|
+
* @param userId - The `user` field of the AppleIdCredentialResult.
|
|
293
|
+
*/
|
|
294
|
+
export async function getAppleIdCredentialState(
|
|
295
|
+
userId: string
|
|
296
|
+
): Promise<AppleIdCredentialState> {
|
|
297
|
+
return (await OauthEssentials.getAppleIdCredentialState(
|
|
298
|
+
userId
|
|
299
|
+
)) as AppleIdCredentialState;
|
|
108
300
|
}
|
|
109
301
|
|
|
110
302
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
303
|
+
* Shows a single picker with the credentials the user already has, meant for app start.
|
|
304
|
+
*
|
|
305
|
+
* iOS: Apple ID + saved passwords (`clientId` is ignored, Google is not included; only
|
|
306
|
+
* `options.nonce` is used, for Apple ID). On iOS 16+ only credentials already on the device are
|
|
307
|
+
* offered. Resolves `APPLE_ID` or `PASSWORD`, or `CANCELLED` when the user dismisses it or nothing
|
|
308
|
+
* is available. Rejects with `INVALID_RESULT_ERROR` or `SIMULTANEOUS_CALL_ERROR`.
|
|
309
|
+
* Android: Credential Manager with Google ID (bottom sheet option with the web client id,
|
|
310
|
+
* `authorizedAccounts`, `autoSelectEnabled` and `nonce`) + saved passwords. Apple is not included
|
|
311
|
+
* and `mode`, `hint`, `scopes`, `offlineAccess`, `hostedDomain` and `serverClientId` are ignored.
|
|
312
|
+
* Resolves `GOOGLE_ID` or `PASSWORD`, or `CANCELLED`. Rejects with `CONFIGURATION_ERROR` (no web
|
|
313
|
+
* client id), `NOT_SUPPORTED_ERROR` (no Play Services), `NO_ACTIVITY_ERROR`,
|
|
314
|
+
* `NO_SIGN_IN_AVAILABLE` or `INVALID_RESULT_ERROR`.
|
|
315
|
+
*
|
|
316
|
+
* Ignored options produce a warning in development.
|
|
317
|
+
* `clientId` is optional like in {@link googleSignIn}: without it the web client id is read from
|
|
318
|
+
* the app config.
|
|
319
|
+
*
|
|
320
|
+
* Emits `onCredentialSuccess` / `onCredentialFailure`.
|
|
113
321
|
*/
|
|
114
322
|
export async function hybridSignIn(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
323
|
+
options?: HybridSignInOptions
|
|
324
|
+
): Promise<HybridSignInResult>;
|
|
325
|
+
export async function hybridSignIn(
|
|
326
|
+
clientId: GoogleClientId | null | undefined,
|
|
327
|
+
options?: HybridSignInOptions
|
|
328
|
+
): Promise<HybridSignInResult>;
|
|
329
|
+
export async function hybridSignIn(
|
|
330
|
+
clientIdOrOptions?: GoogleClientId | HybridSignInOptions | null,
|
|
331
|
+
maybeOptions?: HybridSignInOptions
|
|
332
|
+
): Promise<HybridSignInResult> {
|
|
333
|
+
const { clientId, options } = splitGoogleArgs(
|
|
334
|
+
clientIdOrOptions,
|
|
335
|
+
maybeOptions
|
|
336
|
+
);
|
|
337
|
+
if (Platform.OS === 'ios') {
|
|
338
|
+
if (clientId) {
|
|
339
|
+
warnOnce(
|
|
340
|
+
'hybridSignIn',
|
|
341
|
+
'clientId',
|
|
342
|
+
'the Google client id is ignored on iOS (Google is not offered there, only Apple ID and passwords).'
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
warnIgnored('hybridSignIn', 'ios', options, [
|
|
346
|
+
...GOOGLE_IOS_IGNORED,
|
|
347
|
+
'hint',
|
|
348
|
+
'scopes',
|
|
349
|
+
'hostedDomain',
|
|
350
|
+
'serverClientId',
|
|
351
|
+
]);
|
|
352
|
+
} else {
|
|
353
|
+
warnIgnored('hybridSignIn', Platform.OS, options, [
|
|
354
|
+
'hint',
|
|
355
|
+
'mode',
|
|
356
|
+
'scopes',
|
|
357
|
+
'offlineAccess',
|
|
358
|
+
'hostedDomain',
|
|
359
|
+
'serverClientId',
|
|
360
|
+
]);
|
|
361
|
+
}
|
|
362
|
+
const resolved = resolveGoogleClientId(Platform.OS, clientId, {
|
|
363
|
+
...options,
|
|
364
|
+
});
|
|
365
|
+
return (await OauthEssentials.hybridSignIn(
|
|
366
|
+
resolved.clientId,
|
|
367
|
+
parseGoogleIdOptions(resolved.options)
|
|
368
|
+
)) as HybridSignInResult;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** Result of {@link hybridSignIn}: Apple ID only on iOS, Google ID only on Android. */
|
|
372
|
+
export type HybridSignInResult =
|
|
118
373
|
| AppleIdCredentialResult
|
|
119
374
|
| GoogleIdCredentialResult
|
|
120
375
|
| PasswordCredentialResult
|
|
121
|
-
| CancelledCredentialResult
|
|
122
|
-
> {
|
|
123
|
-
return (await OauthEssentials.hybridSignIn(
|
|
124
|
-
googleClientId,
|
|
125
|
-
parseGoogleIdOptions(options)
|
|
126
|
-
)) as
|
|
127
|
-
| AppleIdCredentialResult
|
|
128
|
-
| GoogleIdCredentialResult
|
|
129
|
-
| PasswordCredentialResult;
|
|
130
|
-
}
|
|
376
|
+
| CancelledCredentialResult;
|
|
131
377
|
|
|
132
378
|
/**
|
|
133
|
-
* Creates a passkey.
|
|
134
|
-
*
|
|
379
|
+
* Creates a passkey from a WebAuthn `PublicKeyCredentialCreationOptions` JSON (`requestJson`).
|
|
380
|
+
*
|
|
381
|
+
* iOS 16+: platform passkey registration. Reads `challenge`, `rp.id`, `user.id`, `user.name`,
|
|
382
|
+
* `authenticatorSelection.userVerification`, `attestation` and, on iOS 17.4+,
|
|
383
|
+
* `excludeCredentials`; other fields are ignored. `data` is a JSON string with `id`, `rawId`,
|
|
384
|
+
* `type` and `response.clientDataJSON`/`attestationObject` (base64url). Rejects with
|
|
385
|
+
* `NOT_SUPPORTED_ERROR` below iOS 16 and `SIMULTANEOUS_CALL_ERROR` while a passkey call is pending.
|
|
386
|
+
* Android 9+: Credential Manager `CreatePublicKeyCredentialRequest` with the whole JSON and
|
|
387
|
+
* `preferImmediatelyAvailableCredentials`. `data` is the registration response JSON returned by
|
|
388
|
+
* Credential Manager. Rejects with `NOT_SUPPORTED_ERROR` below Android 9 and `NO_ACTIVITY_ERROR`.
|
|
389
|
+
*
|
|
390
|
+
* Resolves `PASSKEY`, or `CANCELLED` when the user dismisses it. Rejects with
|
|
391
|
+
* `INVALID_RESULT_ERROR` for an invalid/missing `requestJson` and other errors.
|
|
392
|
+
* Emits `onCredentialSuccess` / `onCredentialFailure`. Send `data` to your server to verify it.
|
|
135
393
|
*/
|
|
136
394
|
export async function createPassKey(
|
|
137
395
|
options: PasskeyOptions
|
|
138
|
-
): Promise<PassKeyCredentialResult> {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
396
|
+
): Promise<PassKeyCredentialResult | CancelledCredentialResult> {
|
|
397
|
+
warnPasskeyOptions('createPassKey', options);
|
|
398
|
+
return (await OauthEssentials.createPassKey(options)) as
|
|
399
|
+
| PassKeyCredentialResult
|
|
400
|
+
| CancelledCredentialResult;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Signs in with a passkey from a WebAuthn `PublicKeyCredentialRequestOptions` JSON (`requestJson`).
|
|
405
|
+
*
|
|
406
|
+
* iOS 16+: platform passkey assertion. Reads `challenge`, `rpId`, `allowCredentials` and
|
|
407
|
+
* `userVerification`. With `mediation: 'autofill'` it does not show a sheet: it waits until the user
|
|
408
|
+
* picks a passkey in the QuickType bar of a `textContentType="username"` field, and any other
|
|
409
|
+
* passkey call (or {@link cancelPassKeyAutoFill}) resolves it as `CANCELLED`. Otherwise it shows a
|
|
410
|
+
* sheet that only offers passkeys available on the device (no "other device" QR option), so it
|
|
411
|
+
* resolves `CANCELLED` when there is none. `data` is a JSON string with `id`, `rawId`, `type` and
|
|
412
|
+
* `response.clientDataJSON`/`authenticatorData`/`signature`/`userHandle` (base64url).
|
|
413
|
+
* Rejects with `NOT_SUPPORTED_ERROR` below iOS 16 and `SIMULTANEOUS_CALL_ERROR` while a modal
|
|
414
|
+
* passkey call is pending.
|
|
415
|
+
* Android 9+: Credential Manager `GetPublicKeyCredentialOption` with the whole JSON and
|
|
416
|
+
* `preferImmediatelyAvailableCredentials`; `mediation` is ignored. `data` is the authentication
|
|
417
|
+
* response JSON returned by Credential Manager. Rejects with `NOT_SUPPORTED_ERROR` below Android 9,
|
|
418
|
+
* `NO_ACTIVITY_ERROR` and `NO_SIGN_IN_AVAILABLE` (no passkey).
|
|
419
|
+
*
|
|
420
|
+
* Resolves `PASSKEY`, or `CANCELLED` when the user dismisses it. Rejects with
|
|
421
|
+
* `INVALID_RESULT_ERROR` for an invalid/missing `requestJson` and other errors.
|
|
422
|
+
* Emits `onCredentialSuccess` / `onCredentialFailure`. Send `data` to your server to verify it.
|
|
423
|
+
*/
|
|
424
|
+
export async function getPassKey(
|
|
425
|
+
options: PasskeyOptions
|
|
426
|
+
): Promise<PassKeyCredentialResult | CancelledCredentialResult> {
|
|
427
|
+
warnPasskeyOptions('getPassKey', options);
|
|
428
|
+
return (await OauthEssentials.getPassKey(options)) as
|
|
429
|
+
| PassKeyCredentialResult
|
|
430
|
+
| CancelledCredentialResult;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function warnPasskeyOptions(method: string, options?: PasskeyOptions) {
|
|
434
|
+
if (Platform.OS === 'ios') {
|
|
435
|
+
if (options?.preferImmediatelyAvailableCredentials === false) {
|
|
436
|
+
warnOnce(
|
|
437
|
+
method,
|
|
438
|
+
'preferImmediatelyAvailableCredentials',
|
|
439
|
+
'"preferImmediatelyAvailableCredentials: false" is ignored on iOS (modal requests only offer passkeys on the device).'
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
} else if (options?.mediation === 'autofill') {
|
|
443
|
+
warnOnce(
|
|
444
|
+
method,
|
|
445
|
+
'mediation',
|
|
446
|
+
'"mediation: \'autofill\'" is ignored on Android.'
|
|
447
|
+
);
|
|
448
|
+
}
|
|
142
449
|
}
|
|
143
450
|
|
|
144
|
-
|
|
451
|
+
/**
|
|
452
|
+
* Cancels a pending `getPassKey({ mediation: 'autofill' })` request, for example when the sign in
|
|
453
|
+
* screen unmounts. The cancelled `getPassKey` resolves `CANCELLED` (and emits it).
|
|
454
|
+
*
|
|
455
|
+
* Resolves `true` when a pending AutoFill request was cancelled, `false` when there was none.
|
|
456
|
+
* Always resolves `false` on Android and below iOS 16.
|
|
457
|
+
*/
|
|
458
|
+
export async function cancelPassKeyAutoFill(): Promise<boolean> {
|
|
459
|
+
return await OauthEssentials.cancelPassKeyAutoFill();
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Signs in with any OAuth 2.0 / OpenID Connect provider using the authorization code flow.
|
|
464
|
+
*
|
|
465
|
+
* 1. Generates a random `state` and a PKCE S256 verifier/challenge on the native side.
|
|
466
|
+
* 2. Builds `authorizationEndpoint?response_type=code&client_id&redirect_uri&state[&scope]
|
|
467
|
+
* [&code_challenge&code_challenge_method=S256]` + `additionalParameters` (which may override
|
|
468
|
+
* `scope`; the other parameters above are reserved and ignored there, with a dev warning).
|
|
469
|
+
* 3. Opens it in `ASWebAuthenticationSession` (iOS, `prefersEphemeralSession`, waits up to 2 seconds
|
|
470
|
+
* for the app to be active) or a Custom Tab (Android) and waits for the redirect to `redirectUri`.
|
|
471
|
+
* On Android the redirect must reach `CustomTabsActivity`: use `OAUTH_REDIRECT_URI` or add an
|
|
472
|
+
* intent filter for your uri.
|
|
473
|
+
* 4. Checks that the returned `state` matches, then reads `code` (query or fragment).
|
|
474
|
+
* 5. When `tokenEndpoint` is set, POSTs `grant_type=authorization_code`, `code`, `client_id`,
|
|
475
|
+
* `redirect_uri` and `code_verifier` as a form and returns the JSON response in `tokens`
|
|
476
|
+
* (no client secret is sent, the id_token is not validated).
|
|
477
|
+
*
|
|
478
|
+
* Resolves `OAUTH`, or `CANCELLED` when the user closes the browser, the provider returns
|
|
479
|
+
* `error=access_denied` or (Android) another browser session starts.
|
|
480
|
+
* Rejects with `INVALID_RESULT_ERROR` (state mismatch, provider error, no code, token endpoint
|
|
481
|
+
* returned non 2xx or non JSON), `NETWORK_ERROR` (token request failed), `CONFIGURATION_ERROR`
|
|
482
|
+
* (invalid `redirectUri`), `BROWSER_UNAVAILABLE` (iOS: Guided Access, app not active or its window
|
|
483
|
+
* not in the foreground; Android: no browser), `NO_ACTIVITY_ERROR` or `SIMULTANEOUS_CALL_ERROR` (iOS).
|
|
484
|
+
* Does not emit events.
|
|
485
|
+
*/
|
|
486
|
+
export async function oauthSignIn(
|
|
487
|
+
options: OAuthSignInOptions
|
|
488
|
+
): Promise<OAuthCredentialResult | CancelledCredentialResult> {
|
|
489
|
+
if (Platform.OS !== 'ios') {
|
|
490
|
+
warnIgnored('oauthSignIn', Platform.OS, options, [
|
|
491
|
+
'prefersEphemeralSession',
|
|
492
|
+
]);
|
|
493
|
+
}
|
|
494
|
+
const redirectUri = options.redirectUri ?? OAUTH_REDIRECT_URI;
|
|
495
|
+
const usePKCE = options.usePKCE !== false;
|
|
496
|
+
const pkce = (await OauthEssentials.generatePkce()) as PkcePair;
|
|
497
|
+
|
|
498
|
+
const url = buildAuthorizationUrl({
|
|
499
|
+
authorizationEndpoint: options.authorizationEndpoint,
|
|
500
|
+
clientId: options.clientId,
|
|
501
|
+
redirectUri,
|
|
502
|
+
scopes: options.scopes,
|
|
503
|
+
additionalParameters: options.additionalParameters,
|
|
504
|
+
state: pkce.state,
|
|
505
|
+
codeChallenge: usePKCE ? pkce.codeChallenge : undefined,
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
const session = (await OauthEssentials.openAuthSession(url, {
|
|
509
|
+
callbackScheme: getUrlScheme(redirectUri),
|
|
510
|
+
prefersEphemeralSession: options.prefersEphemeralSession === true,
|
|
511
|
+
})) as { type: string; data: { url: string } | null };
|
|
512
|
+
|
|
513
|
+
if (session.type === CredentialsType.CANCELLED || !session.data) {
|
|
514
|
+
return { type: CredentialsType.CANCELLED, data: null };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const params = parseRedirectParams(session.data.url);
|
|
518
|
+
if (params.state !== pkce.state) {
|
|
519
|
+
throw oauthError('State mismatch in authorization response');
|
|
520
|
+
}
|
|
521
|
+
if (params.error) {
|
|
522
|
+
if (params.error === 'access_denied') {
|
|
523
|
+
return { type: CredentialsType.CANCELLED, data: null };
|
|
524
|
+
}
|
|
525
|
+
throw oauthError(params.error_description ?? params.error);
|
|
526
|
+
}
|
|
527
|
+
if (!params.code) {
|
|
528
|
+
throw oauthError('Authorization response has no code');
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const codeVerifier = usePKCE ? pkce.codeVerifier : null;
|
|
532
|
+
let tokens: Record<string, unknown> | null = null;
|
|
533
|
+
if (options.tokenEndpoint) {
|
|
534
|
+
let response: Response;
|
|
535
|
+
try {
|
|
536
|
+
response = await fetch(options.tokenEndpoint, {
|
|
537
|
+
method: 'POST',
|
|
538
|
+
headers: {
|
|
539
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
540
|
+
'Accept': 'application/json',
|
|
541
|
+
},
|
|
542
|
+
body: buildTokenRequestBody({
|
|
543
|
+
code: params.code,
|
|
544
|
+
clientId: options.clientId,
|
|
545
|
+
redirectUri,
|
|
546
|
+
codeVerifier,
|
|
547
|
+
}),
|
|
548
|
+
});
|
|
549
|
+
} catch (e) {
|
|
550
|
+
throw createCredentialError(
|
|
551
|
+
CredentialError.NETWORK_ERROR,
|
|
552
|
+
`Token request failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
553
|
+
e
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
try {
|
|
557
|
+
tokens = (await response.json()) as Record<string, unknown>;
|
|
558
|
+
} catch (e) {
|
|
559
|
+
throw createCredentialError(
|
|
560
|
+
CredentialError.INVALID_RESULT_ERROR,
|
|
561
|
+
`Token endpoint returned a non JSON response (status ${response.status})`,
|
|
562
|
+
e
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
if (!response.ok) {
|
|
566
|
+
throw oauthError(
|
|
567
|
+
String(tokens.error_description ?? tokens.error ?? response.status)
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return {
|
|
573
|
+
type: CredentialsType.OAUTH,
|
|
574
|
+
data: {
|
|
575
|
+
code: params.code,
|
|
576
|
+
state: params.state,
|
|
577
|
+
redirectUri,
|
|
578
|
+
codeVerifier,
|
|
579
|
+
params,
|
|
580
|
+
tokens,
|
|
581
|
+
},
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function oauthError(message: string) {
|
|
586
|
+
return createCredentialError(CredentialError.INVALID_RESULT_ERROR, message);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Called with the same `{ type, data }` the promise resolves with (including `CANCELLED`), for
|
|
591
|
+
* `googleSignIn`, `googleSilentSignIn`, `getPassword`, `appleSignIn`, `hybridSignIn`,
|
|
592
|
+
* `createPassKey` and `getPassKey`. Not called by `signOut`, `passwordSignIn`,
|
|
593
|
+
* `getAppleIdCredentialState`, `cancelPassKeyAutoFill` and `oauthSignIn`.
|
|
594
|
+
*/
|
|
595
|
+
export function addCredentialSuccessListener(
|
|
596
|
+
listener: (event: CredentialSuccessEvent) => void
|
|
597
|
+
): EventSubscription {
|
|
598
|
+
return OauthEssentials.onCredentialSuccess(listener);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Called with `{ code, message }` when one of the methods listed in
|
|
603
|
+
* {@link addCredentialSuccessListener} rejects. `SIMULTANEOUS_CALL_ERROR`, the iOS
|
|
604
|
+
* `NOT_SUPPORTED_ERROR` below iOS 16 and the iOS missing `requestJson` error are not emitted.
|
|
605
|
+
*/
|
|
606
|
+
export function addCredentialFailureListener(
|
|
607
|
+
listener: (event: CredentialFailureEvent) => void
|
|
608
|
+
): EventSubscription {
|
|
609
|
+
return OauthEssentials.onCredentialFailure((event) =>
|
|
610
|
+
listener(event as CredentialFailureEvent)
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Called when iOS posts `credentialRevokedNotification`, i.e. the user stopped using Apple ID with
|
|
616
|
+
* the app while it is running. Sign the user out when it fires. Never called on Android.
|
|
617
|
+
*/
|
|
618
|
+
export function addAppleIdCredentialRevokedListener(
|
|
619
|
+
listener: () => void
|
|
620
|
+
): EventSubscription {
|
|
621
|
+
return OauthEssentials.onAppleIdCredentialRevoked(() => listener());
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function parseGoogleIdOptions(options?: GoogleSignInOptions) {
|
|
145
625
|
const bridgeOptions: GoogleSignInOptions = {
|
|
146
626
|
authorizedAccounts: false,
|
|
147
627
|
autoSelectEnabled: false,
|
|
628
|
+
mode: 'bottomSheet',
|
|
148
629
|
};
|
|
149
630
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
631
|
+
if (options && typeof options === 'object') {
|
|
632
|
+
if (options.authorizedAccounts !== undefined) {
|
|
633
|
+
bridgeOptions.authorizedAccounts = options.authorizedAccounts === true;
|
|
634
|
+
}
|
|
635
|
+
if (options.autoSelectEnabled !== undefined) {
|
|
636
|
+
bridgeOptions.autoSelectEnabled = options.autoSelectEnabled === true;
|
|
637
|
+
}
|
|
638
|
+
if (options.mode === 'button') {
|
|
639
|
+
bridgeOptions.mode = 'button';
|
|
640
|
+
}
|
|
641
|
+
if (options.offlineAccess !== undefined) {
|
|
642
|
+
bridgeOptions.offlineAccess = options.offlineAccess === true;
|
|
643
|
+
}
|
|
644
|
+
if (typeof options.nonce === 'string') {
|
|
645
|
+
bridgeOptions.nonce = options.nonce;
|
|
646
|
+
}
|
|
647
|
+
if (typeof options.hint === 'string') {
|
|
648
|
+
bridgeOptions.hint = options.hint;
|
|
649
|
+
}
|
|
650
|
+
if (typeof options.hostedDomain === 'string') {
|
|
651
|
+
bridgeOptions.hostedDomain = options.hostedDomain;
|
|
652
|
+
}
|
|
653
|
+
if (typeof options.serverClientId === 'string') {
|
|
654
|
+
bridgeOptions.serverClientId = options.serverClientId;
|
|
655
|
+
}
|
|
656
|
+
if (Array.isArray(options.scopes)) {
|
|
657
|
+
bridgeOptions.scopes = options.scopes.filter(
|
|
658
|
+
(s) => typeof s === 'string'
|
|
659
|
+
);
|
|
161
660
|
}
|
|
162
661
|
}
|
|
163
662
|
return bridgeOptions;
|
|
@@ -166,34 +665,42 @@ function parseGoogleIdOptions(options?: any) {
|
|
|
166
665
|
const CONSTANTS = OauthEssentials.getConstants();
|
|
167
666
|
|
|
168
667
|
/**
|
|
169
|
-
* Google Play are
|
|
170
|
-
*
|
|
668
|
+
* Android: Google Play Services are installed and up to date (read once, when the module loads).
|
|
669
|
+
* iOS: always false.
|
|
171
670
|
*/
|
|
172
671
|
export const GOOGLE_PLAY_SERVICES_SUPPORTED =
|
|
173
672
|
CONSTANTS.GOOGLE_PLAY_SERVICES_SUPPORTED;
|
|
174
673
|
|
|
175
674
|
/**
|
|
176
|
-
*
|
|
675
|
+
* iOS: always true. Android: same as `GOOGLE_PLAY_SERVICES_SUPPORTED` (`getPassword` and
|
|
676
|
+
* `passwordSignIn` reject with `NOT_SUPPORTED_ERROR` without Play Services).
|
|
177
677
|
*/
|
|
178
678
|
export const PASSWORD_SUPPORTED = CONSTANTS.PASSWORD_SUPPORTED;
|
|
179
679
|
|
|
180
680
|
/**
|
|
181
|
-
*
|
|
182
|
-
* You should mainly rely on this constant to check if you can use google sign in.
|
|
681
|
+
* iOS: always true. Android: same as `GOOGLE_PLAY_SERVICES_SUPPORTED`.
|
|
183
682
|
*/
|
|
184
683
|
export const GOOGLE_ID_SUPPORTED = CONSTANTS.GOOGLE_ID_SUPPORTED;
|
|
185
684
|
|
|
186
685
|
/**
|
|
187
|
-
*
|
|
686
|
+
* Always true (native on iOS, web flow in a Custom Tab on Android).
|
|
188
687
|
*/
|
|
189
688
|
export const APPLE_ID_SUPPORTED = CONSTANTS.APPLE_ID_SUPPORTED;
|
|
190
689
|
|
|
191
690
|
/**
|
|
192
|
-
*
|
|
691
|
+
* iOS: always true. Android: same as `GOOGLE_PLAY_SERVICES_SUPPORTED` (`hybridSignIn` rejects with
|
|
692
|
+
* `NOT_SUPPORTED_ERROR` without Play Services).
|
|
193
693
|
*/
|
|
194
694
|
export const HYBRID_SUPPORTED = CONSTANTS.HYBRID_SUPPORTED;
|
|
195
695
|
|
|
196
696
|
/**
|
|
197
|
-
*
|
|
697
|
+
* iOS 16+ / Android 9+ (API 28).
|
|
198
698
|
*/
|
|
199
699
|
export const PASSKEYS_SUPPORTED = CONSTANTS.PASSKEYS_SUPPORTED;
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Default redirect uri of `oauthSignIn`: `<bundle id, lowercased>://oauth/callback` on iOS and
|
|
703
|
+
* `<applicationId>://oauth/callback` on Android (the uri the library's `CustomTabsActivity`
|
|
704
|
+
* intent filter handles).
|
|
705
|
+
*/
|
|
706
|
+
export const OAUTH_REDIRECT_URI = CONSTANTS.OAUTH_REDIRECT_URI;
|