Skip to content

Conversation

@chriscanin
Copy link
Contributor

@chriscanin chriscanin commented Nov 12, 2025

Description

This PR implements native Google Sign-In support for Expo iOS and Android apps using the @react-native-google-signin/google-signin library. This provides a seamless native authentication experience for users signing in with their Google accounts.

What's New

  • New Hook: useSignInWithGoogle() - Provides native Google Sign-In functionality for iOS and Android
    • Platform-specific implementations for iOS (useSignInWithGoogle.ios.ts) and Android (useSignInWithGoogle.android.ts)
    • Automatic handling of sign-in/sign-up transfer flow
    • Graceful error handling including user cancellation detection
    • Support for custom unsafeMetadata during authentication

Key Features

  • Native UI: Uses platform-native Google Sign-In UI instead of web-based OAuth flow
  • Automatic Flow Detection: Seamlessly handles both new user sign-up and existing user sign-in
  • Error Handling: Comprehensive error handling for:
    • Missing credentials
    • User cancellation (codes SIGN_IN_CANCELLED and -5)
    • Play Services availability (Android)
  • Environment Variable Support: Reads credentials from both Expo config and environment variables
  • Full Test Coverage: Complete unit tests with 100% coverage for the new hook

Files Changed

  • packages/expo/src/hooks/useSignInWithGoogle.ts - Cross-platform stub with helpful error message
  • packages/expo/src/hooks/useSignInWithGoogle.ios.ts - iOS-specific implementation
  • packages/expo/src/hooks/useSignInWithGoogle.android.ts - Android-specific implementation
  • packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts - Comprehensive unit tests
  • packages/expo/src/hooks/index.ts - Export new hook
  • packages/expo/package.json - Add optional peer dependency
  • packages/expo/vitest.setup.mts - Add test mocks for expo-modules-core

Implementation Details

iOS Implementation:

  • Uses expo-apple-authentication for native Sign-In
  • Supports oauth_token_apple strategy
  • Handles identity token exchange

Android Implementation:

  • Uses @react-native-google-signin/google-signin library
  • Configures with Web Client ID and Android Client ID
  • Checks Google Play Services availability
  • Handles google_one_tap strategy with ID token

Both implementations:

  • Automatically handle transfer flow when user doesn't exist
  • Return consistent API: { createdSessionId, setActive, signIn, signUp }
  • Support optional unsafeMetadata parameter

Breaking Changes

None - this is a new feature.

Test Plan

  1. Unit tests pass (pnpm test --filter @clerk/clerk-expo)
  2. Build succeeds (pnpm build)
  3. All 9 test cases pass including:
    • Successful sign-in for existing users
    • Transfer flow for new users
    • User cancellation handling (both error codes)
    • Play Services availability check (Android)
    • Missing ID token validation
    • Missing credentials validation
    • Clerk not loaded handling

Documentation

Related documentation PR: [Link to clerk-docs PR]

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • JSDoc comments have been added for the new hook and all exported functions
  • Documentation has been created (see related PR in clerk-docs)

Type of change

  • 🌟 New feature

Summary by CodeRabbit

  • New Features

    • Native Google One Tap Sign-In for Expo (Android & iOS): configure, sign-in, create-account, explicit sign-in, sign-out, and structured responses.
    • Public Expo hook to start Google authentication with nonce handling and platform-specific implementations plus a web stub.
    • Expo config plugin to optionally add iOS URL scheme; native module registration and platform mappings added.
    • iOS podspec and Android manifest/Gradle support included.
  • Tests

    • Comprehensive Android sign-in tests covering success, transfer, cancellation, and error cases.
  • Chores

    • Package exports and published files updated to include native integrations and plugin.

✏️ Tip: You can customize this high-level summary in your review settings.

@chriscanin chriscanin self-assigned this Nov 12, 2025
@changeset-bot
Copy link

changeset-bot bot commented Nov 12, 2025

🦋 Changeset detected

Latest commit: bc11a78

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@clerk/clerk-expo Minor

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

@vercel
Copy link

vercel bot commented Nov 12, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
clerk-js-sandbox Ready Ready Preview Comment Nov 26, 2025 8:47pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 12, 2025

Walkthrough

Adds 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

Cohort / File(s) Summary
Android Native Module
packages/expo/android/build.gradle, packages/expo/android/src/main/AndroidManifest.xml, packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt
New Android Gradle config and Expo module: plugins, SDK/JDK/Kotlin targets, dependencies (expo-modules-core, AndroidX Credentials, Google Identity, coroutines), minimal manifest, and a Kotlin module implementing configure, signIn, createAccount, presentExplicitSignIn, signOut, credential parsing, and custom exceptions.
iOS Native Module & Podspec
packages/expo/ios/ClerkGoogleSignIn.podspec, packages/expo/ios/ClerkGoogleSignInModule.swift
New CocoaPods podspec and Swift Expo module: pod metadata sourced from package.json, platform & dependency declarations, and a Swift module exposing configure, signIn, createAccount, presentExplicitSignIn, signOut, result mapping, view controller resolution, and custom exceptions.
Expo Module Config & Plugin
packages/expo/expo-module.config.json, packages/expo/app.plugin.js
Adds expo-module.config.json mapping native modules for Android/iOS and an Expo run-once config plugin that injects an iOS URL scheme (CFBundleURLTypes) when CLERK_GOOGLE_IOS_URL_SCHEME is provided.
TypeScript Bridge & Types
packages/expo/src/google-one-tap/types.ts, packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts, packages/expo/src/google-one-tap/index.ts
New typed API for Google One Tap: config/sign-in/create/account/explicit types, user/response/error shapes, a lazy JS bridge to native module, runtime type guards, and barrel exports.
React Hooks (platform-specific + fallback)
packages/expo/src/hooks/useSignInWithGoogle.ts, packages/expo/src/hooks/useSignInWithGoogle.android.ts, packages/expo/src/hooks/useSignInWithGoogle.ios.ts, packages/expo/src/hooks/index.ts
Adds platform-specific useSignInWithGoogle hooks: read client IDs, configure native bridge, generate SHA‑256 nonce, present explicit sign-in, and handle sign-in ↔ sign-up ↔ transfer flows with Clerk APIs; adds unsupported‑platform stub and re-export in hooks index.
Tests, packaging, and setup
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts, packages/expo/package.json, packages/expo/vitest.setup.mts, .changeset/brave-clouds-swim.md
Adds comprehensive tests for the sign-in hook, updates package.json exports/files and dev/peer dependencies (expo-constants, expo-modules-core), augments vitest setup with global mocks, and includes a changeset noting the native Google Sign-In addition.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Heterogeneous cross‑layer changes (Kotlin, Swift, JS/TS, config, tests).
  • Review focus:
    • Android/Kotlin: credential/request construction, main-thread and activity handling, error-code mapping.
    • iOS/Swift: GIDSignIn configuration, presenting view controller selection, token/result mapping.
    • Hooks: nonce generation/hash correctness, transfer vs sign-in branching, Clerk error mapping.
    • Tests and package metadata: test mocks, package.json exports/files, podspec source/version consistency, and Expo plugin URL type deduplication.

Poem

🐰 I hopped through native code and TypeScript bright,
Configured a nonce and hashed it just right.
From Android mounds to iOS hilltops high,
One‑Tap sign‑ins now flutter by.
A joyful hop — auth blooms under the sky! 🌿

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.06% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: implementing Google Sign-In support for Android and iOS in the Expo package.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chris/mobile-289-expo-google-universal-sign-in

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

- 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.
@chriscanin chriscanin marked this pull request as ready for review November 25, 2025 21:08
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 semantics

The expo mock and guarded initialization are fine for tests. The only nuance is defaulting globalThis.__DEV__ to false, 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 to true (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 defaults

The use of safeExtGet and the overall Android/Kotlin configuration look good for an Expo module. To avoid divergence over time, you may want to keep the fallback compileSdk, targetSdk, and minSdk values 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 brittleness

Right now the react-native and expo-modules-core mocks return only the pieces you need (Platform and EventEmitter). If useSignInWithGoogle.android later imports additional exports from these modules, those will silently become undefined in tests. To make the suite more robust, consider using partial mocks that spread the real module and override just what you need, e.g. via vi.importActual('react-native') and overriding Platform.OS.

packages/expo/app.plugin.js (1)

9-44: Config plugin logic is sound; optionally derive plugin version from package.json

The plugin correctly short‑circuits when no iOS URL scheme is configured and safely appends a new CFBundleURLTypes entry only when needed, avoiding duplicates. As a small polish, you might consider wiring createRunOncePlugin’s version argument from package.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 in s.source for reproducible builds

The podspec wiring (metadata, platform, Swift version, dependencies, and *.swift source file glob) looks appropriate for this module. If this spec is ever published to a CocoaPods spec repo or consumed via the :git source, you may want to add a :tag tied to package['version'] in s.source to 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.ts barrels 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 ./ClerkGoogleOneTapSignIn and ./types to 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.

  • SignUpUnsafeMetadata is Record<string, any> and StartGoogleAuthenticationFlowReturnType uses any for setActive, signIn, and signUp. Guidelines say to avoid any; you already have precise types in the shared stub (useSignInWithGoogle.ts).
  • You’re also re‑declaring StartGoogleAuthenticationFlowParams/ReturnType here, which risks drifting from the shared definition over time.

Consider instead:

  • Defining SignUpUnsafeMetadata once (e.g. in the shared stub) as Record<string, unknown>; and
  • Importing the shared types here via a type‑only import, reusing the same SetActive, SignInResource, and SignUpResource types.

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 UIViewController is appropriate for Expo modules.
  • handleSignInResult correctly special-cases user cancellation via kGIDSignInErrorDomain + GIDSignInError.canceled and otherwise surfaces a structured success payload (type: "success", idToken, and user profile fields) that matches the JS bridge expectations.
  • Guarding on clientId and returning a NotConfiguredException from sign-in methods is a good explicit failure mode.

Minor cleanliness points:

  • hostedDomain and autoSelectEnabled in ConfigureParams are currently unused, as is NoSavedCredentialException. 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/types instead of the deprecated @clerk/types alias.


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 unknown types.

+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 for signIn before calling create.

If isSignInLoaded is true but signIn is unexpectedly undefined, calling signIn.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 @platform annotation may be misleading.

The documentation states @platform Android, but this file serves as a cross-platform bridge (no .android.ts suffix). 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 the CredentialManager instance.

The credentialManager getter creates a new instance on each access. While CredentialManager.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 for webClientId may pass validation.

ConfigureParams.webClientId defaults to an empty string. The configure function stores this value directly without validation, which means an empty webClientId could 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.

📥 Commits

Reviewing files that changed from the base of the PR and between b944ff3 and 6811d5f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is 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.ts
  • packages/expo/src/hooks/useSignInWithGoogle.android.ts
  • packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts
  • packages/expo/app.plugin.js
  • packages/expo/src/hooks/useSignInWithGoogle.ios.ts
  • packages/expo/src/google-one-tap/types.ts
  • packages/expo/src/google-one-tap/index.ts
  • packages/expo/src/hooks/index.ts
  • 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/hooks/useSignInWithGoogle.ts
  • packages/expo/src/hooks/useSignInWithGoogle.android.ts
  • packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts
  • packages/expo/app.plugin.js
  • packages/expo/package.json
  • packages/expo/src/hooks/useSignInWithGoogle.ios.ts
  • packages/expo/src/google-one-tap/types.ts
  • packages/expo/src/google-one-tap/index.ts
  • packages/expo/expo-module.config.json
  • packages/expo/src/hooks/index.ts
  • 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/hooks/useSignInWithGoogle.ts
  • packages/expo/src/hooks/useSignInWithGoogle.android.ts
  • packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts
  • packages/expo/src/hooks/useSignInWithGoogle.ios.ts
  • packages/expo/src/google-one-tap/types.ts
  • packages/expo/src/google-one-tap/index.ts
  • packages/expo/src/hooks/index.ts
  • 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/types instead of the deprecated @clerk/types alias

Files:

  • packages/expo/src/hooks/useSignInWithGoogle.ts
  • packages/expo/src/hooks/useSignInWithGoogle.android.ts
  • packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts
  • packages/expo/app.plugin.js
  • packages/expo/src/hooks/useSignInWithGoogle.ios.ts
  • packages/expo/src/google-one-tap/types.ts
  • packages/expo/src/google-one-tap/index.ts
  • packages/expo/src/hooks/index.ts
  • 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/hooks/useSignInWithGoogle.ts
  • packages/expo/src/hooks/useSignInWithGoogle.android.ts
  • packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts
  • packages/expo/src/hooks/useSignInWithGoogle.ios.ts
  • packages/expo/src/google-one-tap/types.ts
  • packages/expo/src/google-one-tap/index.ts
  • packages/expo/src/hooks/index.ts
  • 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/hooks/useSignInWithGoogle.ts
  • packages/expo/src/hooks/useSignInWithGoogle.android.ts
  • packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts
  • packages/expo/src/hooks/useSignInWithGoogle.ios.ts
  • packages/expo/src/google-one-tap/types.ts
  • packages/expo/src/google-one-tap/index.ts
  • packages/expo/src/hooks/index.ts
  • 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
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Implement type guards for unknown types using the pattern function isType(value: unknown): value is Type
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details in classes
Use protected for inheritance hierarchies
Use public explicitly 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 like Omit, Partial, and Pick for 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
Use const assertions with as const for literal types
Use satisfies operator 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.ts
  • packages/expo/src/hooks/useSignInWithGoogle.android.ts
  • packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts
  • packages/expo/src/hooks/useSignInWithGoogle.ios.ts
  • packages/expo/src/google-one-tap/types.ts
  • packages/expo/src/google-one-tap/index.ts
  • packages/expo/src/hooks/index.ts
  • packages/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 packages

All 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.ts
  • packages/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 acceptable

A 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: New useSignInWithGoogle export aligns with existing hook surface

Re‑exporting useSignInWithGoogle from the hooks index keeps the Expo hook API consistent with Apple/SSO/OAuth exports and remains tree‑shaking friendly. Just ensure useSignInWithGoogle doesn’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 names

The config structure and module identifiers for Android and iOS look correct for Expo modules and line up with the described ClerkGoogleSignInModule implementations. 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 / isSignUpLoaded and 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) plus ClerkGoogleOneTapSignIn.configure aligns with the native module signature.
  • Nonce generation + SHA‑256 hashing via expo-crypto and passing the hashed nonce into presentExplicitSignIn is the right pattern.
  • Transfer flow is covered twice: first via signIn.firstFactorVerification.status === 'transferable', then via the external_account_not_found Clerk error path, which is a good safety net.
  • Cancellation from native (SIGN_IN_CANCELLED) and non‑success One‑Tap responses both gracefully resolve with createdSessionId: null and 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-tap with both types and default matches the rest of the package and keeps the TS surface available.
  • Including android, ios, expo-module.config.json, and app.plugin.js in files ensures the native module and config plugin ship correctly.
  • Adding expo-constants / expo-modules-core as both devDependencies and peerDependencies (with expo-constants optional) matches how other Expo modules are usually modeled.

Please just double‑check that the >=12 / >=3.0.0 peer 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 useSSO with strategy: "oauth_google", which is very helpful.
  • The explicit return type on useSignInWithGoogle and the StartGoogleAuthenticationFlowReturnType structure (session id + optional setActive, signIn, signUp) matches what the platform-specific hooks are returning at runtime.
  • Using errorThrower.throw to produce a consistent runtime error for unsupported platforms keeps the API predictable.

Once the SignUpUnsafeMetadata type is added here and reused in the .ios / .android variants, 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 Type return 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: GoogleUser is re-exported but not imported.

The GoogleUser type 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 type field for OneTapResponse. This enables exhaustive type checking and clean type narrowing with the type guards in the bridge module.


159-161: Consider using interface vs type for error interface.

The GoogleSignInError interface extending Error follows the coding guideline to use interface for 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 from id field.

Note that googleIdTokenCredential.id returns the user's email address (used for credential matching), not the unique Google user ID (sub claim). This is consistent with how the type is documented but worth being aware of - if you need the actual sub claim, you'd need to decode the ID token.

Comment on lines +93 to +97
// 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 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 -150

Repository: 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 3

Repository: 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/expo

Repository: 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 3

Repository: 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 -20

Repository: 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.ts

Repository: 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 2

Repository: 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 -10

Repository: 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).

Comment on lines 1 to 14
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;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 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.ts

Repository: 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 tsx

Repository: 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 -20

Repository: 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 2

Repository: 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 -10

Repository: 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 -20

Repository: 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 -20

Repository: 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.ts

Repository: 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.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 25, 2025

Open in StackBlitz

@clerk/agent-toolkit

npm i https://pkg.pr.new/@clerk/agent-toolkit@7208

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@7208

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@7208

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@7208

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@7208

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@7208

@clerk/elements

npm i https://pkg.pr.new/@clerk/elements@7208

@clerk/clerk-expo

npm i https://pkg.pr.new/@clerk/clerk-expo@7208

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@7208

@clerk/express

npm i https://pkg.pr.new/@clerk/express@7208

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@7208

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@7208

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@7208

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@7208

@clerk/clerk-react

npm i https://pkg.pr.new/@clerk/clerk-react@7208

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@7208

@clerk/remix

npm i https://pkg.pr.new/@clerk/remix@7208

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@7208

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@7208

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@7208

@clerk/themes

npm i https://pkg.pr.new/@clerk/themes@7208

@clerk/types

npm i https://pkg.pr.new/@clerk/types@7208

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@7208

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@7208

commit: bc11a78

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 hashedNonce should be replaced with the plain nonce.

packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts (1)

119-127: Test assertions may pass incorrectly due to mock mismatch.

Given that mocks.GoogleSignin.hasPlayServices and mocks.GoogleSignin.signIn are never called by the actual hook (which uses ClerkGoogleOneTapSignIn), 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 (ClerkGoogleOneTapSignIn via expo-modules-core).

🧹 Nitpick comments (5)
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts (2)

117-158: Consider extracting shared error-handling logic.

The signIn and createAccount methods 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, iOS or 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 the credentialManager property 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 mainScope is never cancelled, which could lead to coroutine leaks if the module is destroyed while operations are in-flight. Consider implementing OnDestroy to 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 isClerkError and hasExternalAccountNotFoundError checks 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 14a5ae6 and 7a9f7de.

📒 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.ts
  • packages/expo/src/hooks/useSignInWithGoogle.android.ts
  • 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/hooks/__tests__/useSignInWithGoogle.test.ts
  • packages/expo/src/hooks/useSignInWithGoogle.android.ts
  • 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/hooks/__tests__/useSignInWithGoogle.test.ts
  • packages/expo/src/hooks/useSignInWithGoogle.android.ts
  • 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/types instead of the deprecated @clerk/types alias

Files:

  • packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts
  • packages/expo/src/hooks/useSignInWithGoogle.android.ts
  • 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/hooks/__tests__/useSignInWithGoogle.test.ts
  • packages/expo/src/hooks/useSignInWithGoogle.android.ts
  • packages/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.ts
  • packages/expo/src/hooks/useSignInWithGoogle.android.ts
  • packages/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
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Implement type guards for unknown types using the pattern function isType(value: unknown): value is Type
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details in classes
Use protected for inheritance hierarchies
Use public explicitly 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 like Omit, Partial, and Pick for 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
Use const assertions with as const for literal types
Use satisfies operator 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.ts
  • packages/expo/src/hooks/useSignInWithGoogle.android.ts
  • packages/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 isErrorWithCode function correctly narrows unknown to 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.id is 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 id and email fields is not ideal. The id field should contain the user's unique Google identifier (the sub claim from the JWT), while email should contain the email address. The idToken is 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 idToken JWT claims for the id field, 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 the external_account_not_found fallback path.

This test addresses the previously noted missing coverage. It properly validates that when signIn.create throws an external_account_not_found error, the hook falls back to signUp.create with 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 startGoogleAuthenticationFlow function. 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';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 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 -20

Repository: 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 -20

Repository: 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 2

Repository: 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 -20

Repository: 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 -50

Repository: 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 f

Repository: 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 -30

Repository: 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.ts

Repository: 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.ts

Repository: 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 -50

Repository: 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 -50

Repository: 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 2

Repository: 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 -100

Repository: 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 -10

Repository: 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 -10

Repository: 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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 ClerkGoogleSignInNativeModule interface and getNativeModule lazy initializer are well-typed and align with Expo’s requireNativeModule usage. 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 to getNativeModule() 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: Tighten isErrorWithCode guard or relax the return type to avoid lying about message.

Right now the guard promises error is { code: string; message: string } but only validates the code property. That means callers could safely access error.message as a string even when it’s missing or non‑string at runtime.

Either of these adjustments would make the guard accurate:

  • Option A – Validate message as 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 code is guaranteed (simpler, since you don’t use message):
-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 ClerkGoogleOneTapSignIn methods are well-typed, documented, and provide a clean Promise‑based API. params?: … plus params ?? {} is a nice ergonomic choice for callers, and the mapping of known error codes (SIGN_IN_CANCELLED, NO_SAVED_CREDENTIAL_FOUND) to structured OneTapResponse shapes is clear.

There is some duplication in how you translate error.code into { type: 'cancelled' | 'noSavedCredentialFound', data: null } across signIn, createAccount, and presentExplicitSignIn. 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 7724619 and bc11a78.

📒 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/types instead of the deprecated @clerk/types alias

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
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Implement type guards for unknown types using the pattern function isType(value: unknown): value is Type
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details in classes
Use protected for inheritance hierarchies
Use public explicitly 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 like Omit, Partial, and Pick for 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
Use const assertions with as const for literal types
Use satisfies operator 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 narrow OneTapResponse based on the type discriminant 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 ./types in a grouped export type { … } keeps the surface area discoverable and maintains good tree-shaking characteristics, while also aligning with the JSDoc’d public API.

@chriscanin chriscanin changed the title feat(expo): Implement Google Sign-In support for Android and iOS feat(component): Implement Google Sign-In support for Android and iOS Nov 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants