-
Notifications
You must be signed in to change notification settings - Fork 410
feat(component): Implement Google Sign-In support for Android and iOS #7208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: bc11a78 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds native Google Sign-In (One Tap) support to the Expo package: Android and iOS native modules, an Expo config plugin, TypeScript bridge and types, platform-specific hooks with tests, and packaging/build metadata. Changes
Sequence Diagram(s)sequenceDiagram
participant App as React App
participant Hook as useSignInWithGoogle Hook
participant Bridge as ClerkGoogleOneTapSignIn (JS)
participant Native as Native Module (Android/iOS)
participant GoogleSDK as Google SDK
participant Clerk as Clerk API
App->>Hook: startGoogleAuthenticationFlow(params?)
activate Hook
Hook->>Hook: ensure hooks loaded, read clientId(s)
Hook->>Bridge: configure({ webClientId, ... })
Bridge->>Native: configure()
Native->>GoogleSDK: initialize
Hook->>Hook: generate nonce + SHA-256
Hook->>Bridge: presentExplicitSignIn({ nonce })
activate Native
Native->>GoogleSDK: present UI
GoogleSDK-->>Native: credential / cancel
Native->>Bridge: response { idToken, user } or cancelled
deactivate Native
alt Cancelled
Bridge-->>Hook: CancelledResponse
Hook-->>App: { createdSessionId: null }
else Success
Hook->>Clerk: signIn(strategy='google_one_tap', token)
alt User exists
Clerk-->>Hook: createdSessionId
Hook-->>App: { createdSessionId }
else Transfer / not found
Hook->>Clerk: signUp.create(transfer, token)
Clerk-->>Hook: createdSessionId
Hook-->>App: { createdSessionId }
end
else Error
Hook-->>App: throw error
end
deactivate Hook
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…ndroid and enhance logging for iOS
- Removed package dependency for google sign in for android. Working on iOS next. Screw this persons package https://react-native-google-signin.github.io/docs. Going to go straight to the source. - Implemented the Clerk Google Sign-In module for Android, including configuration and sign-in functionalities. - Added necessary Gradle configuration and AndroidManifest setup. - Created native Kotlin module with methods for configuring, signing in, creating accounts, and signing out. - Developed TypeScript interface for the module, including response types for success, cancellation, and no saved credentials. - Integrated the module with Expo's autolinking and created a config plugin for seamless integration.
…iguration options
… and update package version
…-google-universal-sign-in
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Nitpick comments (15)
packages/expo/vitest.setup.mts (1)
6-18: Expo and__DEV__mocks look good; consider matching RN/Expo dev semanticsThe expo mock and guarded initialization are fine for tests. The only nuance is defaulting
globalThis.__DEV__tofalse, which makes the test runtime behave more like production than a typical React Native/Expo dev build. If any code under test gates behavior on__DEV__, you may want to consider defaulting it totrue(or reading from an env flag) so tests better mirror the usual development environment.packages/expo/android/build.gradle (1)
7-21: Gradle config is solid; consider aligning fallback SDK versions with Expo’s defaultsThe use of
safeExtGetand the overall Android/Kotlin configuration look good for an Expo module. To avoid divergence over time, you may want to keep the fallbackcompileSdk,targetSdk, andminSdkvalues in sync with whatever versions the root Expo setup uses (so that a misconfigured root project doesn’t silently build against an unexpected API level).packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts (1)
31-43: Module mocks are minimal; consider partial mocks to reduce brittlenessRight now the
react-nativeandexpo-modules-coremocks return only the pieces you need (PlatformandEventEmitter). IfuseSignInWithGoogle.androidlater imports additional exports from these modules, those will silently becomeundefinedin tests. To make the suite more robust, consider using partial mocks that spread the real module and override just what you need, e.g. viavi.importActual('react-native')and overridingPlatform.OS.packages/expo/app.plugin.js (1)
9-44: Config plugin logic is sound; optionally derive plugin version from package.jsonThe plugin correctly short‑circuits when no iOS URL scheme is configured and safely appends a new
CFBundleURLTypesentry only when needed, avoiding duplicates. As a small polish, you might consider wiringcreateRunOncePlugin’s version argument frompackage.json(similar to the podspec) to keep the plugin version in sync with the package, but that’s not required for correctness.packages/expo/ios/ClerkGoogleSignIn.podspec (1)
5-21: Podspec looks correct; consider pinning a tag ins.sourcefor reproducible buildsThe podspec wiring (metadata, platform, Swift version, dependencies, and
*.swiftsource file glob) looks appropriate for this module. If this spec is ever published to a CocoaPods spec repo or consumed via the:gitsource, you may want to add a:tagtied topackage['version']ins.sourceto make builds reproducible across releases.packages/expo/src/google-one-tap/index.ts (1)
1-21: Barrel index.ts goes against “avoid barrel files” guideline (low risk here).This file is a classic barrel re-export. It’s clean and tree‑shaking friendly, but it does conflict with the guideline to avoid
index.tsbarrels because of potential circular dependencies. Given it’s a leaf-ish submodule, the risk is low, but if this surface grows or starts being re-exported further up, consider switching callers to import directly from./ClerkGoogleOneTapSignInand./typesto keep dependency edges explicit.packages/expo/src/hooks/useSignInWithGoogle.ios.ts (1)
8-20: Tighten typings and avoid duplicating public API types in the iOS-specific file.
SignUpUnsafeMetadataisRecord<string, any>andStartGoogleAuthenticationFlowReturnTypeusesanyforsetActive,signIn, andsignUp. Guidelines say to avoidany; you already have precise types in the shared stub (useSignInWithGoogle.ts).- You’re also re‑declaring
StartGoogleAuthenticationFlowParams/ReturnTypehere, which risks drifting from the shared definition over time.Consider instead:
- Defining
SignUpUnsafeMetadataonce (e.g. in the shared stub) asRecord<string, unknown>; and- Importing the shared types here via a type‑only import, reusing the same
SetActive,SignInResource, andSignUpResourcetypes.For example:
-import { useSignIn, useSignUp } from '@clerk/clerk-react'; +import { useSignIn, useSignUp } from '@clerk/clerk-react'; +import type { SetActive, SignInResource, SignUpResource } from '@clerk/shared/types'; -// Type for unsafe metadata that can be attached to sign-ups -type SignUpUnsafeMetadata = Record<string, any>; +// Type for unsafe metadata that can be attached to sign-ups +type SignUpUnsafeMetadata = Record<string, unknown>; -export type StartGoogleAuthenticationFlowReturnType = { - createdSessionId: string | null; - setActive?: any; - signIn?: any; - signUp?: any; -}; +export type StartGoogleAuthenticationFlowReturnType = { + createdSessionId: string | null; + setActive?: SetActive; + signIn?: SignInResource; + signUp?: SignUpResource; +};This keeps the iOS implementation aligned with the public TS surface and preserves type safety.
packages/expo/ios/ClerkGoogleSignInModule.swift (1)
4-165: Native iOS Google Sign-In flow looks correct; consider trimming or wiring unused fields.
- Running all GoogleSignIn calls on the main queue and deriving the top-most presenting
UIViewControlleris appropriate for Expo modules.handleSignInResultcorrectly special-cases user cancellation viakGIDSignInErrorDomain+GIDSignInError.canceledand otherwise surfaces a structured success payload (type: "success",idToken, and user profile fields) that matches the JS bridge expectations.- Guarding on
clientIdand returning aNotConfiguredExceptionfrom sign-in methods is a good explicit failure mode.Minor cleanliness points:
hostedDomainandautoSelectEnabledinConfigureParamsare currently unused, as isNoSavedCredentialException. Either wire them into the GoogleSignIn configuration/flows or drop them to avoid confusion about behavior that doesn’t yet exist.Also applies to: 170-182, 209-212
packages/expo/src/hooks/useSignInWithGoogle.android.ts (4)
2-2: Consider importing types from@clerk/shared/types.As per coding guidelines, prefer importing types from
@clerk/shared/typesinstead of the deprecated@clerk/typesalias.
65-67: Add explicit return type annotation.As per coding guidelines, always define explicit return types for functions, especially public APIs.
- async function startGoogleAuthenticationFlow( - startGoogleAuthenticationFlowParams?: StartGoogleAuthenticationFlowParams, - ): Promise<StartGoogleAuthenticationFlowReturnType> { + async function startGoogleAuthenticationFlow( + startGoogleAuthenticationFlowParams?: StartGoogleAuthenticationFlowParams, + ): Promise<StartGoogleAuthenticationFlowReturnType> {The return type is already specified - this looks correct. No change needed.
150-165: Extract Clerk error type guard for improved readability.The inline type assertions are verbose and repeated. Consider extracting a type guard function to improve readability and maintainability, following the coding guideline to implement type guards for
unknowntypes.+function isClerkErrorWithCode(error: unknown, code: string): boolean { + if ( + error && + typeof error === 'object' && + 'clerkError' in error && + (error as { clerkError: boolean }).clerkError === true && + 'errors' in error && + Array.isArray((error as { errors: unknown[] }).errors) + ) { + return (error as { errors: Array<{ code: string }> }).errors.some( + err => err.code === code, + ); + } + return false; +} + // In the catch block: - const isClerkError = - signInError && - typeof signInError === 'object' && - 'clerkError' in signInError && - (signInError as { clerkError: boolean }).clerkError === true; - - const hasExternalAccountNotFoundError = - signInError && - typeof signInError === 'object' && - 'errors' in signInError && - Array.isArray((signInError as { errors: unknown[] }).errors) && - (signInError as { errors: Array<{ code: string }> }).errors.some( - err => err.code === 'external_account_not_found', - ); - - if (isClerkError && hasExternalAccountNotFoundError) { + if (isClerkErrorWithCode(signInError, 'external_account_not_found')) {
118-123: Add null check forsignInbefore callingcreate.If
isSignInLoadedis true butsignInis unexpectedly undefined, callingsignIn.create()will throw. Consider adding a defensive null check.+ if (!signIn || !signUp) { + return { + createdSessionId: null, + setActive, + signIn, + signUp, + }; + } + try { // Try to sign in with the Google One Tap strategy await signIn.create({packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts (1)
65-91: JSDoc@platformannotation may be misleading.The documentation states
@platform Android, but this file serves as a cross-platform bridge (no.android.tssuffix). The native module handles platform-specific logic. Consider updating the docs to reflect cross-platform support or adding a note about iOS availability.packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt (2)
74-75: Consider caching theCredentialManagerinstance.The
credentialManagergetter creates a new instance on each access. WhileCredentialManager.create()is lightweight, caching the instance could improve efficiency for repeated sign-in attempts.+ private var _credentialManager: CredentialManager? = null + private val credentialManager: CredentialManager - get() = CredentialManager.create(context) + get() = _credentialManager ?: CredentialManager.create(context).also { _credentialManager = it }
26-36: Empty string default forwebClientIdmay pass validation.
ConfigureParams.webClientIddefaults to an empty string. Theconfigurefunction stores this value directly without validation, which means an emptywebClientIdcould be set. While the sign-in will fail later with a less clear error, consider validating early.// Configure the module Function("configure") { params: ConfigureParams -> + if (params.webClientId.isBlank()) { + throw GoogleSignInException("webClientId is required and cannot be empty") + } webClientId = params.webClientId hostedDomain = params.hostedDomain autoSelectEnabled = params.autoSelectEnabled ?: false }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (17)
packages/expo/android/build.gradle(1 hunks)packages/expo/android/src/main/AndroidManifest.xml(1 hunks)packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt(1 hunks)packages/expo/app.plugin.js(1 hunks)packages/expo/expo-module.config.json(1 hunks)packages/expo/ios/ClerkGoogleSignIn.podspec(1 hunks)packages/expo/ios/ClerkGoogleSignInModule.swift(1 hunks)packages/expo/package.json(4 hunks)packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts(1 hunks)packages/expo/src/google-one-tap/index.ts(1 hunks)packages/expo/src/google-one-tap/types.ts(1 hunks)packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts(1 hunks)packages/expo/src/hooks/index.ts(1 hunks)packages/expo/src/hooks/useSignInWithGoogle.android.ts(1 hunks)packages/expo/src/hooks/useSignInWithGoogle.ios.ts(1 hunks)packages/expo/src/hooks/useSignInWithGoogle.ts(1 hunks)packages/expo/vitest.setup.mts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
All code must pass ESLint checks with the project's configuration
Files:
packages/expo/src/hooks/useSignInWithGoogle.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/app.plugin.jspackages/expo/src/hooks/useSignInWithGoogle.ios.tspackages/expo/src/google-one-tap/types.tspackages/expo/src/google-one-tap/index.tspackages/expo/src/hooks/index.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use Prettier for consistent code formatting
Files:
packages/expo/src/hooks/useSignInWithGoogle.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/app.plugin.jspackages/expo/package.jsonpackages/expo/src/hooks/useSignInWithGoogle.ios.tspackages/expo/src/google-one-tap/types.tspackages/expo/src/google-one-tap/index.tspackages/expo/expo-module.config.jsonpackages/expo/src/hooks/index.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
packages/**/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
TypeScript is required for all packages
Files:
packages/expo/src/hooks/useSignInWithGoogle.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.ios.tspackages/expo/src/google-one-tap/types.tspackages/expo/src/google-one-tap/index.tspackages/expo/src/hooks/index.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Follow established naming conventions (PascalCase for components, camelCase for variables)
Prefer importing types from
@clerk/shared/typesinstead of the deprecated@clerk/typesalias
Files:
packages/expo/src/hooks/useSignInWithGoogle.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/app.plugin.jspackages/expo/src/hooks/useSignInWithGoogle.ios.tspackages/expo/src/google-one-tap/types.tspackages/expo/src/google-one-tap/index.tspackages/expo/src/hooks/index.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
packages/**/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
packages/**/src/**/*.{ts,tsx,js,jsx}: Maintain comprehensive JSDoc comments for public APIs
Use tree-shaking friendly exports
Validate all inputs and sanitize outputs
All public APIs must be documented with JSDoc
Use dynamic imports for optional features
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Implement proper logging with different levels
Files:
packages/expo/src/hooks/useSignInWithGoogle.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.ios.tspackages/expo/src/google-one-tap/types.tspackages/expo/src/google-one-tap/index.tspackages/expo/src/hooks/index.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.ts?(x)
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use proper TypeScript error types
Files:
packages/expo/src/hooks/useSignInWithGoogle.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.ios.tspackages/expo/src/google-one-tap/types.tspackages/expo/src/google-one-tap/index.tspackages/expo/src/hooks/index.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)
**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoidanytype - preferunknownwhen type is uncertain, then narrow with type guards
Implement type guards forunknowntypes using the patternfunction isType(value: unknown): value is Type
Useinterfacefor object shapes that might be extended
Usetypefor unions, primitives, and computed types
Preferreadonlyproperties for immutable data structures
Useprivatefor internal implementation details in classes
Useprotectedfor inheritance hierarchies
Usepublicexplicitly for clarity in public APIs
Use mixins for shared behavior across unrelated classes in TypeScript
Use generic constraints with bounded type parameters like<T extends { id: string }>
Use utility types likeOmit,Partial, andPickfor data transformation instead of manual type construction
Use discriminated unions instead of boolean flags for state management and API responses
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation at the type level
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Document functions with JSDoc comments including @param, @returns, @throws, and @example tags
Create custom error classes that extend Error for specific error types
Use the Result pattern for error handling instead of throwing exceptions
Use optional chaining (?.) and nullish coalescing (??) operators for safe property access
Let TypeScript infer obvious types to reduce verbosity
Useconst assertionswithas constfor literal types
Usesatisfiesoperator for type checking without widening types
Declare readonly arrays and objects for immutable data structures
Use spread operator and array spread for immutable updates instead of mutations
Use lazy loading for large types...
Files:
packages/expo/src/hooks/useSignInWithGoogle.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.ios.tspackages/expo/src/google-one-tap/types.tspackages/expo/src/google-one-tap/index.tspackages/expo/src/hooks/index.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{test,spec}.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
**/*.{test,spec}.{ts,tsx,js,jsx}: Unit tests are required for all new functionality
Verify proper error handling and edge cases
Include tests for all new features
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts
**/*.{test,spec,e2e}.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use real Clerk instances for integration tests
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts
packages/*/package.json
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
packages/*/package.json: Packages should export TypeScript types alongside runtime code
Follow semantic versioning for all packagesAll packages must be published under @clerk namespace
Files:
packages/expo/package.json
**/package.json
📄 CodeRabbit inference engine (.cursor/rules/global.mdc)
Use pnpm as the package manager for this monorepo
Files:
packages/expo/package.json
**/index.ts
📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)
Avoid barrel files (index.ts re-exports) as they can cause circular dependencies
Files:
packages/expo/src/google-one-tap/index.tspackages/expo/src/hooks/index.ts
🧬 Code graph analysis (3)
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts (2)
packages/expo/src/hooks/useSignInWithGoogle.ios.ts (1)
useSignInWithGoogle(63-217)packages/expo/src/hooks/useSignInWithGoogle.android.ts (1)
useSignInWithGoogle(61-210)
packages/expo/ios/ClerkGoogleSignInModule.swift (2)
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt (1)
message(62-62)packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts (1)
signIn(117-131)
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts (2)
packages/expo/src/google-one-tap/types.ts (8)
ConfigureParams(4-31)SignInParams(36-49)OneTapResponse(145-145)CreateAccountParams(54-60)ExplicitSignInParams(65-71)CancelledResponse(129-132)NoSavedCredentialFound(137-140)OneTapSuccessResponse(111-124)packages/expo/src/google-one-tap/index.ts (13)
ConfigureParams(10-10)SignInParams(11-11)OneTapResponse(14-14)CreateAccountParams(12-12)ExplicitSignInParams(13-13)isCancelledResponse(3-3)CancelledResponse(16-16)isNoSavedCredentialFoundResponse(4-4)NoSavedCredentialFound(17-17)isSuccessResponse(5-5)OneTapSuccessResponse(15-15)isErrorWithCode(6-6)ClerkGoogleOneTapSignIn(2-2)
🪛 detekt (1.23.8)
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt
[warning] 115-115: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
[warning] 117-117: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
[warning] 154-154: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
[warning] 156-156: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
[warning] 192-192: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
🔇 Additional comments (13)
packages/expo/android/src/main/AndroidManifest.xml (1)
1-2: Minimal library manifest is acceptableA bare manifest with just the Android namespace is fine here since the namespace is configured in Gradle and the module doesn’t declare its own components. No issues from a packaging perspective.
packages/expo/src/hooks/index.ts (1)
14-18: NewuseSignInWithGoogleexport aligns with existing hook surfaceRe‑exporting
useSignInWithGooglefrom the hooks index keeps the Expo hook API consistent with Apple/SSO/OAuth exports and remains tree‑shaking friendly. Just ensureuseSignInWithGoogledoesn’t itself import from this barrel to avoid potential circular references.packages/expo/expo-module.config.json (1)
1-9: Expo module wiring looks consistent with native module namesThe config structure and module identifiers for Android and iOS look correct for Expo modules and line up with the described
ClerkGoogleSignInModuleimplementations. I don’t see any structural issues here.packages/expo/src/hooks/useSignInWithGoogle.ios.ts (1)
63-211: Core iOS Google sign-in flow, transfer handling, and cancellation behavior look solid.
- You correctly gate on
isSignInLoaded/isSignUpLoadedand still return{ setActive, signIn, signUp }, matching other Clerk hooks.- Env handling (
EXPO_PUBLIC_CLERK_GOOGLE_WEB_CLIENT_ID/EXPO_PUBLIC_CLERK_GOOGLE_IOS_CLIENT_ID) plusClerkGoogleOneTapSignIn.configurealigns with the native module signature.- Nonce generation + SHA‑256 hashing via
expo-cryptoand passing the hashed nonce intopresentExplicitSignInis the right pattern.- Transfer flow is covered twice: first via
signIn.firstFactorVerification.status === 'transferable', then via theexternal_account_not_foundClerk error path, which is a good safety net.- Cancellation from native (
SIGN_IN_CANCELLED) and non‑success One‑Tap responses both gracefully resolve withcreatedSessionId: nulland the Clerk handles, which gives callers a uniform “no session created” signal without throwing.No changes needed here from a logic standpoint.
packages/expo/package.json (1)
55-65: Exports and Expo module wiring look consistent; just confirm peer version ranges.
- Exporting
./google-one-tapwith bothtypesanddefaultmatches the rest of the package and keeps the TS surface available.- Including
android,ios,expo-module.config.json, andapp.plugin.jsinfilesensures the native module and config plugin ship correctly.- Adding
expo-constants/expo-modules-coreas both devDependencies and peerDependencies (withexpo-constantsoptional) matches how other Expo modules are usually modeled.Please just double‑check that the
>=12/>=3.0.0peer ranges align with the minimum Expo SDK versions you intend to support.Also applies to: 69-81, 104-116, 117-150
packages/expo/src/hooks/useSignInWithGoogle.ts (1)
16-69: Stub hook behavior and typing are good; just ensure the shared surface stays in sync with platform files.
- The JSDoc clearly communicates that this is a stub for non‑iOS/Android platforms and nudges devs toward
useSSOwithstrategy: "oauth_google", which is very helpful.- The explicit return type on
useSignInWithGoogleand theStartGoogleAuthenticationFlowReturnTypestructure (session id + optionalsetActive,signIn,signUp) matches what the platform-specific hooks are returning at runtime.- Using
errorThrower.throwto produce a consistent runtime error for unsupported platforms keeps the API predictable.Once the
SignUpUnsafeMetadatatype is added here and reused in the.ios/.androidvariants, the TS surface across platforms will be nicely aligned.packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts (3)
32-63: Well-implemented type guards.The type guard functions follow best practices with clear naming, proper
response is Typereturn signatures, and concise implementations. Good use of discriminated unions.
21-30: Lazy-loading pattern prevents initialization crashes.Good approach to lazily load the native module using
require(). This prevents app crashes when the module isn't available (e.g., in Expo Go or web).
194-204:GoogleUseris re-exported but not imported.The
GoogleUsertype is re-exported at line 203 but isn't included in the imports at lines 1-10. This will cause a TypeScript error.import type { ConfigureParams, SignInParams, CreateAccountParams, ExplicitSignInParams, OneTapResponse, OneTapSuccessResponse, CancelledResponse, NoSavedCredentialFound, + GoogleUser, } from './types';Likely an incorrect or invalid review comment.
packages/expo/src/google-one-tap/types.ts (2)
111-145: Well-designed discriminated union types.Good use of the discriminated union pattern with a
typefield forOneTapResponse. This enables exhaustive type checking and clean type narrowing with the type guards in the bridge module.
159-161: Consider usinginterfacevstypefor error interface.The
GoogleSignInErrorinterface extendingErrorfollows the coding guideline to useinterfacefor object shapes that might be extended. Good choice.packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt (2)
115-123: Exception swallowing is intentional and appropriate here.The static analysis tool flags these as "swallowed exceptions," but this is intentional behavior - the exceptions are being transformed into typed error responses (
GoogleSignInCancelledException,GoogleSignInNoCredentialException). The original exception details are captured in the custom exception messages where relevant.
227-228: User email is derived fromidfield.Note that
googleIdTokenCredential.idreturns the user's email address (used for credential matching), not the unique Google user ID (subclaim). This is consistent with how the type is documented but worth being aware of - if you need the actualsubclaim, you'd need to decode the ID token.
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt
Show resolved
Hide resolved
| // Generate a cryptographic nonce for replay attack protection | ||
| const nonce = Crypto.randomUUID(); | ||
|
|
||
| // Hash the nonce with SHA-256 (Google requires the hashed version) | ||
| const hashedNonce = await Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, nonce); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Clerk Google one-tap authentication nonce verification requirements
<web_search_result>
Short answer — Clerk requires you to verify the Google One‑Tap ID token exactly like any OIDC id_token: verify the JWT signature (use your Clerk instance JWKS), validate iss/aud/exp/iat/jti and ensure the id_token.nonce matches the nonce you supplied to the One‑Tap prompt. Also enable Google as a social connection in your Clerk app and pass the nonce into Google One‑Tap (data-nonce / nonce option). [1][2][3]
Steps (concise)
- Enable Google as a social connection in Clerk and use custom credentials. [4]
- When rendering One‑Tap, pass a cryptographically‑random nonce (data-nonce / nonce). [3]
- On receipt, verify the id_token JWT: signature (use Clerk Frontend API /.well-known/jwks.json), issuer, audience (client_id), exp/iat, etc. — and confirm id_token.claims.nonce equals the nonce you sent. [1][2]
Sources
- Clerk: verify social provider id_token and claims (includes nonce claim). [1]
- Google OIDC / One‑Tap docs: nonce parameter and how to pass it (JS / HTML APIs). [2][3]
</web_search_result>
Script executed:
cat -n packages/expo/src/hooks/useSignInWithGoogle.android.ts | head -150Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
1 import { useSignIn, useSignUp } from '@clerk/clerk-react';
2 import type { SetActive, SignInResource, SignUpResource } from '@clerk/types';
3 import * as Crypto from 'expo-crypto';
4 import Constants from 'expo-constants';
5
6 import { ClerkGoogleOneTapSignIn, isSuccessResponse } from '../google-one-tap';
7 import { errorThrower } from '../utils/errors';
8
9 export type StartGoogleAuthenticationFlowParams = {
10 unsafeMetadata?: SignUpUnsafeMetadata;
11 };
12
13 export type StartGoogleAuthenticationFlowReturnType = {
14 createdSessionId: string | null;
15 setActive?: SetActive;
16 signIn?: SignInResource;
17 signUp?: SignUpResource;
18 };
19
20 /**
21 * Hook for native Google Authentication on Android using Clerk's built-in Google One Tap module.
22 *
23 * This hook provides a simplified way to authenticate users with their Google account
24 * using the native Android Google Sign-In UI with Credential Manager. The authentication
25 * flow automatically handles the ID token exchange with Clerk's backend and manages
26 * the transfer flow between sign-in and sign-up.
27 *
28 * Features:
29 * - Native Google One Tap UI
30 * - Built-in nonce support for replay attack protection
31 * - No additional dependencies required
32 *
33 * @example
34 * ```tsx
35 * import { useSignInWithGoogle } from '@clerk/clerk-expo';
36 * import { Button } from 'react-native';
37 *
38 * function GoogleSignInButton() {
39 * const { startGoogleAuthenticationFlow } = useSignInWithGoogle();
40 *
41 * const onPress = async () => {
42 * try {
43 * const { createdSessionId, setActive } = await startGoogleAuthenticationFlow();
44 *
45 * if (createdSessionId && setActive) {
46 * await setActive({ session: createdSessionId });
47 * }
48 * } catch (err) {
49 * console.error('Google Authentication error:', err);
50 * }
51 * };
52 *
53 * return <Button title="Sign in with Google" onPress={onPress} />;
54 * }
55 * ```
56 *
57 * @platform Android - This is the Android-specific implementation using Credential Manager
58 *
59 * @returns An object containing the `startGoogleAuthenticationFlow` function
60 */
61 export function useSignInWithGoogle() {
62 const { signIn, setActive, isLoaded: isSignInLoaded } = useSignIn();
63 const { signUp, isLoaded: isSignUpLoaded } = useSignUp();
64
65 async function startGoogleAuthenticationFlow(
66 startGoogleAuthenticationFlowParams?: StartGoogleAuthenticationFlowParams,
67 ): Promise<StartGoogleAuthenticationFlowReturnType> {
68 if (!isSignInLoaded || !isSignUpLoaded) {
69 return {
70 createdSessionId: null,
71 signIn,
72 signUp,
73 setActive,
74 };
75 }
76
77 // Get environment variables from expo-constants
78 const webClientId =
79 Constants.expoConfig?.extra?.EXPO_PUBLIC_CLERK_GOOGLE_WEB_CLIENT_ID ||
80 process.env.EXPO_PUBLIC_CLERK_GOOGLE_WEB_CLIENT_ID;
81
82 if (!webClientId) {
83 return errorThrower.throw(
84 'Google Sign-In credentials not found. Please set EXPO_PUBLIC_CLERK_GOOGLE_WEB_CLIENT_ID in your .env file.',
85 );
86 }
87
88 // Configure Google Sign-In
89 ClerkGoogleOneTapSignIn.configure({
90 webClientId,
91 });
92
93 // Generate a cryptographic nonce for replay attack protection
94 const nonce = Crypto.randomUUID();
95
96 // Hash the nonce with SHA-256 (Google requires the hashed version)
97 const hashedNonce = await Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, nonce);
98
99 try {
100 // Try to sign in with Google One Tap (shows saved accounts)
101 // Using presentExplicitSignIn for consistent "Sign in with Google" button behavior
102 const response = await ClerkGoogleOneTapSignIn.presentExplicitSignIn({
103 nonce: hashedNonce,
104 });
105
106 // User cancelled
107 if (!isSuccessResponse(response)) {
108 return {
109 createdSessionId: null,
110 setActive,
111 signIn,
112 signUp,
113 };
114 }
115
116 const { idToken } = response.data;
117
118 try {
119 // Try to sign in with the Google One Tap strategy
120 await signIn.create({
121 strategy: 'google_one_tap',
122 token: idToken,
123 });
124
125 // Check if we need to transfer to SignUp (user doesn't exist yet)
126 const userNeedsToBeCreated = signIn.firstFactorVerification.status === 'transferable';
127
128 if (userNeedsToBeCreated) {
129 // User doesn't exist - create a new SignUp with transfer
130 await signUp.create({
131 transfer: true,
132 unsafeMetadata: startGoogleAuthenticationFlowParams?.unsafeMetadata,
133 });
134
135 return {
136 createdSessionId: signUp.createdSessionId,
137 setActive,
138 signIn,
139 signUp,
140 };
141 }
142
143 // User exists - return the SignIn session
144 return {
145 createdSessionId: signIn.createdSessionId,
146 setActive,
147 signIn,
148 signUp,
149 };
150 } catch (signInError: unknown) {
</shell_result>
Google One-Tap credential manager nonce parameter Android format SHA-256 hash or plain text
<web_search_result>
Short answer: Use a high-entropy random string (plain text), not a SHA‑256 hash. Google’s One‑Tap / Credential Manager APIs accept a nonce string you generate client‑side (and the ID token will contain that nonce claim for server verification). The docs recommend a unique, unpredictable nonce (Base64URL or other URL‑safe random string is common practice for OpenID Connect), but do not require you to send a SHA‑256 hash like Apple does. [1][2][3]
Sources:
- GetGoogleIdOption.Builder / setNonce (Android Credential Manager) — "Sets the nonce to use when generating a Google ID token." [1]
- OpenID Connect / Sign in with Google — nonce is "a random value generated by your app that enables replay protection." (recommend unique, unpredictable value; can be Base64URL, etc.). [2]
- One‑Tap web/html reference — data-nonce is "a random string used by the ID token to prevent replay attacks." [3]
</web_search_result>
Script executed:
# Search for other nonce implementations in the codebase
rg -n "nonce" packages/expo --type ts --type tsx -C 3Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 2
stderr:
unrecognized file type: tsx
</shell_result>
Script executed:
# Look for tests or documentation about nonce handling
fd -e md -e txt packages/expo | xargs grep -l "nonce" 2>/dev/null || echo "No docs found"Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
No docs found
stderr:
[fd error]: The search pattern 'packages/expo' contains a path-separation character ('/') and will not lead to any search results.
If you want to search for all files inside the 'packages/expo' directory, use a match-all pattern:
fd . 'packages/expo'
Instead, if you want your pattern to match the full file path, use:
fd --full-path 'packages/expo'
</shell_result>
Script executed:
# Check the ClerkGoogleOneTapSignIn module to understand its API
fd -name "*google-one-tap*" packages/expoRepository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 2
stderr:
error: Found argument '-n' which wasn't expected, or isn't valid in this context
If you tried to supply '-n' as a value rather than a flag, use '-- -n'
Usage: fd [OPTIONS] [pattern] [path]...
For more information try '--help'
</shell_result>
Script executed:
# Find google-one-tap related files
fd "google-one-tap" packages/expo/Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
packages/expo/src/google-one-tap/
</shell_result>
Script executed:
# Search for nonce usage in the codebase with proper flags
rg "nonce" packages/expo/ -A 3 -B 3Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
packages/expo/ios/ClerkGoogleSignInModule.swift- withPresenting: presentingVC,
packages/expo/ios/ClerkGoogleSignInModule.swift- hint: hint,
packages/expo/ios/ClerkGoogleSignInModule.swift- additionalScopes: nil,
packages/expo/ios/ClerkGoogleSignInModule.swift: nonce: params?.nonce
packages/expo/ios/ClerkGoogleSignInModule.swift- ) { result, error in
packages/expo/ios/ClerkGoogleSignInModule.swift- self.handleSignInResult(result: result, error: error, promise: promise)
packages/expo/ios/ClerkGoogleSignInModule.swift- }
--
packages/expo/ios/ClerkGoogleSignInModule.swift- withPresenting: presentingVC,
packages/expo/ios/ClerkGoogleSignInModule.swift- hint: nil,
packages/expo/ios/ClerkGoogleSignInModule.swift- additionalScopes: nil,
packages/expo/ios/ClerkGoogleSignInModule.swift: nonce: params?.nonce
packages/expo/ios/ClerkGoogleSignInModule.swift- ) { result, error in
packages/expo/ios/ClerkGoogleSignInModule.swift- self.handleSignInResult(result: result, error: error, promise: promise)
packages/expo/ios/ClerkGoogleSignInModule.swift- }
--
packages/expo/ios/ClerkGoogleSignInModule.swift- withPresenting: presentingVC,
packages/expo/ios/ClerkGoogleSignInModule.swift- hint: nil,
packages/expo/ios/ClerkGoogleSignInModule.swift- additionalScopes: nil,
packages/expo/ios/ClerkGoogleSignInModule.swift: nonce: params?.nonce
packages/expo/ios/ClerkGoogleSignInModule.swift- ) { result, error in
packages/expo/ios/ClerkGoogleSignInModule.swift- self.handleSignInResult(result: result, error: error, promise: promise)
packages/expo/ios/ClerkGoogleSignInModule.swift- }
--
packages/expo/ios/ClerkGoogleSignInModule.swift-
packages/expo/ios/ClerkGoogleSignInModule.swift-struct SignInParams: Record {
packages/expo/ios/ClerkGoogleSignInModule.swift- @Field
packages/expo/ios/ClerkGoogleSignInModule.swift: var nonce: String?
packages/expo/ios/ClerkGoogleSignInModule.swift-
packages/expo/ios/ClerkGoogleSignInModule.swift- @Field
packages/expo/ios/ClerkGoogleSignInModule.swift- var filterByAuthorizedAccounts: Bool?
--
packages/expo/ios/ClerkGoogleSignInModule.swift-
packages/expo/ios/ClerkGoogleSignInModule.swift-struct CreateAccountParams: Record {
packages/expo/ios/ClerkGoogleSignInModule.swift- @Field
packages/expo/ios/ClerkGoogleSignInModule.swift: var nonce: String?
packages/expo/ios/ClerkGoogleSignInModule.swift-}
packages/expo/ios/ClerkGoogleSignInModule.swift-
packages/expo/ios/ClerkGoogleSignInModule.swift-struct ExplicitSignInParams: Record {
packages/expo/ios/ClerkGoogleSignInModule.swift- @Field
packages/expo/ios/ClerkGoogleSignInModule.swift: var nonce: String?
packages/expo/ios/ClerkGoogleSignInModule.swift-}
packages/expo/ios/ClerkGoogleSignInModule.swift-
packages/expo/ios/ClerkGoogleSignInModule.swift-// MARK: - Exceptions
--
packages/expo/ios/ClerkGoogleSignIn.podspec- s.name = 'ClerkGoogleSignIn'
packages/expo/ios/ClerkGoogleSignIn.podspec- s.version = package['version']
packages/expo/ios/ClerkGoogleSignIn.podspec- s.summary = 'Native Google Sign-In module for Clerk Expo'
packages/expo/ios/ClerkGoogleSignIn.podspec: s.description = 'Native Google Sign-In functionality using Google Sign-In SDK with nonce support for Clerk authentication'
packages/expo/ios/ClerkGoogleSignIn.podspec- s.license = package['license']
packages/expo/ios/ClerkGoogleSignIn.podspec- s.author = package['author']
packages/expo/ios/ClerkGoogleSignIn.podspec- s.homepage = package['homepage']
--
packages/expo/android/build.gradle- // Expo modules core
packages/expo/android/build.gradle- implementation project(':expo-modules-core')
packages/expo/android/build.gradle-
packages/expo/android/build.gradle: // Credential Manager for Google Sign-In with nonce support
packages/expo/android/build.gradle- implementation "androidx.credentials:credentials:1.3.0"
packages/expo/android/build.gradle- implementation "androidx.credentials:credentials-play-services-auth:1.3.0"
packages/expo/android/build.gradle- implementation "com.google.android.libraries.identity.googleid:googleid:1.1.1"
--
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt-// Sign-in parameters
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt-class SignInParams : Record {
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt- @Field
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt: val nonce: String? = null
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt-
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt- @Field
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt- val filterByAuthorizedAccounts: Boolean? = null
--
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt-// Create account parameters
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt-class CreateAccountParams : Record {
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt- @Field
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt: val nonce: String? = null
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt-}
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt-
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt-// Explicit sign-in parameters
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt-class ExplicitSignInParams : Record {
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt- @Field
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt: val nonce: String? = null
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt-}
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt-
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt-// Custom exceptions
--
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt- .setServerClientId(clientId)
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt- .setAutoSelectEnabled(autoSelectEnabled)
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt- .apply {
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt: params?.nonce?.let { setNonce(it) }
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt- }
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt- .build()
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt-
--
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt- .setFilterByAuthorizedAccounts(false) // Show all accounts for creation
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt- .setServerClientId(clientId)
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt- .apply {
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt: params?.nonce?.let { setNonce(it) }
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt- }
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt- .build()
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt-
--
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt- try {
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt- val signInWithGoogleOption = GetSignInWithGoogleOption.Builder(clientId)
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt- .apply {
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt: params?.nonce?.let { setNonce(it) }
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt- hostedDomain?.let { setHostedDomainFilter(it) }
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt- }
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt- .build()
--
packages/expo/src/hooks/useSignInWithGoogle.ios.ts- *
packages/expo/src/hooks/useSignInWithGoogle.ios.ts- * Features:
packages/expo/src/hooks/useSignInWithGoogle.ios.ts- * - Native Google Sign-In UI
packages/expo/src/hooks/useSignInWithGoogle.ios.ts: * - Built-in nonce support for replay attack protection
packages/expo/src/hooks/useSignInWithGoogle.ios.ts- * - No additional dependencies required
packages/expo/src/hooks/useSignInWithGoogle.ios.ts- *
packages/expo/src/hooks/useSignInWithGoogle.ios.ts- * @example
--
packages/expo/src/hooks/useSignInWithGoogle.ios.ts- iosClientId,
packages/expo/src/hooks/useSignInWithGoogle.ios.ts- });
packages/expo/src/hooks/useSignInWithGoogle.ios.ts-
packages/expo/src/hooks/useSignInWithGoogle.ios.ts: // Generate a cryptographic nonce for replay attack protection
packages/expo/src/hooks/useSignInWithGoogle.ios.ts: const nonce = Crypto.randomUUID();
packages/expo/src/hooks/useSignInWithGoogle.ios.ts-
packages/expo/src/hooks/useSignInWithGoogle.ios.ts: // Hash the nonce with SHA-256 (Google requires the hashed version)
packages/expo/src/hooks/useSignInWithGoogle.ios.ts: const hashedNonce = await Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, nonce);
packages/expo/src/hooks/useSignInWithGoogle.ios.ts-
packages/expo/src/hooks/useSignInWithGoogle.ios.ts- try {
packages/expo/src/hooks/useSignInWithGoogle.ios.ts: // Present Google Sign-In UI with nonce
packages/expo/src/hooks/useSignInWithGoogle.ios.ts- const response = await ClerkGoogleOneTapSignIn.presentExplicitSignIn({
packages/expo/src/hooks/useSignInWithGoogle.ios.ts: nonce: hashedNonce,
packages/expo/src/hooks/useSignInWithGoogle.ios.ts- });
packages/expo/src/hooks/useSignInWithGoogle.ios.ts-
packages/expo/src/hooks/useSignInWithGoogle.ios.ts- // User cancelled
--
packages/expo/src/hooks/useSignInWithApple.ios.ts- }
packages/expo/src/hooks/useSignInWithApple.ios.ts-
packages/expo/src/hooks/useSignInWithApple.ios.ts- try {
packages/expo/src/hooks/useSignInWithApple.ios.ts: // Generate a cryptographically secure nonce for the Apple Sign-In request (required by Clerk)
packages/expo/src/hooks/useSignInWithApple.ios.ts: const nonce = Crypto.randomUUID();
packages/expo/src/hooks/useSignInWithApple.ios.ts-
packages/expo/src/hooks/useSignInWithApple.ios.ts- // Request Apple authentication with requested scopes
packages/expo/src/hooks/useSignInWithApple.ios.ts- const credential = await AppleAuthentication.signInAsync({
--
packages/expo/src/hooks/useSignInWithApple.ios.ts- AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
packages/expo/src/hooks/useSignInWithApple.ios.ts- AppleAuthentication.AppleAuthenticationScope.EMAIL,
packages/expo/src/hooks/useSignInWithApple.ios.ts- ],
packages/expo/src/hooks/useSignInWithApple.ios.ts: nonce,
packages/expo/src/hooks/useSignInWithApple.ios.ts- });
packages/expo/src/hooks/useSignInWithApple.ios.ts-
packages/expo/src/hooks/useSignInWithApple.ios.ts- // Extract the identity token from the credential
--
packages/expo/src/hooks/useSignInWithGoogle.android.ts- *
packages/expo/src/hooks/useSignInWithGoogle.android.ts- * Features:
packages/expo/src/hooks/useSignInWithGoogle.android.ts- * - Native Google One Tap UI
packages/expo/src/hooks/useSignInWithGoogle.android.ts: * - Built-in nonce support for replay attack protection
packages/expo/src/hooks/useSignInWithGoogle.android.ts- * - No additional dependencies required
packages/expo/src/hooks/useSignInWithGoogle.android.ts- *
packages/expo/src/hooks/useSignInWithGoogle.android.ts- * @example
--
packages/expo/src/hooks/useSignInWithGoogle.android.ts- webClientId,
packages/expo/src/hooks/useSignInWithGoogle.android.ts- });
packages/expo/src/hooks/useSignInWithGoogle.android.ts-
packages/expo/src/hooks/useSignInWithGoogle.android.ts: // Generate a cryptographic nonce for replay attack protection
packages/expo/src/hooks/useSignInWithGoogle.android.ts: const nonce = Crypto.randomUUID();
packages/expo/src/hooks/useSignInWithGoogle.android.ts-
packages/expo/src/hooks/useSignInWithGoogle.android.ts: // Hash the nonce with SHA-256 (Google requires the hashed version)
packages/expo/src/hooks/useSignInWithGoogle.android.ts: const hashedNonce = await Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, nonce);
packages/expo/src/hooks/useSignInWithGoogle.android.ts-
packages/expo/src/hooks/useSignInWithGoogle.android.ts- try {
packages/expo/src/hooks/useSignInWithGoogle.android.ts- // Try to sign in with Google One Tap (shows saved accounts)
packages/expo/src/hooks/useSignInWithGoogle.android.ts- // Using presentExplicitSignIn for consistent "Sign in with Google" button behavior
packages/expo/src/hooks/useSignInWithGoogle.android.ts- const response = await ClerkGoogleOneTapSignIn.presentExplicitSignIn({
packages/expo/src/hooks/useSignInWithGoogle.android.ts: nonce: hashedNonce,
packages/expo/src/hooks/useSignInWithGoogle.android.ts- });
packages/expo/src/hooks/useSignInWithGoogle.android.ts-
packages/expo/src/hooks/useSignInWithGoogle.android.ts- // User cancelled
--
packages/expo/src/cache/dummy-data/client-resource.ts- id: '',
packages/expo/src/cache/dummy-data/client-resource.ts- status: null,
packages/expo/src/cache/dummy-data/client-resource.ts- strategy: null,
packages/expo/src/cache/dummy-data/client-resource.ts: nonce: null,
packages/expo/src/cache/dummy-data/client-resource.ts- message: null,
packages/expo/src/cache/dummy-data/client-resource.ts- external_verification_redirect_url: null,
packages/expo/src/cache/dummy-data/client-resource.ts- attempts: null,
--
packages/expo/src/cache/dummy-data/client-resource.ts- id: '',
packages/expo/src/cache/dummy-data/client-resource.ts- status: null,
packages/expo/src/cache/dummy-data/client-resource.ts- strategy: null,
packages/expo/src/cache/dummy-data/client-resource.ts: nonce: null,
packages/expo/src/cache/dummy-data/client-resource.ts- message: null,
packages/expo/src/cache/dummy-data/client-resource.ts- external_verification_redirect_url: null,
packages/expo/src/cache/dummy-data/client-resource.ts- attempts: null,
--
packages/expo/src/cache/dummy-data/client-resource.ts- id: '',
packages/expo/src/cache/dummy-data/client-resource.ts- status: null,
packages/expo/src/cache/dummy-data/client-resource.ts- strategy: null,
packages/expo/src/cache/dummy-data/client-resource.ts: nonce: null,
packages/expo/src/cache/dummy-data/client-resource.ts- message: null,
packages/expo/src/cache/dummy-data/client-resource.ts- external_verification_redirect_url: null,
packages/expo/src/cache/dummy-data/client-resource.ts- attempts: null,
--
packages/expo/src/cache/dummy-data/client-resource.ts- id: '',
packages/expo/src/cache/dummy-data/client-resource.ts- status: null,
packages/expo/src/cache/dummy-data/client-resource.ts- strategy: null,
packages/expo/src/cache/dummy-data/client-resource.ts: nonce: null,
packages/expo/src/cache/dummy-data/client-resource.ts- message: null,
packages/expo/src/cache/dummy-data/client-resource.ts- external_verification_redirect_url: null,
packages/expo/src/cache/dummy-data/client-resource.ts- attempts: null,
--
packages/expo/src/cache/dummy-data/client-resource.ts- id: '',
packages/expo/src/cache/dummy-data/client-resource.ts- status: null,
packages/expo/src/cache/dummy-data/client-resource.ts- strategy: null,
packages/expo/src/cache/dummy-data/client-resource.ts: nonce: null,
packages/expo/src/cache/dummy-data/client-resource.ts- message: null,
packages/expo/src/cache/dummy-data/client-resource.ts- external_verification_redirect_url: null,
packages/expo/src/cache/dummy-data/client-resource.ts- attempts: null,
--
packages/expo/src/cache/dummy-data/client-resource.ts- id: '',
packages/expo/src/cache/dummy-data/client-resource.ts- status: null,
packages/expo/src/cache/dummy-data/client-resource.ts- strategy: null,
packages/expo/src/cache/dummy-data/client-resource.ts: nonce: null,
packages/expo/src/cache/dummy-data/client-resource.ts- message: null,
packages/expo/src/cache/dummy-data/client-resource.ts- external_verification_redirect_url: null,
packages/expo/src/cache/dummy-data/client-resource.ts- attempts: null,
--
packages/expo/src/hooks/useSSO.ts- /**
packages/expo/src/hooks/useSSO.ts- * Creates a redirect URL based on the application platform
packages/expo/src/hooks/useSSO.ts- * It must be whitelisted, either via Clerk Dashboard, or BAPI, in order
packages/expo/src/hooks/useSSO.ts: * to include the `rotating_token_nonce` on SSO callback
packages/expo/src/hooks/useSSO.ts- * @ref https://clerk.com/docs/reference/backend-api/tag/Redirect-URLs#operation/CreateRedirectURL
packages/expo/src/hooks/useSSO.ts- */
packages/expo/src/hooks/useSSO.ts- const redirectUrl =
--
packages/expo/src/hooks/useSSO.ts- }
packages/expo/src/hooks/useSSO.ts-
packages/expo/src/hooks/useSSO.ts- const params = new URL(authSessionResult.url).searchParams;
packages/expo/src/hooks/useSSO.ts: const rotatingTokenNonce = params.get('rotating_token_nonce') ?? '';
packages/expo/src/hooks/useSSO.ts- await signIn.reload({ rotatingTokenNonce });
packages/expo/src/hooks/useSSO.ts-
packages/expo/src/hooks/useSSO.ts- const userNeedsToBeCreated = signIn.firstFactorVerification.status === 'transferable';
--
packages/expo/src/hooks/useOAuth.ts-
packages/expo/src/hooks/useOAuth.ts- const params = new URL(url).searchParams;
packages/expo/src/hooks/useOAuth.ts-
packages/expo/src/hooks/useOAuth.ts: const rotatingTokenNonce = params.get('rotating_token_nonce') || '';
packages/expo/src/hooks/useOAuth.ts- await signIn.reload({ rotatingTokenNonce });
packages/expo/src/hooks/useOAuth.ts-
packages/expo/src/hooks/useOAuth.ts- const { status, firstFactorVerification } = signIn;
--
packages/expo/src/hooks/__tests__/useSignInWithApple.test.ts- });
packages/expo/src/hooks/__tests__/useSignInWithApple.test.ts-
packages/expo/src/hooks/__tests__/useSignInWithApple.test.ts- mocks.isAvailableAsync.mockResolvedValue(true);
packages/expo/src/hooks/__tests__/useSignInWithApple.test.ts: mocks.randomUUID.mockReturnValue('test-nonce-uuid');
packages/expo/src/hooks/__tests__/useSignInWithApple.test.ts- });
packages/expo/src/hooks/__tests__/useSignInWithApple.test.ts-
packages/expo/src/hooks/__tests__/useSignInWithApple.test.ts- afterEach(() => {
--
packages/expo/src/hooks/__tests__/useSignInWithApple.test.ts- expect(mocks.signInAsync).toHaveBeenCalledWith(
packages/expo/src/hooks/__tests__/useSignInWithApple.test.ts- expect.objectContaining({
packages/expo/src/hooks/__tests__/useSignInWithApple.test.ts- requestedScopes: expect.any(Array),
packages/expo/src/hooks/__tests__/useSignInWithApple.test.ts: nonce: 'test-nonce-uuid',
packages/expo/src/hooks/__tests__/useSignInWithApple.test.ts- }),
packages/expo/src/hooks/__tests__/useSignInWithApple.test.ts- );
packages/expo/src/hooks/__tests__/useSignInWithApple.test.ts- expect(mockSignIn.create).toHaveBeenCalledWith({
--
packages/expo/src/google-one-tap/types.ts-export type SignInParams = {
packages/expo/src/google-one-tap/types.ts- /**
packages/expo/src/google-one-tap/types.ts- * A cryptographically random string used to mitigate replay attacks.
packages/expo/src/google-one-tap/types.ts: * The nonce will be included in the ID token.
packages/expo/src/google-one-tap/types.ts- */
packages/expo/src/google-one-tap/types.ts: nonce?: string;
packages/expo/src/google-one-tap/types.ts-
packages/expo/src/google-one-tap/types.ts- /**
packages/expo/src/google-one-tap/types.ts- * Whether to filter credentials to only show accounts that have been
--
packages/expo/src/google-one-tap/types.ts-export type CreateAccountParams = {
packages/expo/src/google-one-tap/types.ts- /**
packages/expo/src/google-one-tap/types.ts- * A cryptographically random string used to mitigate replay attacks.
packages/expo/src/google-one-tap/types.ts: * The nonce will be included in the ID token.
packages/expo/src/google-one-tap/types.ts- */
packages/expo/src/google-one-tap/types.ts: nonce?: string;
packages/expo/src/google-one-tap/types.ts-};
packages/expo/src/google-one-tap/types.ts-
packages/expo/src/google-one-tap/types.ts-/**
--
packages/expo/src/google-one-tap/types.ts-export type ExplicitSignInParams = {
packages/expo/src/google-one-tap/types.ts- /**
packages/expo/src/google-one-tap/types.ts- * A cryptographically random string used to mitigate replay attacks.
packages/expo/src/google-one-tap/types.ts: * The nonce will be included in the ID token.
packages/expo/src/google-one-tap/types.ts- */
packages/expo/src/google-one-tap/types.ts: nonce?: string;
packages/expo/src/google-one-tap/types.ts-};
packages/expo/src/google-one-tap/types.ts-
packages/expo/src/google-one-tap/types.ts-/**
--
packages/expo/src/google-one-tap/types.ts- type: 'success';
packages/expo/src/google-one-tap/types.ts- data: {
packages/expo/src/google-one-tap/types.ts- /**
packages/expo/src/google-one-tap/types.ts: * The Google ID token containing user information and nonce.
packages/expo/src/google-one-tap/types.ts- */
packages/expo/src/google-one-tap/types.ts- idToken: string;
packages/expo/src/google-one-tap/types.ts-
--
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- * Clerk's Google One Tap Sign-In module for Android.
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- *
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- * This module provides native Google Sign-In functionality using Google's
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts: * Credential Manager API with full nonce support for replay attack protection.
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- *
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- * @example
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- * ```typescript
--
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- * webClientId: 'YOUR_WEB_CLIENT_ID',
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- * });
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- *
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts: * // Sign in with nonce
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts: * const nonce = generateNonce();
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts: * const response = await ClerkGoogleOneTapSignIn.signIn({ nonce });
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- *
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- * if (response.type === 'success') {
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- * const { idToken } = response.data;
--
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- * or return a "noSavedCredentialFound" response if there are none.
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- *
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- * @param params - Sign-in parameters
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts: * @param params.nonce - Cryptographic nonce for replay protection
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- * @param params.filterByAuthorizedAccounts - Only show previously authorized accounts (default: true)
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- *
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- * @returns Promise resolving to OneTapResponse
--
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- * not just previously authorized ones.
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- *
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- * @param params - Create account parameters
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts: * @param params.nonce - Cryptographic nonce for replay protection
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- *
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- * @returns Promise resolving to OneTapResponse
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- */
--
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- * "Sign in with Google" button.
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- *
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- * @param params - Explicit sign-in parameters
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts: * @param params.nonce - Cryptographic nonce for replay protection
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- *
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- * @returns Promise resolving to OneTapResponse
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts- */
</shell_result>
Script executed:
# Look at the google-one-tap implementation
find packages/expo -name "*google*" -type f | head -20Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
</shell_result>
Script executed:
# Check google-one-tap types definition
cat packages/expo/src/google-one-tap/types.tsRepository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
/**
* Configuration parameters for Google One Tap Sign-In.
*/
export type ConfigureParams = {
/**
* The web client ID from Google Cloud Console.
* This is required for Google Sign-In to work.
* On iOS, this is used as the serverClientID for token audience.
*/
webClientId: string;
/**
* The iOS client ID from Google Cloud Console.
* This is only used on iOS for the OAuth flow.
* If not provided, webClientId will be used.
* @platform iOS
*/
iosClientId?: string;
/**
* Optional hosted domain to restrict sign-in to a specific domain.
*/
hostedDomain?: string;
/**
* Whether to enable auto-select for returning users.
* When true, if only one credential is available, it will be automatically selected.
* @default false
*/
autoSelectEnabled?: boolean;
};
/**
* Parameters for the signIn method.
*/
export type SignInParams = {
/**
* A cryptographically random string used to mitigate replay attacks.
* The nonce will be included in the ID token.
*/
nonce?: string;
/**
* Whether to filter credentials to only show accounts that have been
* previously authorized for this app.
* @default true
*/
filterByAuthorizedAccounts?: boolean;
};
/**
* Parameters for the createAccount method.
*/
export type CreateAccountParams = {
/**
* A cryptographically random string used to mitigate replay attacks.
* The nonce will be included in the ID token.
*/
nonce?: string;
};
/**
* Parameters for the presentExplicitSignIn method.
*/
export type ExplicitSignInParams = {
/**
* A cryptographically random string used to mitigate replay attacks.
* The nonce will be included in the ID token.
*/
nonce?: string;
};
/**
* User information returned from Google Sign-In.
*/
export type GoogleUser = {
/**
* The user's Google ID (also their email).
*/
id: string;
/**
* The user's email address.
*/
email: string;
/**
* The user's full display name.
*/
name: string | null;
/**
* The user's given (first) name.
*/
givenName: string | null;
/**
* The user's family (last) name.
*/
familyName: string | null;
/**
* URL to the user's profile picture.
*/
photo: string | null;
};
/**
* Successful sign-in response.
*/
export type OneTapSuccessResponse = {
type: 'success';
data: {
/**
* The Google ID token containing user information and nonce.
*/
idToken: string;
/**
* The user's information.
*/
user: GoogleUser;
};
};
/**
* Response when the user cancels the sign-in flow.
*/
export type CancelledResponse = {
type: 'cancelled';
data: null;
};
/**
* Response when no saved credential is found.
*/
export type NoSavedCredentialFound = {
type: 'noSavedCredentialFound';
data: null;
};
/**
* Union type for all possible One Tap responses.
*/
export type OneTapResponse = OneTapSuccessResponse | CancelledResponse | NoSavedCredentialFound;
/**
* Error codes that can be thrown by the Google Sign-In module.
*/
export type GoogleSignInErrorCode =
| 'SIGN_IN_CANCELLED'
| 'NO_SAVED_CREDENTIAL_FOUND'
| 'NOT_CONFIGURED'
| 'GOOGLE_SIGN_IN_ERROR';
/**
* Error thrown by the Google Sign-In module.
*/
export interface GoogleSignInError extends Error {
code: GoogleSignInErrorCode;
}
</shell_result>
Script executed:
# Look for signIn.create() usage patterns with google_one_tap
rg "google_one_tap" packages/expo/src --type ts -B 2 -A 2Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
packages/expo/src/resource-cache/__tests__/dummy-test-data.ts- after_leave_organization_url: '',
packages/expo/src/resource-cache/__tests__/dummy-test-data.ts- after_create_organization_url: '',
packages/expo/src/resource-cache/__tests__/dummy-test-data.ts: google_one_tap_client_id: null,
packages/expo/src/resource-cache/__tests__/dummy-test-data.ts- show_devmode_warning: true,
packages/expo/src/resource-cache/__tests__/dummy-test-data.ts- terms_url: null,
--
packages/expo/src/hooks/useSignInWithGoogle.android.ts- // Try to sign in with the Google One Tap strategy
packages/expo/src/hooks/useSignInWithGoogle.android.ts- await signIn.create({
packages/expo/src/hooks/useSignInWithGoogle.android.ts: strategy: 'google_one_tap',
packages/expo/src/hooks/useSignInWithGoogle.android.ts- token: idToken,
packages/expo/src/hooks/useSignInWithGoogle.android.ts- });
--
packages/expo/src/hooks/useSignInWithGoogle.android.ts- // User doesn't exist - create a new SignUp with the token
packages/expo/src/hooks/useSignInWithGoogle.android.ts- await signUp.create({
packages/expo/src/hooks/useSignInWithGoogle.android.ts: strategy: 'google_one_tap',
packages/expo/src/hooks/useSignInWithGoogle.android.ts- token: idToken,
packages/expo/src/hooks/useSignInWithGoogle.android.ts- unsafeMetadata: startGoogleAuthenticationFlowParams?.unsafeMetadata,
--
packages/expo/src/hooks/useSignInWithGoogle.ios.ts- // Try to sign in with the Google One Tap strategy
packages/expo/src/hooks/useSignInWithGoogle.ios.ts- await signIn.create({
packages/expo/src/hooks/useSignInWithGoogle.ios.ts: strategy: 'google_one_tap',
packages/expo/src/hooks/useSignInWithGoogle.ios.ts- token: idToken,
packages/expo/src/hooks/useSignInWithGoogle.ios.ts- });
--
packages/expo/src/hooks/useSignInWithGoogle.ios.ts- // User doesn't exist - create a new SignUp with the token
packages/expo/src/hooks/useSignInWithGoogle.ios.ts- await signUp.create({
packages/expo/src/hooks/useSignInWithGoogle.ios.ts: strategy: 'google_one_tap',
packages/expo/src/hooks/useSignInWithGoogle.ios.ts- token: idToken,
packages/expo/src/hooks/useSignInWithGoogle.ios.ts- unsafeMetadata: startGoogleAuthenticationFlowParams?.unsafeMetadata,
--
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts- expect(mocks.GoogleSignin.signIn).toHaveBeenCalled();
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts- expect(mockSignIn.create).toHaveBeenCalledWith({
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts: strategy: 'google_one_tap',
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts- token: mockIdToken,
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts- });
--
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts-
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts- expect(mockSignIn.create).toHaveBeenCalledWith({
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts: strategy: 'google_one_tap',
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts- token: mockIdToken,
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts- });
--
packages/expo/src/cache/dummy-data/environment-resource.ts- after_leave_organization_url: '',
packages/expo/src/cache/dummy-data/environment-resource.ts- after_create_organization_url: '',
packages/expo/src/cache/dummy-data/environment-resource.ts: google_one_tap_client_id: null,
packages/expo/src/cache/dummy-data/environment-resource.ts- show_devmode_warning: true,
packages/expo/src/cache/dummy-data/environment-resource.ts- terms_url: null,
</shell_result>
Script executed:
# Check if there are any tests showing correct nonce usage
find packages/expo -name "*.test.ts" -o -name "*.test.tsx" | head -10Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
packages/expo/src/hooks/__tests__/useSignInWithApple.test.ts
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts
packages/expo/src/resource-cache/__tests__/secure-store.test.ts
</shell_result>
Pass the original nonce to Google, not a SHA-256 hash. Google's Credential Manager API expects plain text.
Google One-Tap requires the plain nonce value, not a hashed version. Per Google's API documentation and the Android native implementation in this repo, setNonce() accepts the cryptographic random string directly. Google includes this plain nonce in the ID token's claims, which Clerk's backend then verifies. The hashing is unnecessary and breaks the nonce verification flow.
// Generate a cryptographic nonce for replay attack protection
const nonce = Crypto.randomUUID();
// Pass the plain nonce directly to Google (do not hash)
const response = await ClerkGoogleOneTapSignIn.presentExplicitSignIn({
nonce,
});
Related: The same bug exists in useSignInWithGoogle.ios.ts at lines 96–97. The code comment claiming "Google requires the hashed version" is incorrect; compare with the plain nonce usage in useSignInWithApple.ios.ts.
🤖 Prompt for AI Agents
In packages/expo/src/hooks/useSignInWithGoogle.android.ts around lines 93 to 97,
the code generates a nonce then hashes it and passes the hashed value to Google;
Google expects the plain nonce (not SHA-256) so this breaks verification.
Replace usage of the hashedNonce with the original nonce when calling
ClerkGoogleOneTapSignIn.presentExplicitSignIn (pass nonce directly) and remove
the misleading comment about Google requiring a hashed version; apply the same
fix in packages/expo/src/hooks/useSignInWithGoogle.ios.ts at the analogous lines
(96–97).
| import type { SetActive, SignInResource, SignUpResource } from '@clerk/types'; | ||
|
|
||
| import { errorThrower } from '../utils/errors'; | ||
|
|
||
| export type StartGoogleAuthenticationFlowParams = { | ||
| unsafeMetadata?: SignUpUnsafeMetadata; | ||
| }; | ||
|
|
||
| export type StartGoogleAuthenticationFlowReturnType = { | ||
| createdSessionId: string | null; | ||
| setActive?: SetActive; | ||
| signIn?: SignInResource; | ||
| signUp?: SignUpResource; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Script executed:
#!/bin/bash
# First, let's look at the full file content to see if SignUpUnsafeMetadata is defined elsewhere
cat -n packages/expo/src/hooks/useSignInWithGoogle.tsRepository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
1 import type { SetActive, SignInResource, SignUpResource } from '@clerk/types';
2
3 import { errorThrower } from '../utils/errors';
4
5 export type StartGoogleAuthenticationFlowParams = {
6 unsafeMetadata?: SignUpUnsafeMetadata;
7 };
8
9 export type StartGoogleAuthenticationFlowReturnType = {
10 createdSessionId: string | null;
11 setActive?: SetActive;
12 signIn?: SignInResource;
13 signUp?: SignUpResource;
14 };
15
16 /**
17 * Stub for Google Authentication hook on unsupported platforms.
18 *
19 * Native Google Authentication using @react-native-google-signin/google-signin is only available on iOS and Android.
20 * For web platforms, use the OAuth-based Google Sign-In flow instead via useSSO.
21 *
22 * @example
23 * ```tsx
24 * import { useSSO } from '@clerk/clerk-expo';
25 * import { Button } from 'react-native';
26 *
27 * function GoogleSignInButton() {
28 * const { startSSOFlow } = useSSO();
29 *
30 * const onPress = async () => {
31 * try {
32 * const { createdSessionId, setActive } = await startSSOFlow({
33 * strategy: 'oauth_google'
34 * });
35 *
36 * if (createdSessionId && setActive) {
37 * await setActive({ session: createdSessionId });
38 * }
39 * } catch (err) {
40 * console.error('Google Authentication error:', err);
41 * }
42 * };
43 *
44 * return <Button title="Sign in with Google" onPress={onPress} />;
45 * }
46 * ```
47 *
48 * @platform iOS, Android - This hook only works on iOS and Android. On other platforms, it will throw an error.
49 *
50 * @returns An object containing the `startGoogleAuthenticationFlow` function that throws an error
51 */
52 export function useSignInWithGoogle(): {
53 startGoogleAuthenticationFlow: (
54 startGoogleAuthenticationFlowParams?: StartGoogleAuthenticationFlowParams,
55 ) => Promise<StartGoogleAuthenticationFlowReturnType>;
56 } {
57 function startGoogleAuthenticationFlow(
58 _startGoogleAuthenticationFlowParams?: StartGoogleAuthenticationFlowParams,
59 ): Promise<StartGoogleAuthenticationFlowReturnType> {
60 return errorThrower.throw(
61 'Google Authentication via @react-native-google-signin/google-signin is only available on iOS and Android. ' +
62 'For web and other platforms, please use the OAuth-based flow with useSSO and strategy: "oauth_google".',
63 );
64 }
65
66 return {
67 startGoogleAuthenticationFlow,
68 };
69 }
</shell_result>
Script executed:
# Search for SignUpUnsafeMetadata definition anywhere in the codebase
rg -n 'SignUpUnsafeMetadata' packages/expo/src/hooks/Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
packages/expo/src/hooks/useSignInWithGoogle.ts:6: unsafeMetadata?: SignUpUnsafeMetadata;
packages/expo/src/hooks/useSignInWithGoogle.android.ts:10: unsafeMetadata?: SignUpUnsafeMetadata;
packages/expo/src/hooks/useSignInWithGoogle.ios.ts:9:type SignUpUnsafeMetadata = Record<string, any>;
packages/expo/src/hooks/useSignInWithGoogle.ios.ts:12: unsafeMetadata?: SignUpUnsafeMetadata;
packages/expo/src/hooks/useSignInWithApple.ios.ts:9: unsafeMetadata?: SignUpUnsafeMetadata;
packages/expo/src/hooks/useSignInWithApple.ts:6: unsafeMetadata?: SignUpUnsafeMetadata;
packages/expo/src/hooks/useSSO.ts:10: unsafeMetadata?: SignUpUnsafeMetadata;
packages/expo/src/hooks/useOAuth.ts:11: unsafeMetadata?: SignUpUnsafeMetadata;
packages/expo/src/hooks/useOAuth.ts:16: unsafeMetadata?: SignUpUnsafeMetadata;
</shell_result>
Script executed:
# Check if SignUpUnsafeMetadata is exported from @clerk/types or @clerk/shared/types
rg -n 'SignUpUnsafeMetadata' . --type ts --type tsxRepository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 2
stderr:
unrecognized file type: tsx
</shell_result>
Script executed:
# Check the iOS implementation to see the pattern
fd -t f 'useSignInWithGoogle' packages/expo/src/hooks/ | head -20Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts
packages/expo/src/hooks/useSignInWithGoogle.android.ts
packages/expo/src/hooks/useSignInWithGoogle.ios.ts
packages/expo/src/hooks/useSignInWithGoogle.ts
</shell_result>
Script executed:
# Verify the deprecation guideline - search for references to @clerk/types vs @clerk/shared/types
rg -n '@clerk/types|@clerk/shared/types' packages/expo/src/hooks/ -A 2 -B 2Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
packages/expo/src/hooks/useSignInWithGoogle.ts:1:import type { SetActive, SignInResource, SignUpResource } from '@clerk/types';
packages/expo/src/hooks/useSignInWithGoogle.ts-2-
packages/expo/src/hooks/useSignInWithGoogle.ts-3-import { errorThrower } from '../utils/errors';
--
packages/expo/src/hooks/useSignInWithGoogle.android.ts-1-import { useSignIn, useSignUp } from '@clerk/clerk-react';
packages/expo/src/hooks/useSignInWithGoogle.android.ts:2:import type { SetActive, SignInResource, SignUpResource } from '@clerk/types';
packages/expo/src/hooks/useSignInWithGoogle.android.ts-3-import * as Crypto from 'expo-crypto';
packages/expo/src/hooks/useSignInWithGoogle.android.ts-4-import Constants from 'expo-constants';
--
packages/expo/src/hooks/useSignInWithApple.ios.ts-1-import { useSignIn, useSignUp } from '@clerk/clerk-react';
packages/expo/src/hooks/useSignInWithApple.ios.ts:2:import type { SetActive, SignInResource, SignUpResource } from '@clerk/types';
packages/expo/src/hooks/useSignInWithApple.ios.ts-3-import * as AppleAuthentication from 'expo-apple-authentication';
packages/expo/src/hooks/useSignInWithApple.ios.ts-4-import * as Crypto from 'expo-crypto';
--
packages/expo/src/hooks/useSignInWithApple.ts:1:import type { SetActive, SignInResource, SignUpResource } from '@clerk/types';
packages/expo/src/hooks/useSignInWithApple.ts-2-
packages/expo/src/hooks/useSignInWithApple.ts-3-import { errorThrower } from '../utils/errors';
--
packages/expo/src/hooks/useSSO.ts-1-import { useSignIn, useSignUp } from '@clerk/clerk-react';
packages/expo/src/hooks/useSSO.ts:2:import type { EnterpriseSSOStrategy, OAuthStrategy, SetActive, SignInResource, SignUpResource } from '@clerk/types';
packages/expo/src/hooks/useSSO.ts-3-import * as AuthSession from 'expo-auth-session';
packages/expo/src/hooks/useSSO.ts-4-import * as WebBrowser from 'expo-web-browser';
--
packages/expo/src/hooks/useOAuth.ts-1-import { useSignIn, useSignUp } from '@clerk/clerk-react';
packages/expo/src/hooks/useOAuth.ts:2:import type { OAuthStrategy, SetActive, SignInResource, SignUpResource } from '@clerk/types';
packages/expo/src/hooks/useOAuth.ts-3-import * as AuthSession from 'expo-auth-session';
packages/expo/src/hooks/useOAuth.ts-4-import * as WebBrowser from 'expo-web-browser';
--
packages/expo/src/hooks/useAuth.ts-1-import { useAuth as useAuthBase } from '@clerk/clerk-react';
packages/expo/src/hooks/useAuth.ts-2-import { isNetworkError } from '@clerk/shared/error';
packages/expo/src/hooks/useAuth.ts:3:import type { GetToken, GetTokenOptions, UseAuthReturn } from '@clerk/types';
packages/expo/src/hooks/useAuth.ts-4-
packages/expo/src/hooks/useAuth.ts-5-import { SessionJWTCache } from '../cache';
</shell_result>
Script executed:
#!/bin/bash
# Check if @clerk/shared/types exports these types
fd -t f 'types.ts' packages/shared/src/ | head -10Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
packages/shared/src/react/clerk-rq/types.ts
packages/shared/src/react/hooks/useSubscription.types.ts
packages/shared/src/react/types.ts
packages/shared/src/telemetry/types.ts
packages/shared/src/workerTimers/workerTimers.types.ts
</shell_result>
Script executed:
# Check the coding guidelines about imports - they mention preferring @clerk/shared/types
rg -n '@clerk/shared/types|@clerk/types' packages/ -t ts --max-count=5 | head -20Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
packages/vue/src/utils/useCustomPages.ts:1:import type { CustomPage } from '@clerk/shared/types';
packages/vue/src/utils/useCustomMenuItems.ts:1:import type { CustomMenuItem } from '@clerk/shared/types';
packages/vue/src/utils/useClerkLoaded.ts:1:import type { LoadedClerk } from '@clerk/shared/types';
packages/vue/src/utils/updateClerkOptions.ts:1:import type { ClerkOptions } from '@clerk/shared/types';
packages/vue/src/utils/updateClerkOptions.ts:24: // @ts-expect-error - `__unstable__updateProps` is not exposed as public API from `@clerk/types`
packages/vue/src/types.ts:16:} from '@clerk/shared/types';
packages/vue/src/plugin.ts:11:} from '@clerk/shared/types';
packages/vue/src/experimental.ts:41:} from '@clerk/shared/types';
packages/vue/src/composables/useSignIn.ts:2:import type { UseSignInReturn } from '@clerk/shared/types';
packages/vue/src/composables/useOrganization.ts:2:import type { OrganizationMembershipResource, OrganizationResource } from '@clerk/shared/types';
packages/vue/src/components/controlComponents.ts:7:} from '@clerk/shared/types';
packages/vue/src/composables/useUser.ts:1:import type { UseUserReturn } from '@clerk/shared/types';
packages/vue/src/composables/useSignUp.ts:2:import type { UseSignUpReturn } from '@clerk/shared/types';
packages/vue/src/composables/useSessionList.ts:1:import type { UseSessionListReturn } from '@clerk/shared/types';
packages/vue/src/composables/useSession.ts:1:import type { UseSessionReturn } from '@clerk/shared/types';
packages/themes/src/createTheme.ts:3:import type { BaseTheme, DeepPartial, Elements, Theme } from '@clerk/shared/types';
packages/vue/src/composables/useAuth.ts:2:import type { Clerk, GetToken, JwtPayload, PendingSessionOptions, SignOut, UseAuthReturn } from '@clerk/shared/types';
packages/types/src/index.d.ts:2: * `@clerk/types` is now merged with `@clerk/shared`
packages/types/src/index.d.ts:3: * Please import from `@clerk/shared/types` instead.
packages/types/src/index.d.ts:8:export type * from '@clerk/shared/types';
</shell_result>
Script executed:
# Look at the iOS implementation more carefully
cat -n packages/expo/src/hooks/useSignInWithGoogle.ios.ts | head -20Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
1 import { useSignIn, useSignUp } from '@clerk/clerk-react';
2 import Constants from 'expo-constants';
3 import * as Crypto from 'expo-crypto';
4
5 import { ClerkGoogleOneTapSignIn, isSuccessResponse } from '../google-one-tap';
6 import { errorThrower } from '../utils/errors';
7
8 // Type for unsafe metadata that can be attached to sign-ups
9 type SignUpUnsafeMetadata = Record<string, any>;
10
11 export type StartGoogleAuthenticationFlowParams = {
12 unsafeMetadata?: SignUpUnsafeMetadata;
13 };
14
15 export type StartGoogleAuthenticationFlowReturnType = {
16 createdSessionId: string | null;
17 setActive?: any;
18 signIn?: any;
19 signUp?: any;
20 };
</shell_result>
Script executed:
# Check if there's a pattern of defining SignUpUnsafeMetadata locally in other files
rg -n 'type SignUpUnsafeMetadata' packages/Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
packages/expo/src/hooks/useSignInWithGoogle.ios.ts:9:type SignUpUnsafeMetadata = Record<string, any>;
</shell_result>
Script executed:
#!/bin/bash
# Check if SetActive, SignInResource, SignUpResource are exported from @clerk/shared/types
rg -n 'export.*\b(SetActive|SignInResource|SignUpResource)\b' packages/shared/src/Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
packages/shared/src/types/signIn.ts:34:export interface SignInResource extends ClerkResource {
packages/shared/src/types/clerk.ts:1268:export type SetActive = (setActiveParams: SetActiveParams) => Promise<void>;
packages/shared/src/types/signUp.ts:40:export interface SignUpResource extends ClerkResource {
</shell_result>
Script executed:
# Also check the @clerk/types index to see what it re-exports
cat packages/types/src/index.d.tsRepository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
/**
* `@clerk/types` is now merged with `@clerk/shared`
* Please import from `@clerk/shared/types` instead.
*
* This export exists for backwards compatibility
* until we drop the package completely with the next major release.
*/
export type * from '@clerk/shared/types';
</shell_result>
Fix undefined SignUpUnsafeMetadata type and update deprecated import source (blocks TypeScript compilation).
StartGoogleAuthenticationFlowParams references SignUpUnsafeMetadata, but that type is never defined in this module, causing a TypeScript compilation error. Additionally, the file imports from the deprecated @clerk/types package, which now re-exports from @clerk/shared/types.
Align with the iOS implementation and update the import source:
-import type { SetActive, SignInResource, SignUpResource } from '@clerk/types';
-
-import { errorThrower } from '../utils/errors';
-
-export type StartGoogleAuthenticationFlowParams = {
- unsafeMetadata?: SignUpUnsafeMetadata;
-};
+import type { SetActive, SignInResource, SignUpResource } from '@clerk/shared/types';
+
+import { errorThrower } from '../utils/errors';
+
+type SignUpUnsafeMetadata = Record<string, unknown>;
+
+export type StartGoogleAuthenticationFlowParams = {
+ unsafeMetadata?: SignUpUnsafeMetadata;
+};This resolves the compilation error, aligns with the deprecation notice in @clerk/types, and improves type safety by using unknown instead of any.
🤖 Prompt for AI Agents
In packages/expo/src/hooks/useSignInWithGoogle.ts around lines 1 to 14, the code
references an undefined SignUpUnsafeMetadata type and imports from the
deprecated @clerk/types; update the import to pull types from
'@clerk/shared/types' (matching iOS implementation) and import or define
SignUpUnsafeMetadata there, then change StartGoogleAuthenticationFlowParams to
use SignUpUnsafeMetadata typed as unknown (not any) to improve type safety and
resolve the TypeScript compilation error.
@clerk/agent-toolkit
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/dev-cli
@clerk/elements
@clerk/clerk-expo
@clerk/expo-passkeys
@clerk/express
@clerk/fastify
@clerk/localizations
@clerk/nextjs
@clerk/nuxt
@clerk/clerk-react
@clerk/react-router
@clerk/remix
@clerk/shared
@clerk/tanstack-react-start
@clerk/testing
@clerk/themes
@clerk/types
@clerk/upgrade
@clerk/vue
commit: |
… improve error handling in sign-in flow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (2)
packages/expo/src/hooks/useSignInWithGoogle.android.ts (1)
95-105: Nonce handling issue was previously identified.The nonce is hashed before being passed to Google, but Google's Credential Manager API expects the plain nonce value. This was flagged in a previous review. The
hashedNonceshould be replaced with the plainnonce.packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts (1)
119-127: Test assertions may pass incorrectly due to mock mismatch.Given that
mocks.GoogleSignin.hasPlayServicesandmocks.GoogleSignin.signInare never called by the actual hook (which usesClerkGoogleOneTapSignIn), these assertions will fail or pass based on incorrect assumptions.This is a consequence of the mock mismatch noted above. The tests need to be updated to mock the correct module (
ClerkGoogleOneTapSignInviaexpo-modules-core).
🧹 Nitpick comments (5)
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts (2)
117-158: Consider extracting shared error-handling logic.The
signInandcreateAccountmethods have identical catch blocks. This duplication could be reduced by extracting a helper function.// Helper function to map errors to responses function mapErrorToResponse(error: unknown): OneTapResponse | null { if (isErrorWithCode(error)) { if (error.code === 'SIGN_IN_CANCELLED') { return { type: 'cancelled', data: null }; } if (error.code === 'NO_SAVED_CREDENTIAL_FOUND') { return { type: 'noSavedCredentialFound', data: null }; } } return null; }
90-91: Documentation indicates Android-only, but module is cross-platform.The JSDoc states
@platform Android, but per the PR summary this module also supports iOS. Consider updating to@platform Android, iOSor removing the platform annotation.packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt (2)
75-76: CredentialManager is recreated on every access.
CredentialManager.create(context)is called each time thecredentialManagerproperty is accessed. While this is likely lightweight, consider caching the instance for consistency and minor performance improvement.+ private var _credentialManager: CredentialManager? = null + private val credentialManager: CredentialManager - get() = CredentialManager.create(context) + get() = _credentialManager ?: CredentialManager.create(context).also { _credentialManager = it }
70-70: Consider cancelling the coroutine scope on module cleanup.The
mainScopeis never cancelled, which could lead to coroutine leaks if the module is destroyed while operations are in-flight. Consider implementingOnDestroyto cancel pending coroutines.-class ClerkGoogleSignInModule : Module() { +class ClerkGoogleSignInModule : Module() { private var webClientId: String? = null private var hostedDomain: String? = null private var autoSelectEnabled: Boolean = false - private val mainScope = CoroutineScope(Dispatchers.Main) + private val mainScope = CoroutineScope(Dispatchers.Main + SupervisorJob()) + + override fun definition() = ModuleDefinition { + Name("ClerkGoogleSignIn") + + OnDestroy { + mainScope.cancel() + }packages/expo/src/hooks/useSignInWithGoogle.android.ts (1)
154-167: Consider extracting Clerk error type checking into a helper.The
isClerkErrorandhasExternalAccountNotFoundErrorchecks are verbose. Consider creating a reusable type guard function for Clerk errors.// In a shared utils file function isClerkErrorWithCode(error: unknown, code: string): boolean { if (!error || typeof error !== 'object') return false; if (!('clerkError' in error) || (error as { clerkError: boolean }).clerkError !== true) return false; if (!('errors' in error) || !Array.isArray((error as { errors: unknown[] }).errors)) return false; return (error as { errors: Array<{ code: string }> }).errors.some(err => err.code === code); } // Usage if (isClerkErrorWithCode(signInError, 'external_account_not_found')) { // ... }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt(1 hunks)packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts(1 hunks)packages/expo/src/google-one-tap/types.ts(1 hunks)packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts(1 hunks)packages/expo/src/hooks/useSignInWithGoogle.android.ts(1 hunks)packages/expo/src/hooks/useSignInWithGoogle.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/expo/src/google-one-tap/types.ts
- packages/expo/src/hooks/useSignInWithGoogle.ts
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
All code must pass ESLint checks with the project's configuration
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use Prettier for consistent code formatting
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
packages/**/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
TypeScript is required for all packages
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Follow established naming conventions (PascalCase for components, camelCase for variables)
Prefer importing types from
@clerk/shared/typesinstead of the deprecated@clerk/typesalias
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
packages/**/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
packages/**/src/**/*.{ts,tsx,js,jsx}: Maintain comprehensive JSDoc comments for public APIs
Use tree-shaking friendly exports
Validate all inputs and sanitize outputs
All public APIs must be documented with JSDoc
Use dynamic imports for optional features
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Implement proper logging with different levels
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{test,spec}.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
**/*.{test,spec}.{ts,tsx,js,jsx}: Unit tests are required for all new functionality
Verify proper error handling and edge cases
Include tests for all new features
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts
**/*.ts?(x)
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use proper TypeScript error types
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{test,spec,e2e}.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use real Clerk instances for integration tests
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)
**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoidanytype - preferunknownwhen type is uncertain, then narrow with type guards
Implement type guards forunknowntypes using the patternfunction isType(value: unknown): value is Type
Useinterfacefor object shapes that might be extended
Usetypefor unions, primitives, and computed types
Preferreadonlyproperties for immutable data structures
Useprivatefor internal implementation details in classes
Useprotectedfor inheritance hierarchies
Usepublicexplicitly for clarity in public APIs
Use mixins for shared behavior across unrelated classes in TypeScript
Use generic constraints with bounded type parameters like<T extends { id: string }>
Use utility types likeOmit,Partial, andPickfor data transformation instead of manual type construction
Use discriminated unions instead of boolean flags for state management and API responses
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation at the type level
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Document functions with JSDoc comments including @param, @returns, @throws, and @example tags
Create custom error classes that extend Error for specific error types
Use the Result pattern for error handling instead of throwing exceptions
Use optional chaining (?.) and nullish coalescing (??) operators for safe property access
Let TypeScript infer obvious types to reduce verbosity
Useconst assertionswithas constfor literal types
Usesatisfiesoperator for type checking without widening types
Declare readonly arrays and objects for immutable data structures
Use spread operator and array spread for immutable updates instead of mutations
Use lazy loading for large types...
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
🧬 Code graph analysis (2)
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts (1)
packages/expo/src/hooks/useSignInWithGoogle.android.ts (1)
useSignInWithGoogle(63-212)
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts (1)
packages/expo/src/google-one-tap/types.ts (8)
ConfigureParams(4-31)SignInParams(36-49)OneTapResponse(146-146)CreateAccountParams(54-60)ExplicitSignInParams(65-71)CancelledResponse(130-133)NoSavedCredentialFound(138-141)OneTapSuccessResponse(112-125)
🪛 detekt (1.23.8)
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt
[warning] 121-121: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
[warning] 123-123: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
[warning] 165-165: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
[warning] 167-167: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
[warning] 208-208: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Build Packages
- GitHub Check: Formatting | Dedupe | Changeset
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: semgrep-cloud-platform/scan
- GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (7)
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts (2)
21-30: Lazy-loading pattern is well-implemented.The lazy-load approach prevents crashes when the native module is unavailable (e.g., on web or in test environments). The module is cached after first load, which is efficient.
56-63: Well-implemented type guard with proper narrowing.The
isErrorWithCodefunction correctly narrowsunknownto a typed error object using safe property checks. This follows TypeScript best practices for type guards.packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt (2)
121-129: Exception handling is appropriate despite static analysis warning.The static analysis flags "swallowed exceptions," but these are correctly converted to typed promise rejections with meaningful error codes (
SIGN_IN_CANCELLED,NO_SAVED_CREDENTIAL_FOUND). The user-facing error semantics are preserved. For enhanced debugging, you could optionally log the original exception before rejecting.
242-244: The review comment's core premise is incorrect, but the underlying concern is valid.Per Google's official documentation,
GoogleIdTokenCredential.idis the email address, not a separate identifier. The review comment incorrectly states "The Google ID may differ from the email address."However, there is a legitimate semantic issue: using the email address for both the
idandidfield should contain the user's unique Google identifier (thesubclaim from the JWT), whileidTokenis available and could be decoded to extract the proper unique identifier."user" to mapOf( "id" to googleIdTokenCredential.id, "email" to googleIdTokenCredential.id,Consider extracting the unique user ID from the
idTokenJWT claims for theidfield, rather than using the email address.Likely an incorrect or invalid review comment.
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts (1)
242-291: Good coverage of theexternal_account_not_foundfallback path.This test addresses the previously noted missing coverage. It properly validates that when
signIn.createthrows anexternal_account_not_founderror, the hook falls back tosignUp.createwith the token and unsafeMetadata. However, ensure the mocks are corrected so this test actually exercises the real code path.packages/expo/src/hooks/useSignInWithGoogle.android.ts (2)
22-62: Comprehensive JSDoc with clear example.The documentation clearly explains the hook's purpose, features, and usage pattern. The example demonstrates proper error handling and session activation.
209-212: Clean hook API design.The hook follows React conventions by returning a stable object with the
startGoogleAuthenticationFlowfunction. The return type is consistent across all execution paths.
| @@ -0,0 +1,212 @@ | |||
| import { useSignIn, useSignUp } from '@clerk/clerk-react'; | |||
| import type { SetActive, SignInResource, SignUpResource } from '@clerk/types'; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Script executed:
#!/bin/bash
# First, let's find where these types are defined
# Search for SetActive, SignInResource, SignUpResource in the shared package
echo "=== Searching for type definitions in @clerk/shared ==="
fd -e ts -e tsx "types" packages/shared/src --type f | head -20Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
=== Searching for type definitions in @clerk/shared ===
packages/shared/src/react/clerk-rq/types.ts
packages/shared/src/react/hooks/usePageOrInfinite.types.ts
packages/shared/src/react/hooks/usePaymentAttemptQuery.types.ts
packages/shared/src/react/hooks/usePlanDetailsQuery.types.ts
packages/shared/src/react/hooks/useStatementQuery.types.ts
packages/shared/src/react/hooks/useSubscription.types.ts
packages/shared/src/react/types.ts
packages/shared/src/telemetry/types.ts
packages/shared/src/workerTimers/workerTimers.types.ts
</shell_result>
Script executed:
#!/bin/bash
# Check the package structure for @clerk/shared
echo "=== Checking @clerk/shared package structure ==="
find packages/shared -name "*.ts" -type f | grep -E "(types|index)" | head -20Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
=== Checking @clerk/shared package structure ===
packages/shared/src/dom/index.ts
packages/shared/src/index.ts
packages/shared/src/react/clerk-rq/types.ts
packages/shared/src/react/hooks/__tests__/mocks/index.ts
packages/shared/src/react/hooks/index.ts
packages/shared/src/react/hooks/usePageOrInfinite.types.ts
packages/shared/src/react/hooks/useSubscription.types.ts
packages/shared/src/react/hooks/usePaymentAttemptQuery.types.ts
packages/shared/src/react/hooks/usePlanDetailsQuery.types.ts
packages/shared/src/react/hooks/useStatementQuery.types.ts
packages/shared/src/react/index.ts
packages/shared/src/react/types.ts
packages/shared/src/telemetry/events/index.ts
packages/shared/src/telemetry/types.ts
packages/shared/src/types/apiKeys.ts
packages/shared/src/types/apiKeysSettings.ts
packages/shared/src/types/appearance.ts
packages/shared/src/types/attributes.ts
packages/shared/src/types/authConfig.ts
packages/shared/src/types/authObject.ts
</shell_result>
Script executed:
#!/bin/bash
# Look for exports in the shared package
echo "=== Searching for type exports in @clerk/shared/src ==="
rg -n "export.*(?:SetActive|SignInResource|SignUpResource)" packages/shared/src --type ts -A 2 -B 2Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
=== Searching for type exports in @clerk/shared/src ===
packages/shared/src/types/signIn.ts-33- * The `SignIn` object holds the state of the current sign-in and provides helper methods to navigate and complete the sign-in process. It is used to manage the sign-in lifecycle, including the first and second factor verification, and the creation of a new session.
packages/shared/src/types/signIn.ts-34- */
packages/shared/src/types/signIn.ts:35:export interface SignInResource extends ClerkResource {
packages/shared/src/types/signIn.ts-36- /**
packages/shared/src/types/signIn.ts-37- * The current status of the sign-in.
--
packages/shared/src/types/clerk.ts-126-export type UnsubscribeCallback = () => void;
packages/shared/src/types/clerk.ts-127-export type BeforeEmitCallback = (session?: SignedInSessionResource | null) => void | Promise<any>;
packages/shared/src/types/clerk.ts:128:export type SetActiveNavigate = ({ session }: { session: SessionResource }) => void | Promise<unknown>;
packages/shared/src/types/clerk.ts-129-
packages/shared/src/types/clerk.ts-130-export type SignOutCallback = () => void | Promise<any>;
--
packages/shared/src/types/clerk.ts-1283- * @interface
packages/shared/src/types/clerk.ts-1284- */
packages/shared/src/types/clerk.ts:1285:export type SetActiveParams = {
packages/shared/src/types/clerk.ts-1286- /**
packages/shared/src/types/clerk.ts-1287- * The session resource or session ID (string version) to be set as active. If `null`, the current session is deleted.
--
packages/shared/src/types/clerk.ts-1333- * @inline
packages/shared/src/types/clerk.ts-1334- */
packages/shared/src/types/clerk.ts:1335:export type SetActive = (setActiveParams: SetActiveParams) => Promise<void>;
packages/shared/src/types/clerk.ts-1336-
packages/shared/src/types/clerk.ts-1337-export type RoutingOptions =
--
packages/shared/src/types/signUp.ts-38- * The `SignUp` object holds the state of the current sign-up and provides helper methods to navigate and complete the sign-up process. Once a sign-up is complete, a new user is created.
packages/shared/src/types/signUp.ts-39- */
packages/shared/src/types/signUp.ts:40:export interface SignUpResource extends ClerkResource {
packages/shared/src/types/signUp.ts-41- /**
packages/shared/src/types/signUp.ts-42- * The current status of the sign-up.
</shell_result>
Script executed:
#!/bin/bash
# Check what's in the current file to understand the context better
echo "=== Examining the current file ==="
cat packages/expo/src/hooks/useSignInWithGoogle.android.ts | head -20Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
=== Examining the current file ===
import { useSignIn, useSignUp } from '@clerk/clerk-react';
import type { SetActive, SignInResource, SignUpResource } from '@clerk/types';
import Constants from 'expo-constants';
import * as Crypto from 'expo-crypto';
import { ClerkGoogleOneTapSignIn, isSuccessResponse } from '../google-one-tap';
import { errorThrower } from '../utils/errors';
type SignUpUnsafeMetadata = Record<string, unknown>;
export type StartGoogleAuthenticationFlowParams = {
unsafeMetadata?: SignUpUnsafeMetadata;
};
export type StartGoogleAuthenticationFlowReturnType = {
createdSessionId: string | null;
setActive?: SetActive;
signIn?: SignInResource;
signUp?: SignUpResource;
};
</shell_result>
Script executed:
#!/bin/bash
# Check the main exports from @clerk/shared
echo "=== Checking @clerk/shared main exports ==="
cat packages/shared/src/index.ts | head -50Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
=== Checking @clerk/shared main exports ===
/** The following files are not exported on purpose:
* - cookie.ts
* - globs.ts
*
* The following folders are also not exported on purpose:
* - react
*
* People should always use @clerk/shared/<name> instead
*/
export * from './utils';
export { apiUrlFromPublishableKey } from './apiUrlFromPublishableKey';
export * from './browser';
export * from './color';
export * from './constants';
export * from './date';
export * from './deprecated';
export { deriveState } from './deriveState';
export * from './error';
export * from './file';
export { isomorphicAtob } from './isomorphicAtob';
export { isomorphicBtoa } from './isomorphicBtoa';
export * from './keys';
export * from './loadClerkJsScript';
export { loadScript } from './loadScript';
export { LocalStorageBroadcastChannel } from './localStorageBroadcastChannel';
export * from './poller';
export * from './proxy';
export * from './underscore';
export * from './url';
export { versionSelector } from './versionSelector';
export * from './object';
export * from './logger';
export { createWorkerTimers } from './workerTimers';
export { DEV_BROWSER_JWT_KEY, extractDevBrowserJWTFromURL, setDevBrowserJWTInURL } from './devBrowser';
export { getEnvVariable } from './getEnvVariable';
export * from './pathMatcher';
export * from './netlifyCacheHandler';
</shell_result>
Script executed:
#!/bin/bash
# Check if there's a types subdirectory index
echo "=== Checking @clerk/shared/src/types/index.ts ==="
fd "index.ts" packages/shared/src/types --type fRepository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
=== Checking @clerk/shared/src/types/index.ts ===
packages/shared/src/types/index.ts
</shell_result>
Script executed:
#!/bin/bash
# Look for all exports from @clerk/shared/src/types
echo "=== Listing all files in @clerk/shared/src/types ==="
ls -la packages/shared/src/types/ | head -30Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
=== Listing all files in @clerk/shared/src/types ===
total 636
drwxr-xr-x 2 jailuser jailuser 1740 Nov 26 18:34 .
drwxr-xr-x 12 jailuser jailuser 1160 Nov 26 18:30 ..
-rw-r--r-- 1 jailuser jailuser 1318 Nov 26 18:30 apiKeys.ts
-rw-r--r-- 1 jailuser jailuser 495 Nov 26 18:30 apiKeysSettings.ts
-rw-r--r-- 1 jailuser jailuser 40598 Nov 26 18:30 appearance.ts
-rw-r--r-- 1 jailuser jailuser 191 Nov 26 18:30 attributes.ts
-rw-r--r-- 1 jailuser jailuser 789 Nov 26 18:30 authConfig.ts
-rw-r--r-- 1 jailuser jailuser 1530 Nov 26 18:30 authObject.ts
-rw-r--r-- 1 jailuser jailuser 197 Nov 26 18:30 backupCode.ts
-rw-r--r-- 1 jailuser jailuser 33302 Nov 26 18:30 billing.ts
-rw-r--r-- 1 jailuser jailuser 74731 Nov 26 18:30 clerk.ts
-rw-r--r-- 1 jailuser jailuser 1358 Nov 26 18:30 client.ts
-rw-r--r-- 1 jailuser jailuser 791 Nov 26 18:30 commerceSettings.ts
-rw-r--r-- 1 jailuser jailuser 283 Nov 26 18:30 customMenuItems.ts
-rw-r--r-- 1 jailuser jailuser 237 Nov 26 18:30 customPages.ts
-rw-r--r-- 1 jailuser jailuser 462 Nov 26 18:30 deletedObject.ts
-rw-r--r-- 1 jailuser jailuser 363 Nov 26 18:30 devtools.ts
-rw-r--r-- 1 jailuser jailuser 3147 Nov 26 18:30 displayConfig.ts
-rw-r--r-- 1 jailuser jailuser 1700 Nov 26 18:30 elementIds.ts
-rw-r--r-- 1 jailuser jailuser 1577 Nov 26 18:30 emailAddress.ts
-rw-r--r-- 1 jailuser jailuser 1474 Nov 26 18:30 enterpriseAccount.ts
-rw-r--r-- 1 jailuser jailuser 1176 Nov 26 18:30 environment.ts
-rw-r--r-- 1 jailuser jailuser 1756 Nov 26 18:30 errors.ts
-rw-r--r-- 1 jailuser jailuser 1107 Nov 26 18:30 externalAccount.ts
-rw-r--r-- 1 jailuser jailuser 4242 Nov 26 18:30 factors.ts
-rw-r--r-- 1 jailuser jailuser 6865 Nov 26 18:30 hooks.ts
-rw-r--r-- 1 jailuser jailuser 276 Nov 26 18:30 identificationLink.ts
-rw-r--r-- 1 jailuser jailuser 284 Nov 26 18:30 identifiers.ts
-rw-r--r-- 1 jailuser jailuser 173 Nov 26 18:30 image.ts
</shell_result>
Script executed:
#!/bin/bash
# Check if SetActive, SignInResource, SignUpResource are exported from the main types barrel
echo "=== Searching for these types in @clerk/shared exports ==="
rg -n "export.*from.*types" packages/shared/src/index.tsRepository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 1
stdout:
=== Searching for these types in @clerk/shared exports ===
</shell_result>
Script executed:
#!/bin/bash
# Check what's exported from @clerk/shared/src/types/index.ts
echo "=== Checking @clerk/shared/src/types/index.ts ==="
cat packages/shared/src/types/index.tsRepository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
=== Checking @clerk/shared/src/types/index.ts ===
export type * from './apiKeys';
export type * from './apiKeysSettings';
export type * from './appearance';
export type * from './attributes';
export type * from './authConfig';
export type * from './authObject';
export type * from './backupCode';
export type * from './billing';
export type * from './clerk';
export type * from './client';
export type * from './commerceSettings';
export type * from './customMenuItems';
export type * from './customPages';
export type * from './deletedObject';
export type * from './devtools';
export type * from './displayConfig';
export type * from './elementIds';
export type * from './emailAddress';
export type * from './enterpriseAccount';
export type * from './environment';
export type * from './errors';
export type * from './externalAccount';
export type * from './factors';
export type * from './hooks';
export type * from './identificationLink';
export type * from './identifiers';
export type * from './image';
export type * from './instance';
export type * from './json';
export type * from './jwt';
export type * from './jwtv2';
export type * from './key';
export type * from './localization';
export type * from './multiDomain';
export type * from './oauth';
export type * from './organization';
export type * from './organizationDomain';
export type * from './organizationInvitation';
export type * from './organizationMembership';
export type * from './organizationMembershipRequest';
export type * from './organizationSettings';
export type * from './organizationSuggestion';
export type * from './pagination';
export type * from './passkey';
export type * from './passwords';
export type * from './permission';
export type * from './phoneCodeChannel';
export type * from './phoneNumber';
export type * from './protect';
export type * from './protectConfig';
export type * from './redirects';
export type * from './resource';
export type * from './role';
export type * from './router';
/**
* TODO @revamp-hooks: Drop this in the next major release.
*/
export type * from './runtime-values';
export type * from './saml';
export type * from './samlAccount';
export type * from './samlConnection';
export type * from './session';
export type * from './sessionVerification';
export type * from './signIn';
export type * from './signInCommon';
export type * from './signInFuture';
export type * from './signUp';
export type * from './signUpCommon';
export type * from './signUpFuture';
export type * from './snapshots';
export type * from './ssr';
export type * from './state';
export type * from './strategies';
export type * from './telemetry';
export type * from './theme';
export type * from './token';
export type * from './totp';
export type * from './user';
export type * from './userOrganizationInvitation';
export type * from './userSettings';
export type * from './utils';
export type * from './verification';
export type * from './waitlist';
export type * from './web3';
export type * from './web3Wallet';
</shell_result>
Script executed:
#!/bin/bash
# Check if @clerk/types package exists (it might be in packages/)
echo "=== Looking for @clerk/types package ==="
find packages -maxdepth 2 -name "package.json" -type f -exec grep -l '"name": "@clerk/types"' {} \;Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
=== Looking for @clerk/types package ===
packages/types/package.json
</shell_result>
Script executed:
#!/bin/bash
# Check the package.json for @clerk/shared to see export maps
echo "=== Checking @clerk/shared package.json exports ==="
cat packages/shared/package.json | grep -A 50 '"exports"'Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
=== Checking @clerk/shared package.json exports ===
"exports": {
".": {
"import": {
"types": "./dist/runtime/index.d.mts",
"default": "./dist/runtime/index.mjs"
},
"require": {
"types": "./dist/runtime/index.d.ts",
"default": "./dist/runtime/index.js"
}
},
"./*": {
"import": {
"types": "./dist/runtime/*.d.mts",
"default": "./dist/runtime/*.mjs"
},
"require": {
"types": "./dist/runtime/*.d.ts",
"default": "./dist/runtime/*.js"
}
},
"./react": {
"import": {
"types": "./dist/runtime/react/index.d.mts",
"default": "./dist/runtime/react/index.mjs"
},
"require": {
"types": "./dist/runtime/react/index.d.ts",
"default": "./dist/runtime/react/index.js"
}
},
"./utils": {
"import": {
"types": "./dist/runtime/utils/index.d.mts",
"default": "./dist/runtime/utils/index.mjs"
},
"require": {
"types": "./dist/runtime/utils/index.d.ts",
"default": "./dist/runtime/utils/index.js"
}
},
"./workerTimers": {
"import": {
"types": "./dist/runtime/workerTimers/index.d.mts",
"default": "./dist/runtime/workerTimers/index.mjs"
},
"require": {
"types": "./dist/runtime/workerTimers/index.d.ts",
"default": "./dist/runtime/workerTimers/index.js"
}
},
</shell_result>
Script executed:
#!/bin/bash
# Check the @clerk/types package
echo "=== Checking @clerk/types package.json ==="
cat packages/types/package.json | head -50Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
=== Checking @clerk/types package.json ===
{
"name": "@clerk/types",
"version": "4.101.3",
"description": "Typings for Clerk libraries.",
"keywords": [
"clerk",
"react",
"auth",
"authentication",
"passwordless",
"session",
"jwt",
"types"
],
"homepage": "https://clerk.com/",
"bugs": {
"url": "https://github.com/clerk/javascript/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/clerk/javascript.git",
"directory": "packages/types"
},
"license": "MIT",
"author": "Clerk",
"exports": {
".": {
"import": {
"types": "./src/index.d.mts",
"default": "./src/index.mjs"
},
"require": {
"types": "./src/index.d.ts",
"default": "./src/index.js"
}
}
},
"main": "src/index.js",
"module": "src/index.js",
"types": "src/index.d.ts",
"files": [
"src"
],
"scripts": {
"format": "node ../../scripts/format-package.mjs",
"format:check": "node ../../scripts/format-package.mjs --check",
"lint:attw": "attw --pack . --profile node16"
},
"dependencies": {
"@clerk/shared": "workspace:^"
</shell_result>
Script executed:
#!/bin/bash
# Check what @clerk/types exports
echo "=== Checking @clerk/types src/index.ts ==="
cat packages/types/src/index.ts | head -50Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
=== Checking @clerk/types src/index.ts ===
stderr:
cat: packages/types/src/index.ts: No such file or directory
</shell_result>
Script executed:
#!/bin/bash
# Search for any deprecation notices related to @clerk/types
echo "=== Searching for deprecation notices ==="
rg -i "deprecat" packages/types --type ts --type json -C 2Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 1
stdout:
=== Searching for deprecation notices ===
</shell_result>
Script executed:
#!/bin/bash
# List the files in @clerk/types/src
echo "=== Checking @clerk/types/src files ==="
ls -la packages/types/src/Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
=== Checking @clerk/types/src files ===
total 16
drwxr-xr-x 2 jailuser jailuser 120 Nov 26 18:30 .
drwxr-xr-x 3 jailuser jailuser 140 Nov 26 18:30 ..
-rw-r--r-- 1 jailuser jailuser 278 Nov 26 18:30 index.d.mts
-rw-r--r-- 1 jailuser jailuser 278 Nov 26 18:30 index.d.ts
-rw-r--r-- 1 jailuser jailuser 257 Nov 26 18:30 index.js
-rw-r--r-- 1 jailuser jailuser 247 Nov 26 18:30 index.mjs
</shell_result>
Script executed:
#!/bin/bash
# Check the generated type definition file
echo "=== Checking @clerk/types index definition ==="
cat packages/types/src/index.d.ts | head -100Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
=== Checking @clerk/types index definition ===
/**
* `@clerk/types` is now merged with `@clerk/shared`
* Please import from `@clerk/shared/types` instead.
*
* This export exists for backwards compatibility
* until we drop the package completely with the next major release.
*/
export type * from '@clerk/shared/types';
</shell_result>
Script executed:
#!/bin/bash
# Search for how many files import from @clerk/types vs @clerk/shared/types
echo "=== Counting imports from @clerk/types (type imports) ==="
rg "from ['\"]@clerk/types['\"]" --type ts --type tsx -c | head -10Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
=== Counting imports from @clerk/types (type imports) ===
stderr:
unrecognized file type: tsx
</shell_result>
Script executed:
#!/bin/bash
# Search for imports from @clerk/shared/types
echo "=== Counting imports from @clerk/shared/types (type imports) ==="
rg "from ['\"]@clerk/shared/types['\"]" --type ts --type tsx -c | head -10Repository: clerk/javascript
<shell_result>
Repository: clerk/javascript
Exit code: 0
stdout:
=== Counting imports from @clerk/shared/types (type imports) ===
stderr:
unrecognized file type: tsx
</shell_result>
Use @clerk/shared/types instead of @clerk/types.
@clerk/types has been deprecated in favor of @clerk/shared/types. The types package now re-exports from shared with a deprecation notice stating it will be removed in the next major release.
-import type { SetActive, SignInResource, SignUpResource } from '@clerk/types';
+import type { SetActive, SignInResource, SignUpResource } from '@clerk/shared/types';🤖 Prompt for AI Agents
In packages/expo/src/hooks/useSignInWithGoogle.android.ts around line 2, the
import currently uses the deprecated package '@clerk/types'; update the import
source to '@clerk/shared/types' (i.e., replace "@clerk/types" with
"@clerk/shared/types" for the SetActive, SignInResource, SignUpResource types)
and run type-checks to ensure no other references to the deprecated package
remain.
…n-In and improve error handling
…droid using built-in native modules
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (3)
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts (3)
14-31: Lazy native-module loading pattern looks good; consider clarifying the comment.The
ClerkGoogleSignInNativeModuleinterface andgetNativeModulelazy initializer are well-typed and align with Expo’srequireNativeModuleusage. The comment “avoid crashes when not available” can be a bit misleading though — you’re primarily avoiding an import‑time crash on platforms where the native module doesn’t exist, but a call togetNativeModule()will still throw if the module is missing.You might tweak the comment to something like “Lazy‑load the native module to avoid crashes at import time on unsupported platforms” to better set expectations, but the implementation itself is solid.
57-64: TightenisErrorWithCodeguard or relax the return type to avoid lying aboutmessage.Right now the guard promises
error is { code: string; message: string }but only validates thecodeproperty. That means callers could safely accesserror.messageas astringeven when it’s missing or non‑string at runtime.Either of these adjustments would make the guard accurate:
- Option A – Validate
messageas well (keeps current type):-export function isErrorWithCode(error: unknown): error is { code: string; message: string } { +export function isErrorWithCode(error: unknown): error is { code: string; message: string } { return ( error !== null && typeof error === 'object' && - 'code' in error && - typeof (error as { code: unknown }).code === 'string' + 'code' in error && + typeof (error as { code: unknown }).code === 'string' && + 'message' in error && + typeof (error as { message: unknown }).message === 'string' ); }
- Option B – Relax the type so only
codeis guaranteed (simpler, since you don’t usemessage):-export function isErrorWithCode(error: unknown): error is { code: string; message: string } { +export function isErrorWithCode(error: unknown): error is { code: string } { return ( error !== null && typeof error === 'object' && 'code' in error && typeof (error as { code: unknown }).code === 'string' ); }Given current usage only checks
error.code, Option B is probably the minimal change.
66-193: Core API shape and error mapping look solid; consider a small helper to dedupe error-to-response mapping.The
ClerkGoogleOneTapSignInmethods are well-typed, documented, and provide a clean Promise‑based API.params?: …plusparams ?? {}is a nice ergonomic choice for callers, and the mapping of known error codes (SIGN_IN_CANCELLED,NO_SAVED_CREDENTIAL_FOUND) to structuredOneTapResponseshapes is clear.There is some duplication in how you translate
error.codeinto{ type: 'cancelled' | 'noSavedCredentialFound', data: null }acrosssignIn,createAccount, andpresentExplicitSignIn. A tiny internal helper would reduce drift if more codes are added later; for example:+function mapErrorToOneTapResponse(error: unknown): OneTapResponse | null { + if (!isErrorWithCode(error)) { + return null; + } + + if (error.code === 'SIGN_IN_CANCELLED') { + return { type: 'cancelled', data: null }; + } + + if (error.code === 'NO_SAVED_CREDENTIAL_FOUND') { + return { type: 'noSavedCredentialFound', data: null }; + } + + return null; +} + export const ClerkGoogleOneTapSignIn = { ... async signIn(params?: SignInParams): Promise<OneTapResponse> { try { return await getNativeModule().signIn(params ?? {}); } catch (error) { - if (isErrorWithCode(error)) { - if (error.code === 'SIGN_IN_CANCELLED') { - return { type: 'cancelled', data: null }; - } - if (error.code === 'NO_SAVED_CREDENTIAL_FOUND') { - return { type: 'noSavedCredentialFound', data: null }; - } - } + const mapped = mapErrorToOneTapResponse(error); + if (mapped) return mapped; throw error; } }, ... async createAccount(params?: CreateAccountParams): Promise<OneTapResponse> { try { return await getNativeModule().createAccount(params ?? {}); } catch (error) { - if (isErrorWithCode(error)) { - if (error.code === 'SIGN_IN_CANCELLED') { - return { type: 'cancelled', data: null }; - } - if (error.code === 'NO_SAVED_CREDENTIAL_FOUND') { - return { type: 'noSavedCredentialFound', data: null }; - } - } + const mapped = mapErrorToOneTapResponse(error); + if (mapped) return mapped; throw error; } }, ... async presentExplicitSignIn(params?: ExplicitSignInParams): Promise<OneTapResponse> { try { return await getNativeModule().presentExplicitSignIn(params ?? {}); } catch (error) { - if (isErrorWithCode(error)) { - if (error.code === 'SIGN_IN_CANCELLED') { - return { type: 'cancelled', data: null }; - } - } + const mapped = mapErrorToOneTapResponse(error); + if (mapped) return mapped; throw error; } }, ... };Not mandatory, but it keeps the behavior consistent as you evolve the error surface.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
.changeset/brave-clouds-swim.md(1 hunks)packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
All code must pass ESLint checks with the project's configuration
Files:
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use Prettier for consistent code formatting
Files:
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
packages/**/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
TypeScript is required for all packages
Files:
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Follow established naming conventions (PascalCase for components, camelCase for variables)
Prefer importing types from
@clerk/shared/typesinstead of the deprecated@clerk/typesalias
Files:
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
packages/**/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
packages/**/src/**/*.{ts,tsx,js,jsx}: Maintain comprehensive JSDoc comments for public APIs
Use tree-shaking friendly exports
Validate all inputs and sanitize outputs
All public APIs must be documented with JSDoc
Use dynamic imports for optional features
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Implement proper logging with different levels
Files:
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.ts?(x)
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use proper TypeScript error types
Files:
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)
**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoidanytype - preferunknownwhen type is uncertain, then narrow with type guards
Implement type guards forunknowntypes using the patternfunction isType(value: unknown): value is Type
Useinterfacefor object shapes that might be extended
Usetypefor unions, primitives, and computed types
Preferreadonlyproperties for immutable data structures
Useprivatefor internal implementation details in classes
Useprotectedfor inheritance hierarchies
Usepublicexplicitly for clarity in public APIs
Use mixins for shared behavior across unrelated classes in TypeScript
Use generic constraints with bounded type parameters like<T extends { id: string }>
Use utility types likeOmit,Partial, andPickfor data transformation instead of manual type construction
Use discriminated unions instead of boolean flags for state management and API responses
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation at the type level
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Document functions with JSDoc comments including @param, @returns, @throws, and @example tags
Create custom error classes that extend Error for specific error types
Use the Result pattern for error handling instead of throwing exceptions
Use optional chaining (?.) and nullish coalescing (??) operators for safe property access
Let TypeScript infer obvious types to reduce verbosity
Useconst assertionswithas constfor literal types
Usesatisfiesoperator for type checking without widening types
Declare readonly arrays and objects for immutable data structures
Use spread operator and array spread for immutable updates instead of mutations
Use lazy loading for large types...
Files:
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (34)
- GitHub Check: Integration Tests (nextjs, chrome, 15)
- GitHub Check: Integration Tests (machine, chrome)
- GitHub Check: Integration Tests (custom, chrome)
- GitHub Check: Integration Tests (nextjs, chrome, 16)
- GitHub Check: Integration Tests (nextjs, chrome, 15, RQ)
- GitHub Check: Integration Tests (quickstart, chrome, 16)
- GitHub Check: Integration Tests (quickstart, chrome, 15)
- GitHub Check: Integration Tests (machine, chrome, RQ)
- GitHub Check: Integration Tests (nextjs, chrome, 14)
- GitHub Check: Integration Tests (sessions, chrome)
- GitHub Check: Integration Tests (elements, chrome)
- GitHub Check: Integration Tests (vue, chrome)
- GitHub Check: Integration Tests (billing, chrome, RQ)
- GitHub Check: Integration Tests (handshake, chrome)
- GitHub Check: Integration Tests (express, chrome)
- GitHub Check: Integration Tests (tanstack-react-start, chrome)
- GitHub Check: Integration Tests (nuxt, chrome)
- GitHub Check: Integration Tests (react-router, chrome)
- GitHub Check: Integration Tests (generic, chrome)
- GitHub Check: Integration Tests (ap-flows, chrome)
- GitHub Check: Integration Tests (billing, chrome)
- GitHub Check: Integration Tests (sessions:staging, chrome)
- GitHub Check: Integration Tests (localhost, chrome)
- GitHub Check: Integration Tests (astro, chrome)
- GitHub Check: Integration Tests (handshake:staging, chrome)
- GitHub Check: Integration Tests (expo-web, chrome)
- GitHub Check: Unit Tests (22, shared, clerk-js, RQ)
- GitHub Check: Publish with pkg-pr-new
- GitHub Check: Unit Tests (22, **)
- GitHub Check: Static analysis
- GitHub Check: Formatting | Dedupe | Changeset
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: semgrep-cloud-platform/scan
- GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (3)
.changeset/brave-clouds-swim.md (1)
1-5: Changeset entry is well-formed and correctly categorized.The file follows the standard changeset format with the appropriate minor version bump for @clerk/clerk-expo and provides a clear, concise description of the feature addition. The version level correctly reflects the non-breaking nature of the new native Google Sign-In support.
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts (2)
33-52: Response discriminant type guards are concise and correct.The three guards (
isCancelledResponse,isNoSavedCredentialFoundResponse,isSuccessResponse) cleanly narrowOneTapResponsebased on thetypediscriminant and should work well with a discriminated union in./types. This is a nice, ergonomic API surface for consumers.
195-205: Type re-exports are clean and tree-shaking–friendly.Re-exporting the public types from
./typesin a groupedexport type { … }keeps the surface area discoverable and maintains good tree-shaking characteristics, while also aligning with the JSDoc’d public API.
Description
This PR implements native Google Sign-In support for Expo iOS and Android apps using the
@react-native-google-signin/google-signinlibrary. This provides a seamless native authentication experience for users signing in with their Google accounts.What's New
useSignInWithGoogle()- Provides native Google Sign-In functionality for iOS and AndroiduseSignInWithGoogle.ios.ts) and Android (useSignInWithGoogle.android.ts)unsafeMetadataduring authenticationKey Features
SIGN_IN_CANCELLEDand-5)Files Changed
packages/expo/src/hooks/useSignInWithGoogle.ts- Cross-platform stub with helpful error messagepackages/expo/src/hooks/useSignInWithGoogle.ios.ts- iOS-specific implementationpackages/expo/src/hooks/useSignInWithGoogle.android.ts- Android-specific implementationpackages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts- Comprehensive unit testspackages/expo/src/hooks/index.ts- Export new hookpackages/expo/package.json- Add optional peer dependencypackages/expo/vitest.setup.mts- Add test mocks for expo-modules-coreImplementation Details
iOS Implementation:
expo-apple-authenticationfor native Sign-Inoauth_token_applestrategyAndroid Implementation:
@react-native-google-signin/google-signinlibrarygoogle_one_tapstrategy with ID tokenBoth implementations:
{ createdSessionId, setActive, signIn, signUp }unsafeMetadataparameterBreaking Changes
None - this is a new feature.
Test Plan
pnpm test --filter @clerk/clerk-expo)pnpm build)Documentation
Related documentation PR: [Link to clerk-docs PR]
Checklist
pnpm testruns as expected.pnpm buildruns as expected.Type of change
Summary by CodeRabbit
New Features
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.