@pricava/react-native-google-credential 0.1.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/IMPLEMENTATION.md +351 -0
- package/README.md +63 -0
- package/adapters.ts +1 -0
- package/android/build.gradle +25 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/pricava/googlecredential/PricavaGoogleCredentialPackage.kt +35 -0
- package/android/src/main/java/com/pricava/googlecredential/PricavaGoogleCredentialReactModule.kt +172 -0
- package/android/src/main/java/com/pricava/googlecredential/ReactGoogleCredentialOptions.kt +45 -0
- package/app.plugin.js +1 -0
- package/index.ts +2 -0
- package/ios/PricavaGoogleCredential.h +13 -0
- package/ios/PricavaGoogleCredential.mm +194 -0
- package/ios/PricavaGoogleCredential.podspec +24 -0
- package/package.json +70 -0
- package/plugin/withGoogleCredentialIos.js +39 -0
- package/react-native.config.js +15 -0
- package/src/NativePricavaGoogleCredential.ts +32 -0
- package/src/PricavaGoogleCredentialModule.ts +31 -0
- package/src/PricavaGoogleCredentialModule.web.ts +296 -0
- package/src/adapters/google-auth-provider.ts +155 -0
- package/src/adapters/index.ts +2 -0
- package/src/adapters/supabase.ts +28 -0
- package/src/index.ts +98 -0
- package/src/types.ts +54 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# React Native Google Credential Package
|
|
2
|
+
|
|
3
|
+
This package provides one platform-agnostic JavaScript API for acquiring a Google
|
|
4
|
+
ID token from the current runtime. It hides Android, iOS, and web differences
|
|
5
|
+
inside the native module so consuming features do not need to branch on
|
|
6
|
+
`Platform.OS`.
|
|
7
|
+
|
|
8
|
+
It returns the Google credential payload, especially `idToken`, It support adapters can exchange
|
|
9
|
+
that ID token with third party auth like Supabase, can support other auth
|
|
10
|
+
providers such as Clerk, Auth0, Convex, Firebase Auth, or a custom backend.
|
|
11
|
+
|
|
12
|
+
## Public API
|
|
13
|
+
|
|
14
|
+
Import from the package root:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import {
|
|
18
|
+
clearGoogleCredentialState,
|
|
19
|
+
isGoogleCredentialAvailable,
|
|
20
|
+
signInWithGoogleCredential,
|
|
21
|
+
} from "@pricava/react-native-google-credential";
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### `signInWithGoogleCredential(options)`
|
|
25
|
+
|
|
26
|
+
The main API for application code.
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
const credential = await signInWithGoogleCredential({
|
|
30
|
+
webClientId: process.env.GOOGLE_WEB_CLIENT_ID,
|
|
31
|
+
iosClientId: process.env.GOOGLE_IOS_CLIENT_ID,
|
|
32
|
+
nonce: hashedNonce,
|
|
33
|
+
android: {
|
|
34
|
+
flow: "sign-in-button",
|
|
35
|
+
accountFilter: "authorized-first",
|
|
36
|
+
autoSelect: true,
|
|
37
|
+
},
|
|
38
|
+
web: {
|
|
39
|
+
autoSelect: true,
|
|
40
|
+
useFedCm: true,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
It checks package-level availability, then dispatches to the active platform
|
|
46
|
+
implementation. Consumers should not check `Platform.OS` before calling it.
|
|
47
|
+
|
|
48
|
+
### `isGoogleCredentialAvailable()`
|
|
49
|
+
|
|
50
|
+
Returns whether the package can attempt Google credential sign-in on the current
|
|
51
|
+
runtime. Unsupported platforms return `false`.
|
|
52
|
+
|
|
53
|
+
### `clearGoogleCredentialState()`
|
|
54
|
+
|
|
55
|
+
Clears or disables the platform's saved credential state where supported. It is
|
|
56
|
+
safe to call on unsupported platforms; the package returns without throwing.
|
|
57
|
+
|
|
58
|
+
### Lower-Level Exports
|
|
59
|
+
|
|
60
|
+
The package still exports `signInAsync`, `isAvailableAsync`, and
|
|
61
|
+
`clearCredentialStateAsync` for advanced callers that explicitly need the raw
|
|
62
|
+
native module shape. App features should prefer the platform-agnostic methods.
|
|
63
|
+
|
|
64
|
+
## Options
|
|
65
|
+
|
|
66
|
+
| Option | Required | Platforms | Purpose |
|
|
67
|
+
| ----------------------- | ----------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
68
|
+
| `webClientId` | Yes | Android, iOS, Web | OAuth Web client ID. This value becomes the ID token audience that the selected auth provider or backend verifies. |
|
|
69
|
+
| `iosClientId` | iOS only unless bundled | iOS | OAuth iOS client ID. If omitted, the iOS module tries `GIDClientID` from `Info.plist`, then `CLIENT_ID` from `GoogleService-Info.plist`. |
|
|
70
|
+
| `nonce` | No | Android, iOS, Web | OIDC nonce. If the selected auth provider validates nonces, pass the hashed nonce here and send the original nonce to that provider during token exchange. |
|
|
71
|
+
| `android.flow` | No | Android | `"sign-in-button"` uses Android's explicit Sign in with Google option. `"credential"` uses the regular Google ID credential option. Defaults to `"sign-in-button"`. |
|
|
72
|
+
| `android.accountFilter` | No | Android | `"authorized-first"` starts with accounts that previously authorized the app, then falls back to all accounts. `"all"` starts with all accounts. Defaults to `"authorized-first"`. |
|
|
73
|
+
| `android.autoSelect` | No | Android | Enables Android auto-select when one eligible credential is available. Defaults to `true`. |
|
|
74
|
+
| `web.autoSelect` | No | Web | Enables Google Identity Services auto-select. Defaults to `true`. |
|
|
75
|
+
| `web.useFedCm` | No | Web | Enables Google Identity Services FedCM prompt support. Defaults to `true`. |
|
|
76
|
+
|
|
77
|
+
## Result
|
|
78
|
+
|
|
79
|
+
All platforms return the same `GoogleCredentialResult` shape:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
type GoogleCredentialResult = {
|
|
83
|
+
idToken: string;
|
|
84
|
+
displayName: string | null;
|
|
85
|
+
email: string | null;
|
|
86
|
+
familyName: string | null;
|
|
87
|
+
givenName: string | null;
|
|
88
|
+
id: string | null;
|
|
89
|
+
phoneNumber: string | null;
|
|
90
|
+
profilePictureUri: string | null;
|
|
91
|
+
};
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The only required field is `idToken`.
|
|
95
|
+
|
|
96
|
+
## Platform Support
|
|
97
|
+
|
|
98
|
+
| Platform | Status | Implementation |
|
|
99
|
+
| -------- | ----------- | ---------------------------------------------------------------------------------------- |
|
|
100
|
+
| Android | Supported | AndroidX Credential Manager with Google ID credential options. |
|
|
101
|
+
| iOS | Supported | GoogleSignIn iOS SDK through the native module. |
|
|
102
|
+
| Web | Supported | Google Identity Services loaded on demand in `PricavaGoogleCredentialModule.web.ts`. |
|
|
103
|
+
| Other | Unsupported | Availability returns `false`; high-level calls throw `GoogleCredentialUnavailableError`. |
|
|
104
|
+
|
|
105
|
+
## Android Implementation
|
|
106
|
+
|
|
107
|
+
Android lives in:
|
|
108
|
+
|
|
109
|
+
```txt
|
|
110
|
+
android/src/main/java/com/pricava/googlecredential/
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`PricavaGoogleCredentialReactModule.kt` uses Android Credential Manager:
|
|
114
|
+
|
|
115
|
+
- `GetSignInWithGoogleOption` when `android.flow` is `"sign-in-button"`.
|
|
116
|
+
- `GetGoogleIdOption` when `android.flow` is `"credential"`.
|
|
117
|
+
- `android.accountFilter: "authorized-first"` first, then fallback to all
|
|
118
|
+
Google accounts if Android returns `NoCredentialException`.
|
|
119
|
+
- `setNonce` when a nonce is supplied.
|
|
120
|
+
- `clearCredentialStateAsync` maps to `CredentialManager.clearCredentialState`.
|
|
121
|
+
|
|
122
|
+
The Android result is mapped from `GoogleIdTokenCredential` into the shared
|
|
123
|
+
TypeScript result shape.
|
|
124
|
+
|
|
125
|
+
## iOS Implementation
|
|
126
|
+
|
|
127
|
+
iOS lives in:
|
|
128
|
+
|
|
129
|
+
```txt
|
|
130
|
+
ios/PricavaGoogleCredential.h
|
|
131
|
+
ios/PricavaGoogleCredential.mm
|
|
132
|
+
ios/PricavaGoogleCredential.podspec
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`PricavaGoogleCredential.mm` uses the GoogleSignIn iOS SDK:
|
|
136
|
+
|
|
137
|
+
- `GIDConfiguration(clientID:serverClientID:)` is configured for each sign-in.
|
|
138
|
+
- `clientID` comes from `options.iosClientId`, `Info.plist` key `GIDClientID`,
|
|
139
|
+
or `GoogleService-Info.plist` key `CLIENT_ID`.
|
|
140
|
+
- `serverClientID` comes from `options.webClientId`.
|
|
141
|
+
- The current React Native presenter is resolved through `RCTPresentedViewController()`.
|
|
142
|
+
- The nonce is passed to `GIDSignIn.sharedInstance.signIn(... nonce: ...)`.
|
|
143
|
+
- `clearCredentialStateAsync` maps to `GIDSignIn.sharedInstance.signOut()`.
|
|
144
|
+
- Bare React Native apps must forward the Google redirect URL to
|
|
145
|
+
`GIDSignIn.sharedInstance.handle(_:)` in the app delegate. Expo apps can use
|
|
146
|
+
the config plugin for URL scheme setup.
|
|
147
|
+
|
|
148
|
+
The podspec declares:
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
s.dependency "GoogleSignIn", "~> 9.0"
|
|
152
|
+
install_modules_dependencies(s)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The app also needs a Google URL scheme so Google can redirect back to the app.
|
|
156
|
+
This project uses the local `plugins/withGoogleCredentialIos.js` Expo config
|
|
157
|
+
plugin for that. The plugin writes `GIDClientID` from
|
|
158
|
+
`GOOGLE_IOS_CLIENT_ID` and registers the redirect URL scheme from
|
|
159
|
+
`GOOGLE_IOS_URL_SCHEME`, or derives the scheme from the iOS client
|
|
160
|
+
ID when possible.
|
|
161
|
+
|
|
162
|
+
## Web Implementation
|
|
163
|
+
|
|
164
|
+
Web lives in:
|
|
165
|
+
|
|
166
|
+
```txt
|
|
167
|
+
src/PricavaGoogleCredentialModule.web.ts
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The web module:
|
|
171
|
+
|
|
172
|
+
- Returns available when `window` and `document` exist.
|
|
173
|
+
- Loads `https://accounts.google.com/gsi/client` on demand.
|
|
174
|
+
- Initializes Google Identity Services with `client_id`, `nonce`,
|
|
175
|
+
`web.autoSelect`, and `web.useFedCm`.
|
|
176
|
+
- Resolves with the returned ID token.
|
|
177
|
+
- Decodes the ID token payload locally to populate display/profile fields.
|
|
178
|
+
- Calls `google.accounts.id.disableAutoSelect()` for clear state.
|
|
179
|
+
|
|
180
|
+
Only the ID token is security-critical. Decoded profile fields are used as
|
|
181
|
+
display metadata and should not be treated as verified authorization data by the
|
|
182
|
+
client.
|
|
183
|
+
|
|
184
|
+
## Auth Provider Integration
|
|
185
|
+
|
|
186
|
+
The package core is provider-neutral. It only acquires a Google credential and
|
|
187
|
+
returns a Google ID token. Provider exchange helpers live in the package
|
|
188
|
+
`adapters` folder so consumers can opt into third-party auth integrations
|
|
189
|
+
without duplicating the Google credential and nonce orchestration code.
|
|
190
|
+
|
|
191
|
+
The provider-neutral adapter helper lives in:
|
|
192
|
+
|
|
193
|
+
```txt
|
|
194
|
+
modules/pricava-google-credential/src/adapters/google-auth-provider.ts
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
That helper:
|
|
198
|
+
|
|
199
|
+
1. Accepts explicit `credentialOptions` from the consuming app.
|
|
200
|
+
2. Creates an OIDC nonce pair.
|
|
201
|
+
3. Calls the package-level `signInWithGoogleCredential`.
|
|
202
|
+
4. Passes `credential`, `idToken`, and the original nonce to an
|
|
203
|
+
`exchangeCredential` callback.
|
|
204
|
+
|
|
205
|
+
The current Supabase adapter factory lives in:
|
|
206
|
+
|
|
207
|
+
```txt
|
|
208
|
+
modules/pricava-google-credential/src/adapters/supabase.ts
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
That wrapper reads environment configuration and passes the existing Supabase
|
|
212
|
+
exchange function into the package adapter:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
const signIn = createSupabaseGoogleAuthAdapter({
|
|
216
|
+
credentialOptions,
|
|
217
|
+
exchangeGoogleIdToken: signInWithSupabaseGoogleIdToken,
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Future providers should follow the same shape. For example:
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
signInWithGoogleAuthProvider({
|
|
225
|
+
credentialOptions,
|
|
226
|
+
exchangeCredential: ({ idToken, nonce, credential }) =>
|
|
227
|
+
exchangeGoogleTokenWithProvider({
|
|
228
|
+
idToken,
|
|
229
|
+
nonce,
|
|
230
|
+
email: credential.email,
|
|
231
|
+
}),
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Provider-specific concerns stay in provider-specific adapters:
|
|
236
|
+
|
|
237
|
+
- Supabase: exchange Google ID token through `/auth/v1/token?grant_type=id_token`.
|
|
238
|
+
- Clerk: hand the Google ID token to a Clerk-supported custom or OIDC flow.
|
|
239
|
+
- Auth0: exchange the Google ID token with an Auth0 connection or custom backend.
|
|
240
|
+
- Convex: send the Google ID token to a Convex action or mutation that verifies it
|
|
241
|
+
or delegates verification to an auth service.
|
|
242
|
+
- Custom backend: verify the Google ID token server-side and mint an app session.
|
|
243
|
+
|
|
244
|
+
This separation keeps Android, iOS, and web credential acquisition independent
|
|
245
|
+
from any one auth vendor while still letting the package offer first-class
|
|
246
|
+
adapter helpers.
|
|
247
|
+
|
|
248
|
+
## Required Configuration
|
|
249
|
+
|
|
250
|
+
### Shared
|
|
251
|
+
|
|
252
|
+
Set the OAuth Web client ID:
|
|
253
|
+
|
|
254
|
+
```txt
|
|
255
|
+
GOOGLE_WEB_CLIENT_ID=...
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
This value is used on all platforms as the Google ID token audience for
|
|
259
|
+
backend token verification.
|
|
260
|
+
|
|
261
|
+
### iOS
|
|
262
|
+
|
|
263
|
+
Provide an iOS OAuth client ID using one of these options:
|
|
264
|
+
|
|
265
|
+
```txt
|
|
266
|
+
GOOGLE_IOS_CLIENT_ID=...
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
or bundle `GoogleService-Info.plist` so the native SDK can read `CLIENT_ID`.
|
|
270
|
+
|
|
271
|
+
The app must also register the reversed iOS client ID as a URL scheme. With the
|
|
272
|
+
local Google credential config plugin, either set it explicitly:
|
|
273
|
+
|
|
274
|
+
```txt
|
|
275
|
+
GOOGLE_IOS_URL_SCHEME=com.googleusercontent.apps.<reversed-ios-client-id>
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
or let `plugins/withGoogleCredentialIos.js` derive it from
|
|
279
|
+
`GOOGLE_IOS_CLIENT_ID`.
|
|
280
|
+
|
|
281
|
+
### Android
|
|
282
|
+
|
|
283
|
+
Android requires the package name and signing certificate fingerprints to be
|
|
284
|
+
registered for the OAuth client in Google Cloud/Firebase. It should use `google-services.json` for Android configuration.
|
|
285
|
+
|
|
286
|
+
### Web
|
|
287
|
+
|
|
288
|
+
The OAuth web client must allow the deployed web origin. Local development also
|
|
289
|
+
needs the localhost origins registered when testing browser sign-in. Google
|
|
290
|
+
Identity Services validates the exact page origin, so register the bare
|
|
291
|
+
localhost origin and every local host/port combination you use:
|
|
292
|
+
|
|
293
|
+
```txt
|
|
294
|
+
http://localhost
|
|
295
|
+
http://localhost:8081
|
|
296
|
+
http://localhost:8082
|
|
297
|
+
http://127.0.0.1:8081
|
|
298
|
+
http://127.0.0.1:8082
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
These entries belong in **Authorized JavaScript origins** on the same Web OAuth
|
|
302
|
+
client ID configured as `GOOGLE_WEB_CLIENT_ID`. Redirect URIs are
|
|
303
|
+
not used by the current web implementation because Google returns the ID token
|
|
304
|
+
through the JavaScript callback.
|
|
305
|
+
|
|
306
|
+
## Error Behavior
|
|
307
|
+
|
|
308
|
+
The high-level API throws `GoogleCredentialUnavailableError` when the current
|
|
309
|
+
platform cannot support Google credential sign-in.
|
|
310
|
+
|
|
311
|
+
Platform implementations can also throw errors from their native SDKs, for
|
|
312
|
+
example:
|
|
313
|
+
|
|
314
|
+
- missing iOS presenter
|
|
315
|
+
- missing iOS client ID
|
|
316
|
+
- user-cancelled sign-in
|
|
317
|
+
- Google Identity Services script load failure
|
|
318
|
+
- Android Credential Manager request failure
|
|
319
|
+
|
|
320
|
+
App features should catch and convert these into user-facing auth messages.
|
|
321
|
+
|
|
322
|
+
## File Map
|
|
323
|
+
|
|
324
|
+
```txt
|
|
325
|
+
modules/pricava-google-credential/
|
|
326
|
+
adapters.ts
|
|
327
|
+
index.ts
|
|
328
|
+
IMPLEMENTATION.md
|
|
329
|
+
README.md
|
|
330
|
+
react-native.config.js
|
|
331
|
+
android/
|
|
332
|
+
build.gradle
|
|
333
|
+
src/main/java/com/pricava/googlecredential/
|
|
334
|
+
PricavaGoogleCredentialPackage.kt
|
|
335
|
+
PricavaGoogleCredentialReactModule.kt
|
|
336
|
+
ReactGoogleCredentialOptions.kt
|
|
337
|
+
ios/
|
|
338
|
+
PricavaGoogleCredential.h
|
|
339
|
+
PricavaGoogleCredential.mm
|
|
340
|
+
PricavaGoogleCredential.podspec
|
|
341
|
+
src/
|
|
342
|
+
NativePricavaGoogleCredential.ts
|
|
343
|
+
adapters/
|
|
344
|
+
google-auth-provider.ts
|
|
345
|
+
index.ts
|
|
346
|
+
supabase.ts
|
|
347
|
+
index.ts
|
|
348
|
+
types.ts
|
|
349
|
+
PricavaGoogleCredentialModule.ts
|
|
350
|
+
PricavaGoogleCredentialModule.web.ts
|
|
351
|
+
```
|
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# React Native Google Credential
|
|
2
|
+
|
|
3
|
+
Google credential sign-in package for React Native and Expo apps across Android, iOS, and web.
|
|
4
|
+
The native runtime is implemented as a React Native TurboModule/native module.
|
|
5
|
+
Expo development builds and prebuild projects use the same native module path.
|
|
6
|
+
|
|
7
|
+
The JavaScript API intentionally returns only the Google credential payload. App
|
|
8
|
+
features decide how to exchange the ID token with an auth provider, which keeps
|
|
9
|
+
this module extractable as a standalone package later.
|
|
10
|
+
|
|
11
|
+
## API
|
|
12
|
+
|
|
13
|
+
Use `signInWithGoogleCredential` from app code. The package checks platform
|
|
14
|
+
availability internally and dispatches to the matching native or web
|
|
15
|
+
implementation, so consumers do not need to check `Platform.OS`.
|
|
16
|
+
|
|
17
|
+
The lower-level `signInAsync`, `isAvailableAsync`, and
|
|
18
|
+
`clearCredentialStateAsync` exports remain available for advanced use.
|
|
19
|
+
|
|
20
|
+
Use generic environment variable names in app config and examples:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
GOOGLE_WEB_CLIENT_ID=...
|
|
24
|
+
GOOGLE_IOS_CLIENT_ID=...
|
|
25
|
+
GOOGLE_IOS_URL_SCHEME=...
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Optional auth provider helpers are exported from:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { createSupabaseGoogleAuthAdapter } from "@pricava/react-native-google-credential/adapters";
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Future provider integrations such as Clerk, Auth0, and Convex should live in
|
|
35
|
+
the package `src/adapters/` folder.
|
|
36
|
+
|
|
37
|
+
See [IMPLEMENTATION.md](./IMPLEMENTATION.md) for the full architecture,
|
|
38
|
+
platform support matrix, setup requirements, and feature behavior.
|
|
39
|
+
|
|
40
|
+
## Android
|
|
41
|
+
|
|
42
|
+
`signInAsync` uses Credential Manager with `GetSignInWithGoogleOption` by
|
|
43
|
+
default for an explicit "Sign in with Google" button. Set
|
|
44
|
+
`android.flow: "credential"` to use `GetGoogleIdOption` instead. Pass the OAuth
|
|
45
|
+
web client ID as `webClientId`. If your auth provider validates an OIDC nonce,
|
|
46
|
+
pass the hashed nonce to this module and the original nonce to the provider
|
|
47
|
+
exchange.
|
|
48
|
+
|
|
49
|
+
## Web
|
|
50
|
+
|
|
51
|
+
`signInAsync` loads Google Identity Services on demand and returns the same ID
|
|
52
|
+
token result shape as Android. The web implementation uses the OAuth web client
|
|
53
|
+
ID as `webClientId`, passes through the nonce for provider-side validation,
|
|
54
|
+
uses FedCM prompts by default, and disables auto-select in
|
|
55
|
+
`clearCredentialStateAsync`.
|
|
56
|
+
|
|
57
|
+
## iOS
|
|
58
|
+
|
|
59
|
+
`signInAsync` uses the GoogleSignIn iOS SDK. Pass the OAuth web client ID as
|
|
60
|
+
`webClientId` and the OAuth iOS client ID as `iosClientId`, or bundle
|
|
61
|
+
`GoogleService-Info.plist` so the native SDK can resolve the iOS client ID.
|
|
62
|
+
The iOS app must also register the reversed iOS client ID URL scheme.
|
|
63
|
+
|
package/adapters.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/adapters";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
plugins {
|
|
2
|
+
id 'com.android.library'
|
|
3
|
+
id 'org.jetbrains.kotlin.android'
|
|
4
|
+
id 'com.facebook.react'
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
group = 'com.pricava'
|
|
8
|
+
version = '0.1.0'
|
|
9
|
+
|
|
10
|
+
android {
|
|
11
|
+
namespace "com.pricava.googlecredential"
|
|
12
|
+
|
|
13
|
+
defaultConfig {
|
|
14
|
+
versionCode 1
|
|
15
|
+
versionName "0.1.0"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
dependencies {
|
|
20
|
+
implementation "com.facebook.react:react-android"
|
|
21
|
+
implementation "androidx.credentials:credentials:1.6.0-beta03"
|
|
22
|
+
implementation "androidx.credentials:credentials-play-services-auth:1.6.0-beta03"
|
|
23
|
+
implementation "com.google.android.libraries.identity.googleid:googleid:1.1.1"
|
|
24
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2"
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
package com.pricava.googlecredential
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
|
|
9
|
+
class PricavaGoogleCredentialPackage : BaseReactPackage() {
|
|
10
|
+
override fun getModule(
|
|
11
|
+
name: String,
|
|
12
|
+
reactContext: ReactApplicationContext
|
|
13
|
+
): NativeModule? {
|
|
14
|
+
return if (name == PricavaGoogleCredentialReactModule.NAME) {
|
|
15
|
+
PricavaGoogleCredentialReactModule(reactContext)
|
|
16
|
+
} else {
|
|
17
|
+
null
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
|
22
|
+
return ReactModuleInfoProvider {
|
|
23
|
+
mapOf(
|
|
24
|
+
PricavaGoogleCredentialReactModule.NAME to ReactModuleInfo(
|
|
25
|
+
PricavaGoogleCredentialReactModule.NAME,
|
|
26
|
+
PricavaGoogleCredentialReactModule::class.java.name,
|
|
27
|
+
false,
|
|
28
|
+
false,
|
|
29
|
+
false,
|
|
30
|
+
true
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
package/android/src/main/java/com/pricava/googlecredential/PricavaGoogleCredentialReactModule.kt
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
package com.pricava.googlecredential
|
|
2
|
+
|
|
3
|
+
import android.os.Bundle
|
|
4
|
+
import androidx.credentials.ClearCredentialStateRequest
|
|
5
|
+
import androidx.credentials.CredentialOption
|
|
6
|
+
import androidx.credentials.CredentialManager
|
|
7
|
+
import androidx.credentials.GetCredentialRequest
|
|
8
|
+
import androidx.credentials.GetCredentialResponse
|
|
9
|
+
import androidx.credentials.exceptions.GetCredentialException
|
|
10
|
+
import androidx.credentials.exceptions.NoCredentialException
|
|
11
|
+
import androidx.credentials.exceptions.publickeycredential.GetPublicKeyCredentialException
|
|
12
|
+
import com.facebook.react.bridge.Arguments
|
|
13
|
+
import com.facebook.react.bridge.Promise
|
|
14
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
15
|
+
import com.facebook.react.bridge.ReadableMap
|
|
16
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
17
|
+
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
|
|
18
|
+
import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption
|
|
19
|
+
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
|
|
20
|
+
import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException
|
|
21
|
+
import kotlinx.coroutines.CoroutineScope
|
|
22
|
+
import kotlinx.coroutines.Dispatchers
|
|
23
|
+
import kotlinx.coroutines.SupervisorJob
|
|
24
|
+
import kotlinx.coroutines.launch
|
|
25
|
+
|
|
26
|
+
@ReactModule(name = PricavaGoogleCredentialReactModule.NAME)
|
|
27
|
+
class PricavaGoogleCredentialReactModule(
|
|
28
|
+
private val reactContext: ReactApplicationContext
|
|
29
|
+
) : NativePricavaGoogleCredentialSpec(reactContext) {
|
|
30
|
+
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
|
31
|
+
|
|
32
|
+
override fun getName() = NAME
|
|
33
|
+
|
|
34
|
+
override fun isAvailableAsync(promise: Promise) {
|
|
35
|
+
promise.resolve(true)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
override fun signInAsync(options: ReadableMap, promise: Promise) {
|
|
39
|
+
val parsedOptions = ReactGoogleCredentialOptions.fromMap(options)
|
|
40
|
+
|
|
41
|
+
coroutineScope.launch {
|
|
42
|
+
try {
|
|
43
|
+
val response = requestCredential(parsedOptions)
|
|
44
|
+
promise.resolve(Arguments.fromBundle(mapCredentialResponse(response)))
|
|
45
|
+
} catch (exception: NoCredentialException) {
|
|
46
|
+
if (parsedOptions.androidAccountFilter == "authorized-first") {
|
|
47
|
+
try {
|
|
48
|
+
val fallbackOptions = parsedOptions.copy(
|
|
49
|
+
androidAccountFilter = "all",
|
|
50
|
+
androidAutoSelect = false
|
|
51
|
+
)
|
|
52
|
+
val response = requestCredential(fallbackOptions)
|
|
53
|
+
promise.resolve(Arguments.fromBundle(mapCredentialResponse(response)))
|
|
54
|
+
} catch (fallbackException: Exception) {
|
|
55
|
+
reject(promise, fallbackException)
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
reject(promise, exception)
|
|
59
|
+
}
|
|
60
|
+
} catch (exception: Exception) {
|
|
61
|
+
reject(promise, exception)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override fun clearCredentialStateAsync(promise: Promise) {
|
|
67
|
+
coroutineScope.launch {
|
|
68
|
+
try {
|
|
69
|
+
val activity = reactContext.currentActivity
|
|
70
|
+
?: throw IllegalStateException("No current activity found for Google credential sign-in.")
|
|
71
|
+
val credentialManager = CredentialManager.create(activity)
|
|
72
|
+
credentialManager.clearCredentialState(ClearCredentialStateRequest())
|
|
73
|
+
promise.resolve(null)
|
|
74
|
+
} catch (exception: Exception) {
|
|
75
|
+
reject(promise, exception)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private suspend fun requestCredential(
|
|
81
|
+
options: ReactGoogleCredentialOptions
|
|
82
|
+
): GetCredentialResponse {
|
|
83
|
+
val activity = reactContext.currentActivity
|
|
84
|
+
?: throw IllegalStateException("No current activity found for Google credential sign-in.")
|
|
85
|
+
val credentialManager = CredentialManager.create(activity)
|
|
86
|
+
val request = GetCredentialRequest.Builder()
|
|
87
|
+
.addCredentialOption(createCredentialOption(options))
|
|
88
|
+
.build()
|
|
89
|
+
|
|
90
|
+
return credentialManager.getCredential(
|
|
91
|
+
context = activity,
|
|
92
|
+
request = request
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private fun createGoogleIdOption(options: ReactGoogleCredentialOptions): GetGoogleIdOption {
|
|
97
|
+
val builder = GetGoogleIdOption.Builder()
|
|
98
|
+
.setFilterByAuthorizedAccounts(options.androidAccountFilter == "authorized-first")
|
|
99
|
+
.setServerClientId(options.webClientId)
|
|
100
|
+
.setAutoSelectEnabled(options.androidAutoSelect)
|
|
101
|
+
|
|
102
|
+
options.nonce?.takeIf { it.isNotBlank() }?.let { builder.setNonce(it) }
|
|
103
|
+
|
|
104
|
+
return builder.build()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private fun createSignInWithGoogleOption(
|
|
108
|
+
options: ReactGoogleCredentialOptions
|
|
109
|
+
): GetSignInWithGoogleOption {
|
|
110
|
+
val builder = GetSignInWithGoogleOption.Builder(options.webClientId)
|
|
111
|
+
|
|
112
|
+
options.nonce?.takeIf { it.isNotBlank() }?.let { builder.setNonce(it) }
|
|
113
|
+
|
|
114
|
+
return builder.build()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private fun createCredentialOption(options: ReactGoogleCredentialOptions): CredentialOption {
|
|
118
|
+
return if (options.androidFlow == "sign-in-button") {
|
|
119
|
+
createSignInWithGoogleOption(options)
|
|
120
|
+
} else {
|
|
121
|
+
createGoogleIdOption(options)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private fun mapCredentialResponse(response: GetCredentialResponse): Bundle {
|
|
126
|
+
val credential = response.credential
|
|
127
|
+
|
|
128
|
+
if (credential.type != GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
|
|
129
|
+
throw IllegalStateException(
|
|
130
|
+
"Credential Manager returned unsupported credential type: ${credential.type}."
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
val googleCredential = try {
|
|
135
|
+
GoogleIdTokenCredential.createFrom(credential.data)
|
|
136
|
+
} catch (exception: GoogleIdTokenParsingException) {
|
|
137
|
+
throw IllegalStateException(
|
|
138
|
+
"Credential Manager returned a Google credential that could not be parsed.",
|
|
139
|
+
exception
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return Bundle().apply {
|
|
144
|
+
putString("idToken", googleCredential.idToken)
|
|
145
|
+
putString("displayName", googleCredential.displayName)
|
|
146
|
+
putString("email", googleCredential.id)
|
|
147
|
+
putString("familyName", googleCredential.familyName)
|
|
148
|
+
putString("givenName", googleCredential.givenName)
|
|
149
|
+
putString("id", googleCredential.id)
|
|
150
|
+
putString("phoneNumber", googleCredential.phoneNumber)
|
|
151
|
+
putString("profilePictureUri", googleCredential.profilePictureUri?.toString())
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private fun reject(promise: Promise, exception: Exception) {
|
|
156
|
+
val code = when (exception) {
|
|
157
|
+
is GetPublicKeyCredentialException -> "ERR_GOOGLE_CREDENTIAL_REQUEST"
|
|
158
|
+
is GetCredentialException -> "ERR_GOOGLE_CREDENTIAL_REQUEST"
|
|
159
|
+
else -> "ERR_GOOGLE_CREDENTIAL_REQUEST"
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
promise.reject(
|
|
163
|
+
code,
|
|
164
|
+
exception.localizedMessage ?: "Google credential request failed.",
|
|
165
|
+
exception
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
companion object {
|
|
170
|
+
const val NAME = "PricavaGoogleCredential"
|
|
171
|
+
}
|
|
172
|
+
}
|