react-native-oauth-essentials 1.0.11 → 1.1.1
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/README.md +384 -362
- 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 +448 -192
- 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 -3
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +8 -4
- 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 -2
package/README.md
CHANGED
|
@@ -1,181 +1,146 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
Sign-In, Apple Sign-In, and Password Sign-In**, delivering a **native sign-in experience** with a simple, promise-based
|
|
5
|
-
API. It's a must-have for every React Native app.
|
|
3
|
+
# 🔐 React Native OAuth Essentials
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
**Every sign-in your app needs, in one library.**<br/>
|
|
6
|
+
Google · Apple · Saved passwords · Passkeys · Any OAuth 2.0 / OpenID Connect provider
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
[](https://www.npmjs.com/package/react-native-oauth-essentials)
|
|
9
|
+
[](https://www.npmjs.com/package/react-native-oauth-essentials)
|
|
10
|
+
[](./LICENSE)
|
|
11
|
+

|
|
12
|
+

|
|
13
|
+

|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
yarn add react-native-oauth-essentials
|
|
17
|
-
```
|
|
15
|
+
[📖 Docs](https://lucaswitch.github.io/react-native-oauth-essentials/docs/intro) ·
|
|
16
|
+
[⚙️ Installation](https://lucaswitch.github.io/react-native-oauth-essentials/docs/installation) ·
|
|
17
|
+
[🤖 AI setup prompt](https://lucaswitch.github.io/react-native-oauth-essentials/docs/ai-setup) ·
|
|
18
|
+
[📜 Changelog](./CHANGELOG.md)
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
</div>
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
---
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
[Installation](https://lucaswitch.github.io/react-native-oauth-essentials/docs/installation)
|
|
24
|
+
## ✨ Why this library?
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
Most React Native apps end up stitching together several sign-in libraries, each with its own philosophy and API. This one
|
|
27
|
+
gives you **a single, promise-based API** with a **truly native experience** on both platforms:
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
- 🟢 **Google Sign-In**: GoogleSignIn SDK on iOS, Credential Manager on Android.
|
|
30
|
+
- 🍎 **Sign in with Apple**: native on iOS (required by the App Store when you offer Google), web flow on Android.
|
|
31
|
+
- 🔑 **Saved passwords & passkeys**: the system pickers, including iOS AutoFill.
|
|
32
|
+
- 🌐 **Any OAuth 2.0 / OpenID Connect provider**: GitHub, Microsoft, Auth0, Keycloak… with PKCE.
|
|
33
|
+
- 🧩 **Expo config plugin** and a **Jest mock** included.
|
|
34
|
+
- 🧾 **Fully typed**, with typed errors you can narrow with `isCredentialError`.
|
|
29
35
|
|
|
30
|
-
|
|
36
|
+
> [!NOTE]
|
|
37
|
+
> Requires the **New Architecture**. Not supported in Expo Go: use a [development build](#-expo).
|
|
31
38
|
|
|
32
|
-
|
|
33
|
-
- **Minimum:** iOS 13+
|
|
34
|
-
- Uses native iOS Google Sign-In SDK
|
|
35
|
-
- Presents native iOS authentication UI
|
|
36
|
-
- No Google Play Services dependency
|
|
37
|
-
- Requires Google OAuth 2.0 Web Client ID from Google Cloud Console
|
|
38
|
-
- Supports One Tap sign-in experience
|
|
39
|
-
- Available: `GOOGLE_ID_SUPPORTED` constant
|
|
39
|
+
## 📦 Installation
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
- Supports additional options: `authorizedAccounts` and `autoSelectEnabled`
|
|
47
|
-
- Presents native Android credential picker UI
|
|
48
|
-
- Requires Google OAuth 2.0 Web Client ID from Google Cloud Console
|
|
49
|
-
- Available: `GOOGLE_ID_SUPPORTED` constant
|
|
41
|
+
```bash
|
|
42
|
+
npm install react-native-oauth-essentials
|
|
43
|
+
# or
|
|
44
|
+
yarn add react-native-oauth-essentials
|
|
45
|
+
```
|
|
50
46
|
|
|
51
|
-
|
|
52
|
-
- Returns standardized `GoogleIdCredentialResult` with user profile data
|
|
53
|
-
- Includes ID token for backend verification
|
|
54
|
-
- User can cancel, returning `CancelledCredentialResult`
|
|
47
|
+
Then install the iOS pods and rebuild the app (reloading Metro is not enough, this library has native code):
|
|
55
48
|
|
|
56
|
-
|
|
49
|
+
```bash
|
|
50
|
+
cd ios && pod install
|
|
51
|
+
```
|
|
57
52
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
**iOS:**
|
|
61
|
-
- **Minimum:** iOS 13+
|
|
62
|
-
- Uses native `AuthenticationServices` framework
|
|
63
|
-
- Presents native Apple Sign-In UI sheet
|
|
64
|
-
- Full integration with iOS Keychain
|
|
65
|
-
- Returns `AppleIdCredentialResult` with:
|
|
66
|
-
- ID token and authorization code
|
|
67
|
-
- User identifier (stable across app reinstalls)
|
|
68
|
-
- Email and full name (only on first sign-in)
|
|
69
|
-
- Real user indicator (`likelyReal`)
|
|
70
|
-
- Caches email and fullName for subsequent sign-ins
|
|
71
|
-
- No additional configuration needed
|
|
72
|
-
- Available: `APPLE_ID_SUPPORTED` constant
|
|
73
|
-
|
|
74
|
-
**Android:**
|
|
75
|
-
- **Minimum:** Android 8.0+ (API 26+)
|
|
76
|
-
- Uses browser-based OAuth flow via Chrome Custom Tabs (no native Apple Sign-In on Android)
|
|
77
|
-
- Opens device's default browser with your provided `androidWebUrl`
|
|
78
|
-
- Returns `WebAppleIdCredentialResult` with redirect URL components
|
|
79
|
-
- **Requires backend implementation** to handle OAuth flow
|
|
80
|
-
- You must implement Apple Sign-In REST API on your server
|
|
81
|
-
- See [Apple Sign-In REST API Documentation](https://developer.apple.com/documentation/signinwithapplerestapi)
|
|
82
|
-
- Available: `APPLE_ID_SUPPORTED` constant
|
|
83
|
-
|
|
84
|
-
**Implementation Note:** iOS provides a complete native experience, while Android uses Chrome Custom Tabs to open your OAuth endpoint in the browser, then captures the redirect URL.
|
|
53
|
+
Using Expo? Jump to [Expo](#-expo). Using a coding agent? Paste the
|
|
54
|
+
[setup prompt for AI agents](https://lucaswitch.github.io/react-native-oauth-essentials/docs/ai-setup) and let it configure everything.
|
|
85
55
|
|
|
86
|
-
|
|
56
|
+
## 🚀 Quick start
|
|
87
57
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
**iOS:**
|
|
91
|
-
- **Minimum:** iOS 12+
|
|
92
|
-
- Uses iOS Keychain Services
|
|
93
|
-
- Integrates with iCloud Keychain for sync across devices
|
|
94
|
-
- Presents native password autofill UI
|
|
95
|
-
- Supports AutoFill in WebViews
|
|
96
|
-
- Available: `PASSWORD_SUPPORTED` constant
|
|
97
|
-
|
|
98
|
-
**Android:**
|
|
99
|
-
- **Minimum:** Android 7.0+ (API 24+)
|
|
100
|
-
- Uses Credential Manager API
|
|
101
|
-
- Integrates with Google Smart Lock for Passwords
|
|
102
|
-
- Can sync credentials across devices via Google account
|
|
103
|
-
- Presents native credential picker UI
|
|
104
|
-
- Supports autofill in WebViews and native forms
|
|
105
|
-
- Available: `PASSWORD_SUPPORTED` constant
|
|
106
|
-
|
|
107
|
-
**Common Features:**
|
|
108
|
-
- `passwordSignIn()` - Saves credentials securely
|
|
109
|
-
- `getPassword()` - Retrieves stored credentials
|
|
110
|
-
- Returns `PasswordCredentialResult` with username and password
|
|
111
|
-
- Can be used alongside biometric authentication
|
|
112
|
-
- Returns `false` if no credentials stored
|
|
58
|
+
Sign in with Google in a few lines:
|
|
113
59
|
|
|
114
|
-
|
|
60
|
+
```tsx
|
|
61
|
+
import { Button } from 'react-native';
|
|
62
|
+
import {
|
|
63
|
+
googleSignIn,
|
|
64
|
+
CredentialsType,
|
|
65
|
+
isCredentialError,
|
|
66
|
+
CredentialError,
|
|
67
|
+
} from 'react-native-oauth-essentials';
|
|
115
68
|
|
|
116
|
-
|
|
69
|
+
export function GoogleButton() {
|
|
70
|
+
const onPress = async () => {
|
|
71
|
+
try {
|
|
72
|
+
const result = await googleSignIn();
|
|
73
|
+
|
|
74
|
+
if (result.type === CredentialsType.CANCELLED) return; // the user closed the UI
|
|
75
|
+
|
|
76
|
+
if (result.type === CredentialsType.GOOGLE_ID) {
|
|
77
|
+
// Send the idToken to YOUR backend and verify it there.
|
|
78
|
+
await api.signInWithGoogle(result.data.idToken);
|
|
79
|
+
}
|
|
80
|
+
} catch (e) {
|
|
81
|
+
if (isCredentialError(e, CredentialError.CONFIGURATION_ERROR)) console.error(e.message);
|
|
82
|
+
else throw e;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
117
85
|
|
|
118
|
-
|
|
86
|
+
return <Button title="Continue with Google" onPress={onPress} />;
|
|
87
|
+
}
|
|
88
|
+
```
|
|
119
89
|
|
|
120
|
-
**
|
|
121
|
-
|
|
122
|
-
- Presents unified credential picker if multiple options available
|
|
123
|
-
- Returns first available credential
|
|
124
|
-
- Ideal for app launch to restore previous session
|
|
125
|
-
- Available: `HYBRID_SUPPORTED` constant
|
|
90
|
+
That's the whole pattern: **call a function, check `result.type`, send the token to your backend.** Every other method
|
|
91
|
+
works the same way.
|
|
126
92
|
|
|
127
|
-
|
|
128
|
-
- Checks for: Google ID and saved passwords (Apple requires web flow)
|
|
129
|
-
- Uses Credential Manager to aggregate all options
|
|
130
|
-
- Presents native Android credential picker
|
|
131
|
-
- Returns first selected credential
|
|
132
|
-
- Available: `HYBRID_SUPPORTED` constant
|
|
93
|
+
### Three things to know
|
|
133
94
|
|
|
134
|
-
**
|
|
95
|
+
1. **Cancelling is not an error.** If the user dismisses the UI, the promise **resolves** with
|
|
96
|
+
`{ type: CredentialsType.CANCELLED, data: null }`.
|
|
97
|
+
2. **Real errors reject** with an `error.code` from `CredentialError`. Narrow them with `isCredentialError`.
|
|
98
|
+
3. **Setup mistakes tell you what to fix.** A missing client id or URL scheme rejects with `CONFIGURATION_ERROR` and a
|
|
99
|
+
message that has the exact value to add.
|
|
135
100
|
|
|
136
|
-
|
|
137
|
-
- `GoogleIdCredentialResult`
|
|
138
|
-
- `AppleIdCredentialResult` (iOS only)
|
|
139
|
-
- `PasswordCredentialResult`
|
|
140
|
-
- `CancelledCredentialResult`
|
|
101
|
+
## 🧭 What's supported
|
|
141
102
|
|
|
142
|
-
|
|
103
|
+
| Feature | iOS | Android |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| Google | GoogleSignIn SDK | Credential Manager (bottom sheet / "Sign in with Google" button flow) + Authorization API |
|
|
106
|
+
| Apple | Native `AuthenticationServices` | Your web flow in a Custom Tab (needs a backend) |
|
|
107
|
+
| Saved passwords | `ASAuthorizationPasswordProvider` | Credential Manager |
|
|
108
|
+
| Passkeys | iOS 16+, including AutoFill | Android 9+ |
|
|
109
|
+
| Hybrid (one picker at app start) | Apple ID + passwords | Google + passwords |
|
|
110
|
+
| Any OAuth 2.0 / OpenID Connect provider | `ASWebAuthenticationSession` + PKCE | Custom Tab + PKCE |
|
|
143
111
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
**iOS:**
|
|
147
|
-
- Uses `AuthenticationServices` framework (iOS 16+)
|
|
148
|
-
- Supports WebAuthn/FIDO2 standard
|
|
149
|
-
- Syncs via iCloud Keychain
|
|
150
|
-
- Presents native passkey creation/selection UI
|
|
151
|
-
- Available: `PASSKEYS_SUPPORTED` constant (iOS 16+)
|
|
152
|
-
|
|
153
|
-
**Android:**
|
|
154
|
-
- Uses Credential Manager API with passkey support (Android 9+ / API 28+)
|
|
155
|
-
- Supports WebAuthn/FIDO2 standard
|
|
156
|
-
- Syncs via Google Password Manager
|
|
157
|
-
- Additional options: `preferImmediatelyAvailableCredentials`, `isConditional`
|
|
158
|
-
- Available: `PASSKEYS_SUPPORTED` constant
|
|
159
|
-
|
|
160
|
-
**Common Features:**
|
|
161
|
-
- Standards-based implementation (WebAuthn)
|
|
162
|
-
- Requires `requestJson` string with `PublicKeyCredentialCreationOptions`
|
|
163
|
-
- Returns `PassKeyCredentialResult` with platform public key credential
|
|
164
|
-
- More secure than passwords (phishing-resistant)
|
|
165
|
-
- Biometric authentication integration
|
|
112
|
+
Android features based on Credential Manager need Google Play Services. Exact per-platform behavior of every function:
|
|
113
|
+
[Methods reference](https://lucaswitch.github.io/react-native-oauth-essentials/docs/methods).
|
|
166
114
|
|
|
167
|
-
|
|
115
|
+
## 📚 API reference
|
|
168
116
|
|
|
169
|
-
|
|
117
|
+
<details>
|
|
118
|
+
<summary><b>Import everything</b></summary>
|
|
170
119
|
|
|
171
120
|
```typescript
|
|
172
121
|
import {
|
|
122
|
+
// Google
|
|
173
123
|
googleSignIn,
|
|
124
|
+
googleSilentSignIn,
|
|
125
|
+
signOut,
|
|
126
|
+
// Apple
|
|
174
127
|
appleSignIn,
|
|
175
|
-
|
|
128
|
+
getAppleIdCredentialState,
|
|
129
|
+
// Passwords
|
|
176
130
|
getPassword,
|
|
131
|
+
passwordSignIn,
|
|
132
|
+
// Hybrid
|
|
177
133
|
hybridSignIn,
|
|
134
|
+
// Passkeys
|
|
178
135
|
createPassKey,
|
|
136
|
+
getPassKey,
|
|
137
|
+
cancelPassKeyAutoFill,
|
|
138
|
+
// Generic OAuth / OpenID Connect
|
|
139
|
+
oauthSignIn,
|
|
140
|
+
// Events
|
|
141
|
+
addCredentialSuccessListener,
|
|
142
|
+
addCredentialFailureListener,
|
|
143
|
+
addAppleIdCredentialRevokedListener,
|
|
179
144
|
// Constants
|
|
180
145
|
GOOGLE_PLAY_SERVICES_SUPPORTED,
|
|
181
146
|
PASSWORD_SUPPORTED,
|
|
@@ -183,356 +148,405 @@ import {
|
|
|
183
148
|
APPLE_ID_SUPPORTED,
|
|
184
149
|
HYBRID_SUPPORTED,
|
|
185
150
|
PASSKEYS_SUPPORTED,
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
type AppleIdCredentialResult,
|
|
191
|
-
type WebAppleIdCredentialResult,
|
|
192
|
-
type PasswordCredentialResult,
|
|
193
|
-
type PassKeyCredentialResult,
|
|
194
|
-
type CancelledCredentialResult,
|
|
151
|
+
OAUTH_REDIRECT_URI,
|
|
152
|
+
// Errors
|
|
153
|
+
isCredentialError,
|
|
154
|
+
// Enums
|
|
195
155
|
CredentialsType,
|
|
196
156
|
CredentialError,
|
|
157
|
+
AppleIdCredentialState,
|
|
197
158
|
} from 'react-native-oauth-essentials';
|
|
198
159
|
```
|
|
199
160
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
Check platform support before using OAuth methods:
|
|
161
|
+
</details>
|
|
203
162
|
|
|
204
|
-
|
|
205
|
-
|----------|------|-------------|
|
|
206
|
-
| `GOOGLE_PLAY_SERVICES_SUPPORTED` | `boolean` | Google Play Services are available. Always `false` on iOS. |
|
|
207
|
-
| `PASSWORD_SUPPORTED` | `boolean` | Password credential manager is supported on this device. |
|
|
208
|
-
| `GOOGLE_ID_SUPPORTED` | `boolean` | Google Sign-In is supported. **Use this to check if you can use `googleSignIn()`**. |
|
|
209
|
-
| `APPLE_ID_SUPPORTED` | `boolean` | Apple Sign-In is supported on this device. |
|
|
210
|
-
| `HYBRID_SUPPORTED` | `boolean` | Hybrid sign-in is supported (multiple methods at once). |
|
|
211
|
-
| `PASSKEYS_SUPPORTED` | `boolean` | Passkeys are supported on this device. |
|
|
163
|
+
### Conventions
|
|
212
164
|
|
|
213
|
-
**
|
|
165
|
+
- Dismissing the UI **resolves** `{ type: CredentialsType.CANCELLED, data: null }` instead of rejecting.
|
|
166
|
+
- Errors reject with `error.code` from `CredentialError` (every rejection has a `code`). Narrow them with
|
|
167
|
+
`isCredentialError`.
|
|
168
|
+
- iOS rejects a second call of the same flow while the first is pending with `SIMULTANEOUS_CALL_ERROR`.
|
|
169
|
+
- In development, options that have no effect on the current platform log a `console.warn` once
|
|
170
|
+
(`[react-native-oauth-essentials] googleSignIn: "hint" is ignored on Android.`).
|
|
214
171
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
await googleSignIn('YOUR_WEB_CLIENT_ID');
|
|
218
|
-
}
|
|
219
|
-
```
|
|
172
|
+
<details>
|
|
173
|
+
<summary><b>Constants</b> (feature detection)</summary>
|
|
220
174
|
|
|
221
|
-
|
|
175
|
+
| Constant | iOS | Android |
|
|
176
|
+
|---|---|---|
|
|
177
|
+
| `GOOGLE_PLAY_SERVICES_SUPPORTED` | `false` | Play Services installed and up to date |
|
|
178
|
+
| `GOOGLE_ID_SUPPORTED` | `true` | Same as `GOOGLE_PLAY_SERVICES_SUPPORTED` |
|
|
179
|
+
| `PASSWORD_SUPPORTED` | `true` | Same as `GOOGLE_PLAY_SERVICES_SUPPORTED` |
|
|
180
|
+
| `APPLE_ID_SUPPORTED` | `true` | `true` (web flow) |
|
|
181
|
+
| `HYBRID_SUPPORTED` | `true` | Same as `GOOGLE_PLAY_SERVICES_SUPPORTED` |
|
|
182
|
+
| `PASSKEYS_SUPPORTED` | iOS 16+ | Android 9+ |
|
|
183
|
+
| `OAUTH_REDIRECT_URI` | `<bundle id, lowercased>://oauth/callback` | `<applicationId>://oauth/callback` |
|
|
222
184
|
|
|
223
|
-
|
|
185
|
+
</details>
|
|
224
186
|
|
|
225
|
-
|
|
187
|
+
### Google
|
|
226
188
|
|
|
227
|
-
|
|
228
|
-
- `webClientId` (string): Your Google OAuth 2.0 web client ID
|
|
229
|
-
- `options` (GoogleSignInOptions, optional): Sign-in configuration
|
|
189
|
+
#### `googleSignIn(clientId?, options?)`
|
|
230
190
|
|
|
231
|
-
|
|
191
|
+
Shows the Google sign in UI. Also `googleSignIn()` / `googleSignIn(options)`: the client id is optional and, when it is
|
|
192
|
+
omitted, read from the app config:
|
|
232
193
|
|
|
233
|
-
|
|
194
|
+
- iOS: `GIDClientID` in Info.plist, then `CLIENT_ID` of the Firebase `GoogleService-Info.plist`. The `serverClientId`
|
|
195
|
+
(idToken audience and `serverAuthCode`) falls back to `webClientId`, then `GIDServerClientID`. iOS also needs the
|
|
196
|
+
reversed iOS client id as a URL scheme in `Info.plist` (`1234-abcd.apps.googleusercontent.com` becomes
|
|
197
|
+
`com.googleusercontent.apps.1234-abcd`): without it the call rejects with `CONFIGURATION_ERROR`, whose message has the
|
|
198
|
+
exact scheme to add.
|
|
199
|
+
- Android: the web client id from the `com.oauthessentials.GOOGLE_WEB_CLIENT_ID` `<meta-data>` of AndroidManifest.xml,
|
|
200
|
+
then the Firebase `default_web_client_id` string (from `google-services.json`). The Android client id is never passed.
|
|
201
|
+
- None found: rejects with `CONFIGURATION_ERROR`. The [Expo config plugin](#-expo) writes all of it.
|
|
234
202
|
|
|
235
|
-
|
|
203
|
+
`clientId` can also be passed as `{ iosClientId?, webClientId? }` (all from the same Google Cloud project; a passed id
|
|
204
|
+
wins over the config), or as a string used as is (the iOS client id on iOS, the web client id on Android).
|
|
236
205
|
|
|
237
206
|
```typescript
|
|
238
|
-
const result = await googleSignIn(
|
|
239
|
-
|
|
240
|
-
|
|
207
|
+
const result = await googleSignIn({ // or googleSignIn({ iosClientId, webClientId }, { ... })
|
|
208
|
+
mode: 'button', // Android: full "Sign in with Google" flow (use it for button taps)
|
|
209
|
+
nonce, // embedded in the idToken
|
|
210
|
+
scopes: ['https://www.googleapis.com/auth/calendar.readonly'],
|
|
241
211
|
});
|
|
242
212
|
|
|
243
213
|
if (result.type === CredentialsType.GOOGLE_ID) {
|
|
244
|
-
|
|
245
|
-
console.log('ID Token:', result.data.idToken);
|
|
246
|
-
console.log('Name:', result.data.displayName);
|
|
214
|
+
await api.signInWithGoogle(result.data.idToken);
|
|
247
215
|
}
|
|
248
216
|
```
|
|
249
217
|
|
|
250
|
-
|
|
218
|
+
**Returns:** `Promise<GoogleIdCredentialResult | CancelledCredentialResult>`
|
|
219
|
+
|
|
220
|
+
#### `googleSilentSignIn(clientId?, options?)`
|
|
251
221
|
|
|
252
|
-
|
|
222
|
+
Restores a previous Google sign in, without UI on iOS (and on Android when a single account signed in before). Rejects
|
|
223
|
+
with `NO_SIGN_IN_AVAILABLE` when there is nothing to restore.
|
|
253
224
|
|
|
254
|
-
|
|
225
|
+
```typescript
|
|
226
|
+
try {
|
|
227
|
+
const result = await googleSilentSignIn();
|
|
228
|
+
} catch (e) {
|
|
229
|
+
if (isCredentialError(e, CredentialError.NO_SIGN_IN_AVAILABLE)) showSignInScreen();
|
|
230
|
+
}
|
|
231
|
+
```
|
|
255
232
|
|
|
256
|
-
|
|
257
|
-
- `androidWebUrl` (string, optional): Web URL for Android OAuth flow. **Required on Android.**
|
|
233
|
+
#### `signOut()`
|
|
258
234
|
|
|
259
|
-
|
|
235
|
+
Clears the Google session on iOS and the Credential Manager state on Android (no auto select until the user picks an
|
|
236
|
+
account again). **Returns:** `Promise<true>`
|
|
260
237
|
|
|
261
|
-
|
|
262
|
-
- iOS: Native Apple Sign-In UI
|
|
263
|
-
- Android: Opens browser with your web URL for OAuth flow
|
|
238
|
+
### Apple
|
|
264
239
|
|
|
265
|
-
|
|
240
|
+
#### `appleSignIn(androidWebUrl?, options?)`
|
|
266
241
|
|
|
267
|
-
|
|
242
|
+
- iOS: native sheet. `options`: `{ nonce?, state? }`. Apple only sends `email`/`fullName` on the first authorization;
|
|
243
|
+
the library caches them in memory, so store them on your backend.
|
|
244
|
+
- Android: opens `androidWebUrl` in a Custom Tab. Your backend runs the Apple web flow and redirects to
|
|
245
|
+
`<applicationId>://oauth/callback`. `options` are ignored.
|
|
268
246
|
|
|
269
247
|
```typescript
|
|
270
|
-
const result = await appleSignIn('https://
|
|
248
|
+
const result = await appleSignIn('https://api.example.com/auth/apple', { nonce: sha256(rawNonce) });
|
|
271
249
|
|
|
272
250
|
if (result.type === CredentialsType.APPLE_ID) {
|
|
273
|
-
// iOS
|
|
274
|
-
console.log('User:', result.data.user);
|
|
275
|
-
console.log('ID Token:', result.data.idToken);
|
|
276
|
-
console.log('Email:', result.data.email);
|
|
251
|
+
await api.signInWithApple(result.data.idToken, result.data.fullName); // iOS
|
|
277
252
|
} else if (result.type === CredentialsType.WEB_APPLE_ID) {
|
|
278
|
-
// Android
|
|
279
|
-
console.log('Redirect URL:', result.data.url);
|
|
253
|
+
await api.finishAppleWebFlow(result.data.url); // Android
|
|
280
254
|
}
|
|
281
255
|
```
|
|
282
256
|
|
|
283
|
-
|
|
257
|
+
**Returns:** `Promise<AppleIdCredentialResult | WebAppleIdCredentialResult | CancelledCredentialResult>`
|
|
284
258
|
|
|
285
|
-
#### `
|
|
259
|
+
#### `getAppleIdCredentialState(userId)` (iOS)
|
|
286
260
|
|
|
287
|
-
|
|
261
|
+
Checks without UI whether the Apple ID `user` is still signed in to the app. Call it on app start.
|
|
288
262
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
263
|
+
```typescript
|
|
264
|
+
const state = await getAppleIdCredentialState(storedAppleUserId);
|
|
265
|
+
if (state !== AppleIdCredentialState.AUTHORIZED) signOutLocally(); // REVOKED, NOT_FOUND, TRANSFERRED
|
|
266
|
+
```
|
|
292
267
|
|
|
293
|
-
|
|
268
|
+
Rejects with `NOT_SUPPORTED_ERROR` on Android.
|
|
294
269
|
|
|
295
|
-
|
|
270
|
+
### Passwords
|
|
271
|
+
|
|
272
|
+
#### `getPassword()`
|
|
296
273
|
|
|
297
|
-
**
|
|
274
|
+
Shows the passwords saved for the app. **Returns:** `Promise<PasswordCredentialResult | CancelledCredentialResult>`.
|
|
275
|
+
When there is no password or the user dismisses it, both platforms resolve the `CANCELLED` result. Other Android errors
|
|
276
|
+
reject with `INVALID_RESULT_ERROR`.
|
|
298
277
|
|
|
299
278
|
```typescript
|
|
300
|
-
const
|
|
301
|
-
|
|
279
|
+
const result = await getPassword();
|
|
280
|
+
if (result.type === CredentialsType.PASSWORD) {
|
|
281
|
+
await api.signIn(result.data.username, result.data.password);
|
|
282
|
+
}
|
|
302
283
|
```
|
|
303
284
|
|
|
304
|
-
|
|
285
|
+
> **Migrating to 1.1:** `getPassword()` no longer resolves `false` on Android. Replace `if (!result)` /
|
|
286
|
+
> `result === false` checks with `result.type === CredentialsType.CANCELLED`.
|
|
305
287
|
|
|
306
|
-
#### `
|
|
288
|
+
#### `passwordSignIn(username, password)`
|
|
307
289
|
|
|
308
|
-
|
|
290
|
+
Saves a password in the password manager (it does not authenticate). Android shows the save prompt and resolves `true`
|
|
291
|
+
when saved. iOS resolves `false` without doing anything: use `textContentType` on your inputs instead.
|
|
309
292
|
|
|
310
|
-
|
|
293
|
+
### Hybrid
|
|
311
294
|
|
|
312
|
-
|
|
295
|
+
#### `hybridSignIn(clientId?, options?)`
|
|
313
296
|
|
|
314
|
-
|
|
297
|
+
One picker with the credentials the user already has, meant for app start. iOS offers Apple ID + saved passwords (the
|
|
298
|
+
Google client id is ignored); Android offers Google + saved passwords (uses `webClientId`, or the one in the app config).
|
|
315
299
|
|
|
316
300
|
```typescript
|
|
317
|
-
const result = await
|
|
318
|
-
|
|
319
|
-
if (result && result.type === CredentialsType.PASSWORD) {
|
|
320
|
-
console.log('Username:', result.data.username);
|
|
321
|
-
console.log('Password:', result.data.password);
|
|
322
|
-
} else {
|
|
323
|
-
console.log('No stored password found');
|
|
324
|
-
}
|
|
301
|
+
const result = await hybridSignIn({ authorizedAccounts: true });
|
|
302
|
+
if (result.type === CredentialsType.CANCELLED) showSignInScreen();
|
|
325
303
|
```
|
|
326
304
|
|
|
327
|
-
|
|
305
|
+
**Returns:** `Promise<AppleIdCredentialResult | GoogleIdCredentialResult | PasswordCredentialResult | CancelledCredentialResult>`
|
|
328
306
|
|
|
329
|
-
|
|
307
|
+
### Passkeys
|
|
330
308
|
|
|
331
|
-
|
|
309
|
+
Your server generates the WebAuthn options and verifies the responses. Needs `webcredentials:` Associated Domains on iOS
|
|
310
|
+
(Expo plugin `webCredentialsDomains`) and `assetlinks.json` with the `get_login_creds` relation on Android.
|
|
332
311
|
|
|
333
|
-
|
|
334
|
-
- `googleClientId` (string): Your Google OAuth 2.0 web client ID
|
|
335
|
-
- `options` (GoogleSignInOptions, optional): Google sign-in configuration
|
|
312
|
+
#### `createPassKey({ requestJson })`
|
|
336
313
|
|
|
337
|
-
|
|
314
|
+
Registers a passkey from a `PublicKeyCredentialCreationOptions` JSON string. `result.data` is the registration response
|
|
315
|
+
JSON to send to the server.
|
|
338
316
|
|
|
339
|
-
|
|
317
|
+
#### `getPassKey({ requestJson, mediation? })`
|
|
340
318
|
|
|
341
|
-
|
|
319
|
+
Signs in with a passkey from a `PublicKeyCredentialRequestOptions` JSON string. On iOS, `mediation: 'autofill'` shows the
|
|
320
|
+
passkeys in the keyboard of a `textContentType="username"` input instead of a sheet.
|
|
342
321
|
|
|
343
322
|
```typescript
|
|
344
|
-
const result = await
|
|
345
|
-
|
|
346
|
-
switch (result.type) {
|
|
347
|
-
case CredentialsType.GOOGLE_ID:
|
|
348
|
-
console.log('Google user:', result.data.displayName);
|
|
349
|
-
break;
|
|
350
|
-
case CredentialsType.APPLE_ID:
|
|
351
|
-
console.log('Apple user:', result.data.user);
|
|
352
|
-
break;
|
|
353
|
-
case CredentialsType.PASSWORD:
|
|
354
|
-
console.log('Username:', result.data.username);
|
|
355
|
-
break;
|
|
356
|
-
case CredentialsType.CANCELLED:
|
|
357
|
-
console.log('User cancelled');
|
|
358
|
-
break;
|
|
359
|
-
}
|
|
323
|
+
const result = await getPassKey({ requestJson: JSON.stringify(await api.passkeyOptions()) });
|
|
324
|
+
if (result.type === CredentialsType.PASSKEY) await api.verifyPasskey(result.data);
|
|
360
325
|
```
|
|
361
326
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
#### `createPassKey(options)`
|
|
365
|
-
|
|
366
|
-
Creates a new passkey credential.
|
|
327
|
+
#### `cancelPassKeyAutoFill()`
|
|
367
328
|
|
|
368
|
-
|
|
369
|
-
|
|
329
|
+
Cancels a pending `getPassKey({ mediation: 'autofill' })` (call it when the screen unmounts). Resolves `true` when there
|
|
330
|
+
was one to cancel.
|
|
370
331
|
|
|
371
|
-
|
|
332
|
+
### Generic OAuth / OpenID Connect
|
|
372
333
|
|
|
373
|
-
|
|
334
|
+
#### `oauthSignIn(options)`
|
|
374
335
|
|
|
375
|
-
|
|
336
|
+
Authorization code flow with PKCE for any provider (GitHub, Microsoft, Auth0, Keycloak…). Register `OAUTH_REDIRECT_URI`
|
|
337
|
+
(or your own `redirectUri`) in the provider.
|
|
376
338
|
|
|
377
339
|
```typescript
|
|
378
|
-
const result = await
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
340
|
+
const result = await oauthSignIn({
|
|
341
|
+
authorizationEndpoint: 'https://github.com/login/oauth/authorize',
|
|
342
|
+
clientId: GITHUB_CLIENT_ID,
|
|
343
|
+
scopes: ['read:user', 'user:email'],
|
|
344
|
+
// tokenEndpoint: exchanges the code on the device (public clients only, never with a client secret)
|
|
382
345
|
});
|
|
383
346
|
|
|
384
|
-
if (result.type === CredentialsType.
|
|
385
|
-
|
|
347
|
+
if (result.type === CredentialsType.OAUTH) {
|
|
348
|
+
await api.exchangeCode(result.data.code, result.data.codeVerifier, result.data.redirectUri);
|
|
386
349
|
}
|
|
387
350
|
```
|
|
388
351
|
|
|
389
|
-
|
|
352
|
+
**Returns:** `Promise<OAuthCredentialResult | CancelledCredentialResult>`
|
|
353
|
+
|
|
354
|
+
### Events
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
const success = addCredentialSuccessListener(({ type }) => analytics.track('sign_in', { type }));
|
|
358
|
+
const failure = addCredentialFailureListener(({ code }) => analytics.track('sign_in_error', { code }));
|
|
359
|
+
const revoked = addAppleIdCredentialRevokedListener(() => signOutLocally()); // iOS
|
|
390
360
|
|
|
391
|
-
|
|
361
|
+
success.remove();
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Success and failure fire for `googleSignIn`, `googleSilentSignIn`, `getPassword`, `appleSignIn`, `hybridSignIn`,
|
|
365
|
+
`createPassKey` and `getPassKey`.
|
|
392
366
|
|
|
393
|
-
|
|
367
|
+
<details>
|
|
368
|
+
<summary><b>Options</b> (TypeScript types)</summary>
|
|
394
369
|
|
|
395
370
|
```typescript
|
|
371
|
+
type GoogleClientId = string | { iosClientId?: string; webClientId?: string };
|
|
372
|
+
|
|
396
373
|
type GoogleSignInOptions = {
|
|
397
|
-
|
|
398
|
-
|
|
374
|
+
mode?: 'bottomSheet' | 'button'; // Android
|
|
375
|
+
authorizedAccounts?: boolean; // Android: only accounts that signed in before
|
|
376
|
+
autoSelectEnabled?: boolean; // Android
|
|
377
|
+
nonce?: string;
|
|
378
|
+
hint?: string; // iOS: email to preselect
|
|
379
|
+
hostedDomain?: string; // Google Workspace domain
|
|
380
|
+
scopes?: string[];
|
|
381
|
+
offlineAccess?: boolean; // Android: returns serverAuthCode
|
|
382
|
+
serverClientId?: string; // iOS: idToken audience + serverAuthCode (default webClientId). Android: serverAuthCode client id
|
|
399
383
|
};
|
|
400
|
-
```
|
|
401
384
|
|
|
402
|
-
|
|
385
|
+
type AppleSignInOptions = { nonce?: string; state?: string }; // iOS
|
|
403
386
|
|
|
404
|
-
```typescript
|
|
405
387
|
type PasskeyOptions = {
|
|
406
|
-
requestJson: string;
|
|
407
|
-
preferImmediatelyAvailableCredentials?: boolean; // Android
|
|
408
|
-
|
|
388
|
+
requestJson: string;
|
|
389
|
+
preferImmediatelyAvailableCredentials?: boolean; // Android
|
|
390
|
+
mediation?: 'modal' | 'autofill'; // iOS 16+
|
|
409
391
|
};
|
|
410
|
-
```
|
|
411
392
|
|
|
412
|
-
|
|
393
|
+
type OAuthSignInOptions = {
|
|
394
|
+
authorizationEndpoint: string;
|
|
395
|
+
clientId: string;
|
|
396
|
+
redirectUri?: string; // default OAUTH_REDIRECT_URI
|
|
397
|
+
scopes?: string[];
|
|
398
|
+
additionalParameters?: Record<string, string>;
|
|
399
|
+
tokenEndpoint?: string;
|
|
400
|
+
usePKCE?: boolean; // default true
|
|
401
|
+
prefersEphemeralSession?: boolean; // iOS
|
|
402
|
+
};
|
|
403
|
+
```
|
|
413
404
|
|
|
414
|
-
|
|
405
|
+
</details>
|
|
415
406
|
|
|
416
|
-
|
|
407
|
+
<details>
|
|
408
|
+
<summary><b>Results</b> (TypeScript types)</summary>
|
|
417
409
|
|
|
418
410
|
```typescript
|
|
419
411
|
type GoogleIdCredentialResult = {
|
|
420
412
|
type: CredentialsType.GOOGLE_ID;
|
|
421
413
|
data: {
|
|
422
|
-
id: string;
|
|
423
|
-
idToken: string;
|
|
424
|
-
displayName: string;
|
|
425
|
-
givenName: string;
|
|
426
|
-
familyName: string;
|
|
427
|
-
|
|
428
|
-
|
|
414
|
+
id: string; // iOS: Google user id (sub). Android: email
|
|
415
|
+
idToken: string;
|
|
416
|
+
displayName: string;
|
|
417
|
+
givenName: string;
|
|
418
|
+
familyName: string;
|
|
419
|
+
email?: string;
|
|
420
|
+
profilePictureUri?: string | null;
|
|
421
|
+
phoneNumber?: string | null; // Android
|
|
422
|
+
accessToken?: string | null;
|
|
423
|
+
serverAuthCode?: string | null;
|
|
424
|
+
grantedScopes?: string[] | null;
|
|
429
425
|
};
|
|
430
426
|
};
|
|
431
|
-
```
|
|
432
427
|
|
|
433
|
-
#### `AppleIdCredentialResult`
|
|
434
|
-
|
|
435
|
-
```typescript
|
|
436
428
|
type AppleIdCredentialResult = {
|
|
437
429
|
type: CredentialsType.APPLE_ID;
|
|
438
430
|
data: {
|
|
439
|
-
idToken: string;
|
|
440
|
-
authorizationCode: string;
|
|
441
|
-
user: string;
|
|
442
|
-
email: string;
|
|
443
|
-
fullName: string;
|
|
444
|
-
likelyReal: boolean;
|
|
431
|
+
idToken: string;
|
|
432
|
+
authorizationCode: string;
|
|
433
|
+
user: string; // stable Apple user id
|
|
434
|
+
email: string;
|
|
435
|
+
fullName: string;
|
|
436
|
+
likelyReal: boolean;
|
|
437
|
+
state?: string | null;
|
|
445
438
|
};
|
|
446
439
|
};
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
#### `WebAppleIdCredentialResult`
|
|
450
440
|
|
|
451
|
-
Android-specific result when using web-based Apple Sign-In:
|
|
452
|
-
|
|
453
|
-
```typescript
|
|
454
441
|
type WebAppleIdCredentialResult = {
|
|
455
|
-
type: CredentialsType.WEB_APPLE_ID;
|
|
456
|
-
data: {
|
|
457
|
-
url: string; // Full redirect URL
|
|
458
|
-
scheme: string | null; // URL scheme
|
|
459
|
-
host: string | null; // URL host
|
|
460
|
-
path: string | null; // URL path
|
|
461
|
-
query: string | null; // URL query parameters
|
|
462
|
-
};
|
|
442
|
+
type: CredentialsType.WEB_APPLE_ID; // Android
|
|
443
|
+
data: { url: string; scheme: string | null; host: string | null; path: string | null; query: string | null };
|
|
463
444
|
};
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
#### `PasswordCredentialResult`
|
|
467
445
|
|
|
468
|
-
```typescript
|
|
469
446
|
type PasswordCredentialResult = {
|
|
470
447
|
type: CredentialsType.PASSWORD;
|
|
471
|
-
data: {
|
|
472
|
-
username: string; // Stored username
|
|
473
|
-
password: string; // Stored password
|
|
474
|
-
};
|
|
448
|
+
data: { username: string; password: string };
|
|
475
449
|
};
|
|
476
|
-
```
|
|
477
450
|
|
|
478
|
-
#### `PassKeyCredentialResult`
|
|
479
|
-
|
|
480
|
-
```typescript
|
|
481
451
|
type PassKeyCredentialResult = {
|
|
482
452
|
type: CredentialsType.PASSKEY;
|
|
483
|
-
data: string;
|
|
453
|
+
data: string; // WebAuthn response JSON
|
|
484
454
|
};
|
|
485
|
-
```
|
|
486
455
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
456
|
+
type OAuthCredentialResult = {
|
|
457
|
+
type: CredentialsType.OAUTH;
|
|
458
|
+
data: {
|
|
459
|
+
code: string;
|
|
460
|
+
state: string;
|
|
461
|
+
redirectUri: string;
|
|
462
|
+
codeVerifier: string | null;
|
|
463
|
+
params: Record<string, string>; // all redirect parameters
|
|
464
|
+
tokens: Record<string, unknown> | null; // token endpoint response
|
|
465
|
+
};
|
|
493
466
|
};
|
|
467
|
+
|
|
468
|
+
type CancelledCredentialResult = { type: CredentialsType.CANCELLED; data: null };
|
|
494
469
|
```
|
|
495
470
|
|
|
496
|
-
|
|
471
|
+
</details>
|
|
497
472
|
|
|
498
|
-
###
|
|
473
|
+
### Error codes
|
|
499
474
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
---
|
|
475
|
+
| `CredentialError` | When |
|
|
476
|
+
|---|---|
|
|
477
|
+
| `CONFIGURATION_ERROR` | Setup bug: no Google client id (passed or in the app config), iOS URL scheme missing from Info.plist (`message` has the exact scheme), invalid `redirectUri` |
|
|
478
|
+
| `NOT_SUPPORTED_ERROR` | No Play Services (Android), passkeys on old OS, `getAppleIdCredentialState` on Android |
|
|
479
|
+
| `NO_ACTIVITY_ERROR` | No activity / view controller to present the UI |
|
|
480
|
+
| `NO_SIGN_IN_AVAILABLE` | Nothing to return: no previous sign in, account or passkey |
|
|
481
|
+
| `BROWSER_UNAVAILABLE` | Browser session could not open (iOS: app not active, Guided Access; Android: no browser) |
|
|
482
|
+
| `SIMULTANEOUS_CALL_ERROR` | iOS: the same flow is still pending |
|
|
483
|
+
| `NETWORK_ERROR` | `oauthSignIn`: the token request failed |
|
|
484
|
+
| `INVALID_RESULT_ERROR` | The provider failed or returned an unexpected result (`message` has the details) |
|
|
485
|
+
| `NO_PLAY_SERVICES_ERROR` | Deprecated, never thrown |
|
|
512
486
|
|
|
513
|
-
|
|
487
|
+
`isCredentialError(error, code?)` narrows an unknown error to `OauthEssentialsError` (`Error & { code }`):
|
|
514
488
|
|
|
515
489
|
```typescript
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
490
|
+
try {
|
|
491
|
+
await googleSignIn();
|
|
492
|
+
} catch (e) {
|
|
493
|
+
if (isCredentialError(e, CredentialError.CONFIGURATION_ERROR)) console.error(e.message);
|
|
494
|
+
else if (isCredentialError(e, CredentialError.BROWSER_UNAVAILABLE)) showRetryMessage();
|
|
495
|
+
else if (isCredentialError(e)) report(e.code);
|
|
496
|
+
else throw e;
|
|
520
497
|
}
|
|
521
498
|
```
|
|
522
499
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
500
|
+
## 📱 Expo
|
|
501
|
+
|
|
502
|
+
Not supported in Expo Go (it only contains the Expo SDK native code; importing the library there throws an error saying
|
|
503
|
+
so): use a development build (`npx expo run:ios` / `npx expo run:android` or `eas build --profile development`). The
|
|
504
|
+
config plugin writes the native config:
|
|
505
|
+
|
|
506
|
+
```json
|
|
507
|
+
{
|
|
508
|
+
"expo": {
|
|
509
|
+
"plugins": [
|
|
510
|
+
["react-native-oauth-essentials", {
|
|
511
|
+
"google": { "iosClientId": "1234-abcd.apps.googleusercontent.com", "webClientId": "5678-efgh.apps.googleusercontent.com" },
|
|
512
|
+
"appleSignIn": true,
|
|
513
|
+
"webCredentialsDomains": ["example.com"]
|
|
514
|
+
}]
|
|
515
|
+
]
|
|
531
516
|
}
|
|
532
517
|
}
|
|
533
518
|
```
|
|
534
519
|
|
|
535
|
-
|
|
520
|
+
- `google`: `{ iosClientId?, webClientId? }` or `true`. Missing ids are read from the Firebase files set in
|
|
521
|
+
`ios.googleServicesFile` / `android.googleServicesFile`. iOS: the reversed iOS client id URL scheme and `GIDClientID` /
|
|
522
|
+
`GIDServerClientID` in Info.plist. Android: the `com.oauthessentials.GOOGLE_WEB_CLIENT_ID` meta-data. Then call
|
|
523
|
+
`googleSignIn()` without client ids.
|
|
524
|
+
- `appleSignIn`: adds the Sign in with Apple entitlement.
|
|
525
|
+
- `webCredentialsDomains`: adds `webcredentials:` associated domains (passkeys and saved passwords).
|
|
526
|
+
|
|
527
|
+
If the native module is missing outside Expo Go, rebuild the app after installing (`pod install` + native build;
|
|
528
|
+
reloading Metro is not enough) with the New Architecture enabled.
|
|
529
|
+
|
|
530
|
+
[Expo docs](https://lucaswitch.github.io/react-native-oauth-essentials/docs/expo)
|
|
531
|
+
|
|
532
|
+
## 🧪 Testing (Jest)
|
|
533
|
+
|
|
534
|
+
Importing the library in Jest fails with a `native module "OauthEssentials" was not found` error unless you use the
|
|
535
|
+
bundled mock:
|
|
536
|
+
|
|
537
|
+
```js
|
|
538
|
+
// jest.setup.js (referenced by "setupFiles" in the Jest config)
|
|
539
|
+
jest.mock('react-native-oauth-essentials', () => require('react-native-oauth-essentials/jest'));
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
Every method is a `jest.fn()` resolving a plausible result; override it per test with `mockResolvedValueOnce` /
|
|
543
|
+
`mockRejectedValueOnce`. [Testing docs](https://lucaswitch.github.io/react-native-oauth-essentials/docs/testing)
|
|
544
|
+
|
|
545
|
+
## 🤝 Contributing
|
|
546
|
+
|
|
547
|
+
Contributions are welcome! Read the [contributing guide](./CONTRIBUTING.md) and the [code of conduct](./CODE_OF_CONDUCT.md).
|
|
548
|
+
|
|
549
|
+
## 💬 Short history
|
|
536
550
|
|
|
537
551
|
I’ve always felt that React Native lacked a single library that handles OAuth end-to-end. Most React Native apps end up
|
|
538
552
|
downloading multiple libraries just to implement OAuth features. Each library feels different—they follow different
|
|
@@ -546,6 +560,14 @@ I was frustrated going through this process myself, and seeing how much time Rea
|
|
|
546
560
|
deliver a “good enough” sign-in experience. So I thought—why not create one library that actually behaves and doesn’t
|
|
547
561
|
make me want to throw my laptop out the window?
|
|
548
562
|
|
|
549
|
-
## Philosophy
|
|
563
|
+
## 💚 Philosophy
|
|
550
564
|
|
|
551
565
|
Free forever, up-to-date features, and focused only on the essentials your mobile app really needs.
|
|
566
|
+
|
|
567
|
+
---
|
|
568
|
+
|
|
569
|
+
<div align="center">
|
|
570
|
+
|
|
571
|
+
MIT © [Lucas Alves](https://github.com/lucaswitch) · If this saved you time, a ⭐ on [GitHub](https://github.com/lucaswitch/react-native-oauth-essentials) helps a lot!
|
|
572
|
+
|
|
573
|
+
</div>
|