@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.
@@ -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
+ }
@@ -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
+ }