- Notifications
You must be signed in to change notification settings - Fork 419
feat(expo): Implement Google Sign-In support for Android and iOS #7208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: b192d31 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
| The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds native Google Sign-In support for Expo across Android and iOS. Android: new Gradle build script, minimal AndroidManifest, and a Kotlin Expo module using Credential Manager. iOS: Swift Expo module and a CocoaPods podspec. Adds a TypeScript bridge (ClerkGoogleOneTapSignIn), shared and platform-specific useSignInWithGoogle hooks, types, and unit tests. Introduces an Expo config plugin to inject an iOS URL scheme, updates package.json exports/files/peer‑dev deps, and adds expo-module.config.json and related packaging files. Pre-merge checks❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: Repository YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Disabled knowledge base sources:
📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ 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). (27)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Nitpick comments (15)
packages/expo/vitest.setup.mts (1)
6-18: Expo and__DEV__mocks look good; consider matching RN/Expo dev semanticsThe expo mock and guarded initialization are fine for tests. The only nuance is defaulting
globalThis.__DEV__tofalse, which makes the test runtime behave more like production than a typical React Native/Expo dev build. If any code under test gates behavior on__DEV__, you may want to consider defaulting it totrue(or reading from an env flag) so tests better mirror the usual development environment.packages/expo/android/build.gradle (1)
7-21: Gradle config is solid; consider aligning fallback SDK versions with Expo’s defaultsThe use of
safeExtGetand the overall Android/Kotlin configuration look good for an Expo module. To avoid divergence over time, you may want to keep the fallbackcompileSdk,targetSdk, andminSdkvalues in sync with whatever versions the root Expo setup uses (so that a misconfigured root project doesn’t silently build against an unexpected API level).packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts (1)
31-43: Module mocks are minimal; consider partial mocks to reduce brittlenessRight now the
react-nativeandexpo-modules-coremocks return only the pieces you need (PlatformandEventEmitter). IfuseSignInWithGoogle.androidlater imports additional exports from these modules, those will silently becomeundefinedin tests. To make the suite more robust, consider using partial mocks that spread the real module and override just what you need, e.g. viavi.importActual('react-native')and overridingPlatform.OS.packages/expo/app.plugin.js (1)
9-44: Config plugin logic is sound; optionally derive plugin version from package.jsonThe plugin correctly short‑circuits when no iOS URL scheme is configured and safely appends a new
CFBundleURLTypesentry only when needed, avoiding duplicates. As a small polish, you might consider wiringcreateRunOncePlugin’s version argument frompackage.json(similar to the podspec) to keep the plugin version in sync with the package, but that’s not required for correctness.packages/expo/ios/ClerkGoogleSignIn.podspec (1)
5-21: Podspec looks correct; consider pinning a tag ins.sourcefor reproducible buildsThe podspec wiring (metadata, platform, Swift version, dependencies, and
*.swiftsource file glob) looks appropriate for this module. If this spec is ever published to a CocoaPods spec repo or consumed via the:gitsource, you may want to add a:tagtied topackage['version']ins.sourceto make builds reproducible across releases.packages/expo/src/google-one-tap/index.ts (1)
1-21: Barrel index.ts goes against “avoid barrel files” guideline (low risk here).This file is a classic barrel re-export. It’s clean and tree‑shaking friendly, but it does conflict with the guideline to avoid
index.tsbarrels because of potential circular dependencies. Given it’s a leaf-ish submodule, the risk is low, but if this surface grows or starts being re-exported further up, consider switching callers to import directly from./ClerkGoogleOneTapSignInand./typesto keep dependency edges explicit.packages/expo/src/hooks/useSignInWithGoogle.ios.ts (1)
8-20: Tighten typings and avoid duplicating public API types in the iOS-specific file.
SignUpUnsafeMetadataisRecord<string, any>andStartGoogleAuthenticationFlowReturnTypeusesanyforsetActive,signIn, andsignUp. Guidelines say to avoidany; you already have precise types in the shared stub (useSignInWithGoogle.ts).- You’re also re‑declaring
StartGoogleAuthenticationFlowParams/ReturnTypehere, which risks drifting from the shared definition over time.Consider instead:
- Defining
SignUpUnsafeMetadataonce (e.g. in the shared stub) asRecord<string, unknown>; and- Importing the shared types here via a type‑only import, reusing the same
SetActive,SignInResource, andSignUpResourcetypes.For example:
-import { useSignIn, useSignUp } from '@clerk/clerk-react'; +import { useSignIn, useSignUp } from '@clerk/clerk-react'; +import type { SetActive, SignInResource, SignUpResource } from '@clerk/shared/types'; -// Type for unsafe metadata that can be attached to sign-ups -type SignUpUnsafeMetadata = Record<string, any>; +// Type for unsafe metadata that can be attached to sign-ups +type SignUpUnsafeMetadata = Record<string, unknown>; -export type StartGoogleAuthenticationFlowReturnType = { - createdSessionId: string | null; - setActive?: any; - signIn?: any; - signUp?: any; -}; +export type StartGoogleAuthenticationFlowReturnType = { + createdSessionId: string | null; + setActive?: SetActive; + signIn?: SignInResource; + signUp?: SignUpResource; +};This keeps the iOS implementation aligned with the public TS surface and preserves type safety.
packages/expo/ios/ClerkGoogleSignInModule.swift (1)
4-165: Native iOS Google Sign-In flow looks correct; consider trimming or wiring unused fields.
- Running all GoogleSignIn calls on the main queue and deriving the top-most presenting
UIViewControlleris appropriate for Expo modules.handleSignInResultcorrectly special-cases user cancellation viakGIDSignInErrorDomain+GIDSignInError.canceledand otherwise surfaces a structured success payload (type: "success",idToken, and user profile fields) that matches the JS bridge expectations.- Guarding on
clientIdand returning aNotConfiguredExceptionfrom sign-in methods is a good explicit failure mode.Minor cleanliness points:
hostedDomainandautoSelectEnabledinConfigureParamsare currently unused, as isNoSavedCredentialException. Either wire them into the GoogleSignIn configuration/flows or drop them to avoid confusion about behavior that doesn’t yet exist.Also applies to: 170-182, 209-212
packages/expo/src/hooks/useSignInWithGoogle.android.ts (4)
2-2: Consider importing types from@clerk/shared/types.As per coding guidelines, prefer importing types from
@clerk/shared/typesinstead of the deprecated@clerk/typesalias.
65-67: Add explicit return type annotation.As per coding guidelines, always define explicit return types for functions, especially public APIs.
- async function startGoogleAuthenticationFlow( - startGoogleAuthenticationFlowParams?: StartGoogleAuthenticationFlowParams, - ): Promise<StartGoogleAuthenticationFlowReturnType> { + async function startGoogleAuthenticationFlow( + startGoogleAuthenticationFlowParams?: StartGoogleAuthenticationFlowParams, + ): Promise<StartGoogleAuthenticationFlowReturnType> {The return type is already specified - this looks correct. No change needed.
150-165: Extract Clerk error type guard for improved readability.The inline type assertions are verbose and repeated. Consider extracting a type guard function to improve readability and maintainability, following the coding guideline to implement type guards for
unknowntypes.+function isClerkErrorWithCode(error: unknown, code: string): boolean { + if ( + error && + typeof error === 'object' && + 'clerkError' in error && + (error as { clerkError: boolean }).clerkError === true && + 'errors' in error && + Array.isArray((error as { errors: unknown[] }).errors) + ) { + return (error as { errors: Array<{ code: string }> }).errors.some( + err => err.code === code, + ); + } + return false; +} + // In the catch block: - const isClerkError = - signInError && - typeof signInError === 'object' && - 'clerkError' in signInError && - (signInError as { clerkError: boolean }).clerkError === true; - - const hasExternalAccountNotFoundError = - signInError && - typeof signInError === 'object' && - 'errors' in signInError && - Array.isArray((signInError as { errors: unknown[] }).errors) && - (signInError as { errors: Array<{ code: string }> }).errors.some( - err => err.code === 'external_account_not_found', - ); - - if (isClerkError && hasExternalAccountNotFoundError) { + if (isClerkErrorWithCode(signInError, 'external_account_not_found')) {
118-123: Add null check forsignInbefore callingcreate.If
isSignInLoadedis true butsignInis unexpectedly undefined, callingsignIn.create()will throw. Consider adding a defensive null check.+ if (!signIn || !signUp) { + return { + createdSessionId: null, + setActive, + signIn, + signUp, + }; + } + try { // Try to sign in with the Google One Tap strategy await signIn.create({packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts (1)
65-91: JSDoc@platformannotation may be misleading.The documentation states
@platform Android, but this file serves as a cross-platform bridge (no.android.tssuffix). The native module handles platform-specific logic. Consider updating the docs to reflect cross-platform support or adding a note about iOS availability.packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt (2)
74-75: Consider caching theCredentialManagerinstance.The
credentialManagergetter creates a new instance on each access. WhileCredentialManager.create()is lightweight, caching the instance could improve efficiency for repeated sign-in attempts.+ private var _credentialManager: CredentialManager? = null + private val credentialManager: CredentialManager - get() = CredentialManager.create(context) + get() = _credentialManager ?: CredentialManager.create(context).also { _credentialManager = it }
26-36: Empty string default forwebClientIdmay pass validation.
ConfigureParams.webClientIddefaults to an empty string. Theconfigurefunction stores this value directly without validation, which means an emptywebClientIdcould be set. While the sign-in will fail later with a less clear error, consider validating early.// Configure the module Function("configure") { params: ConfigureParams -> + if (params.webClientId.isBlank()) { + throw GoogleSignInException("webClientId is required and cannot be empty") + } webClientId = params.webClientId hostedDomain = params.hostedDomain autoSelectEnabled = params.autoSelectEnabled ?: false }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (17)
packages/expo/android/build.gradle(1 hunks)packages/expo/android/src/main/AndroidManifest.xml(1 hunks)packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt(1 hunks)packages/expo/app.plugin.js(1 hunks)packages/expo/expo-module.config.json(1 hunks)packages/expo/ios/ClerkGoogleSignIn.podspec(1 hunks)packages/expo/ios/ClerkGoogleSignInModule.swift(1 hunks)packages/expo/package.json(4 hunks)packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts(1 hunks)packages/expo/src/google-one-tap/index.ts(1 hunks)packages/expo/src/google-one-tap/types.ts(1 hunks)packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts(1 hunks)packages/expo/src/hooks/index.ts(1 hunks)packages/expo/src/hooks/useSignInWithGoogle.android.ts(1 hunks)packages/expo/src/hooks/useSignInWithGoogle.ios.ts(1 hunks)packages/expo/src/hooks/useSignInWithGoogle.ts(1 hunks)packages/expo/vitest.setup.mts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
All code must pass ESLint checks with the project's configuration
Files:
packages/expo/src/hooks/useSignInWithGoogle.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/app.plugin.jspackages/expo/src/hooks/useSignInWithGoogle.ios.tspackages/expo/src/google-one-tap/types.tspackages/expo/src/google-one-tap/index.tspackages/expo/src/hooks/index.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use Prettier for consistent code formatting
Files:
packages/expo/src/hooks/useSignInWithGoogle.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/app.plugin.jspackages/expo/package.jsonpackages/expo/src/hooks/useSignInWithGoogle.ios.tspackages/expo/src/google-one-tap/types.tspackages/expo/src/google-one-tap/index.tspackages/expo/expo-module.config.jsonpackages/expo/src/hooks/index.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
packages/**/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
TypeScript is required for all packages
Files:
packages/expo/src/hooks/useSignInWithGoogle.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.ios.tspackages/expo/src/google-one-tap/types.tspackages/expo/src/google-one-tap/index.tspackages/expo/src/hooks/index.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Follow established naming conventions (PascalCase for components, camelCase for variables)
Prefer importing types from
@clerk/shared/typesinstead of the deprecated@clerk/typesalias
Files:
packages/expo/src/hooks/useSignInWithGoogle.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/app.plugin.jspackages/expo/src/hooks/useSignInWithGoogle.ios.tspackages/expo/src/google-one-tap/types.tspackages/expo/src/google-one-tap/index.tspackages/expo/src/hooks/index.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
packages/**/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
packages/**/src/**/*.{ts,tsx,js,jsx}: Maintain comprehensive JSDoc comments for public APIs
Use tree-shaking friendly exports
Validate all inputs and sanitize outputs
All public APIs must be documented with JSDoc
Use dynamic imports for optional features
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Implement proper logging with different levels
Files:
packages/expo/src/hooks/useSignInWithGoogle.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.ios.tspackages/expo/src/google-one-tap/types.tspackages/expo/src/google-one-tap/index.tspackages/expo/src/hooks/index.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.ts?(x)
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use proper TypeScript error types
Files:
packages/expo/src/hooks/useSignInWithGoogle.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.ios.tspackages/expo/src/google-one-tap/types.tspackages/expo/src/google-one-tap/index.tspackages/expo/src/hooks/index.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)
**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoidanytype - preferunknownwhen type is uncertain, then narrow with type guards
Implement type guards forunknowntypes using the patternfunction isType(value: unknown): value is Type
Useinterfacefor object shapes that might be extended
Usetypefor unions, primitives, and computed types
Preferreadonlyproperties for immutable data structures
Useprivatefor internal implementation details in classes
Useprotectedfor inheritance hierarchies
Usepublicexplicitly for clarity in public APIs
Use mixins for shared behavior across unrelated classes in TypeScript
Use generic constraints with bounded type parameters like<T extends { id: string }>
Use utility types likeOmit,Partial, andPickfor data transformation instead of manual type construction
Use discriminated unions instead of boolean flags for state management and API responses
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation at the type level
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Document functions with JSDoc comments including @param, @returns, @throws, and @example tags
Create custom error classes that extend Error for specific error types
Use the Result pattern for error handling instead of throwing exceptions
Use optional chaining (?.) and nullish coalescing (??) operators for safe property access
Let TypeScript infer obvious types to reduce verbosity
Useconst assertionswithas constfor literal types
Usesatisfiesoperator for type checking without widening types
Declare readonly arrays and objects for immutable data structures
Use spread operator and array spread for immutable updates instead of mutations
Use lazy loading for large types...
Files:
packages/expo/src/hooks/useSignInWithGoogle.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.ios.tspackages/expo/src/google-one-tap/types.tspackages/expo/src/google-one-tap/index.tspackages/expo/src/hooks/index.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{test,spec}.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
**/*.{test,spec}.{ts,tsx,js,jsx}: Unit tests are required for all new functionality
Verify proper error handling and edge cases
Include tests for all new features
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts
**/*.{test,spec,e2e}.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use real Clerk instances for integration tests
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts
packages/*/package.json
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
packages/*/package.json: Packages should export TypeScript types alongside runtime code
Follow semantic versioning for all packagesAll packages must be published under @clerk namespace
Files:
packages/expo/package.json
**/package.json
📄 CodeRabbit inference engine (.cursor/rules/global.mdc)
Use pnpm as the package manager for this monorepo
Files:
packages/expo/package.json
**/index.ts
📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)
Avoid barrel files (index.ts re-exports) as they can cause circular dependencies
Files:
packages/expo/src/google-one-tap/index.tspackages/expo/src/hooks/index.ts
🧬 Code graph analysis (3)
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts (2)
packages/expo/src/hooks/useSignInWithGoogle.ios.ts (1)
useSignInWithGoogle(63-217)packages/expo/src/hooks/useSignInWithGoogle.android.ts (1)
useSignInWithGoogle(61-210)
packages/expo/ios/ClerkGoogleSignInModule.swift (2)
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt (1)
message(62-62)packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts (1)
signIn(117-131)
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts (2)
packages/expo/src/google-one-tap/types.ts (8)
ConfigureParams(4-31)SignInParams(36-49)OneTapResponse(145-145)CreateAccountParams(54-60)ExplicitSignInParams(65-71)CancelledResponse(129-132)NoSavedCredentialFound(137-140)OneTapSuccessResponse(111-124)packages/expo/src/google-one-tap/index.ts (13)
ConfigureParams(10-10)SignInParams(11-11)OneTapResponse(14-14)CreateAccountParams(12-12)ExplicitSignInParams(13-13)isCancelledResponse(3-3)CancelledResponse(16-16)isNoSavedCredentialFoundResponse(4-4)NoSavedCredentialFound(17-17)isSuccessResponse(5-5)OneTapSuccessResponse(15-15)isErrorWithCode(6-6)ClerkGoogleOneTapSignIn(2-2)
🪛 detekt (1.23.8)
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt
[warning] 115-115: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
[warning] 117-117: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
[warning] 154-154: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
[warning] 156-156: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
[warning] 192-192: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
🔇 Additional comments (13)
packages/expo/android/src/main/AndroidManifest.xml (1)
1-2: Minimal library manifest is acceptableA bare manifest with just the Android namespace is fine here since the namespace is configured in Gradle and the module doesn’t declare its own components. No issues from a packaging perspective.
packages/expo/src/hooks/index.ts (1)
14-18: NewuseSignInWithGoogleexport aligns with existing hook surfaceRe‑exporting
useSignInWithGooglefrom the hooks index keeps the Expo hook API consistent with Apple/SSO/OAuth exports and remains tree‑shaking friendly. Just ensureuseSignInWithGoogledoesn’t itself import from this barrel to avoid potential circular references.packages/expo/expo-module.config.json (1)
1-9: Expo module wiring looks consistent with native module namesThe config structure and module identifiers for Android and iOS look correct for Expo modules and line up with the described
ClerkGoogleSignInModuleimplementations. I don’t see any structural issues here.packages/expo/src/hooks/useSignInWithGoogle.ios.ts (1)
63-211: Core iOS Google sign-in flow, transfer handling, and cancellation behavior look solid.
- You correctly gate on
isSignInLoaded/isSignUpLoadedand still return{ setActive, signIn, signUp }, matching other Clerk hooks.- Env handling (
EXPO_PUBLIC_CLERK_GOOGLE_WEB_CLIENT_ID/EXPO_PUBLIC_CLERK_GOOGLE_IOS_CLIENT_ID) plusClerkGoogleOneTapSignIn.configurealigns with the native module signature.- Nonce generation + SHA‑256 hashing via
expo-cryptoand passing the hashed nonce intopresentExplicitSignInis the right pattern.- Transfer flow is covered twice: first via
signIn.firstFactorVerification.status === 'transferable', then via theexternal_account_not_foundClerk error path, which is a good safety net.- Cancellation from native (
SIGN_IN_CANCELLED) and non‑success One‑Tap responses both gracefully resolve withcreatedSessionId: nulland the Clerk handles, which gives callers a uniform “no session created” signal without throwing.No changes needed here from a logic standpoint.
packages/expo/package.json (1)
55-65: Exports and Expo module wiring look consistent; just confirm peer version ranges.
- Exporting
./google-one-tapwith bothtypesanddefaultmatches the rest of the package and keeps the TS surface available.- Including
android,ios,expo-module.config.json, andapp.plugin.jsinfilesensures the native module and config plugin ship correctly.- Adding
expo-constants/expo-modules-coreas both devDependencies and peerDependencies (withexpo-constantsoptional) matches how other Expo modules are usually modeled.Please just double‑check that the
>=12/>=3.0.0peer ranges align with the minimum Expo SDK versions you intend to support.Also applies to: 69-81, 104-116, 117-150
packages/expo/src/hooks/useSignInWithGoogle.ts (1)
16-69: Stub hook behavior and typing are good; just ensure the shared surface stays in sync with platform files.
- The JSDoc clearly communicates that this is a stub for non‑iOS/Android platforms and nudges devs toward
useSSOwithstrategy: "oauth_google", which is very helpful.- The explicit return type on
useSignInWithGoogleand theStartGoogleAuthenticationFlowReturnTypestructure (session id + optionalsetActive,signIn,signUp) matches what the platform-specific hooks are returning at runtime.- Using
errorThrower.throwto produce a consistent runtime error for unsupported platforms keeps the API predictable.Once the
SignUpUnsafeMetadatatype is added here and reused in the.ios/.androidvariants, the TS surface across platforms will be nicely aligned.packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts (3)
32-63: Well-implemented type guards.The type guard functions follow best practices with clear naming, proper
response is Typereturn signatures, and concise implementations. Good use of discriminated unions.
21-30: Lazy-loading pattern prevents initialization crashes.Good approach to lazily load the native module using
require(). This prevents app crashes when the module isn't available (e.g., in Expo Go or web).
194-204:GoogleUseris re-exported but not imported.The
GoogleUsertype is re-exported at line 203 but isn't included in the imports at lines 1-10. This will cause a TypeScript error.import type { ConfigureParams, SignInParams, CreateAccountParams, ExplicitSignInParams, OneTapResponse, OneTapSuccessResponse, CancelledResponse, NoSavedCredentialFound, + GoogleUser, } from './types';Likely an incorrect or invalid review comment.
packages/expo/src/google-one-tap/types.ts (2)
111-145: Well-designed discriminated union types.Good use of the discriminated union pattern with a
typefield forOneTapResponse. This enables exhaustive type checking and clean type narrowing with the type guards in the bridge module.
159-161: Consider usinginterfacevstypefor error interface.The
GoogleSignInErrorinterface extendingErrorfollows the coding guideline to useinterfacefor object shapes that might be extended. Good choice.packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt (2)
115-123: Exception swallowing is intentional and appropriate here.The static analysis tool flags these as "swallowed exceptions," but this is intentional behavior - the exceptions are being transformed into typed error responses (
GoogleSignInCancelledException,GoogleSignInNoCredentialException). The original exception details are captured in the custom exception messages where relevant.
227-228: User email is derived fromidfield.Note that
googleIdTokenCredential.idreturns the user's email address (used for credential matching), not the unique Google user ID (subclaim). This is consistent with how the type is documented but worth being aware of - if you need the actualsubclaim, you'd need to decode the ID token.
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt Show resolved Hide resolved
@clerk/agent-toolkit
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/dev-cli
@clerk/expo
@clerk/expo-passkeys
@clerk/express
@clerk/fastify
@clerk/localizations
@clerk/nextjs
@clerk/nuxt
@clerk/react
@clerk/react-router
@clerk/shared
@clerk/tanstack-react-start
@clerk/testing
@clerk/ui
@clerk/upgrade
@clerk/vue
commit: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (2)
packages/expo/src/hooks/useSignInWithGoogle.android.ts (1)
95-105: Nonce handling issue was previously identified.The nonce is hashed before being passed to Google, but Google's Credential Manager API expects the plain nonce value. This was flagged in a previous review. The
hashedNonceshould be replaced with the plainnonce.packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts (1)
119-127: Test assertions may pass incorrectly due to mock mismatch.Given that
mocks.GoogleSignin.hasPlayServicesandmocks.GoogleSignin.signInare never called by the actual hook (which usesClerkGoogleOneTapSignIn), these assertions will fail or pass based on incorrect assumptions.This is a consequence of the mock mismatch noted above. The tests need to be updated to mock the correct module (
ClerkGoogleOneTapSignInviaexpo-modules-core).
🧹 Nitpick comments (5)
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts (2)
117-158: Consider extracting shared error-handling logic.The
signInandcreateAccountmethods have identical catch blocks. This duplication could be reduced by extracting a helper function.// Helper function to map errors to responses function mapErrorToResponse(error: unknown): OneTapResponse | null { if (isErrorWithCode(error)) { if (error.code === 'SIGN_IN_CANCELLED') { return { type: 'cancelled', data: null }; } if (error.code === 'NO_SAVED_CREDENTIAL_FOUND') { return { type: 'noSavedCredentialFound', data: null }; } } return null; }
90-91: Documentation indicates Android-only, but module is cross-platform.The JSDoc states
@platform Android, but per the PR summary this module also supports iOS. Consider updating to@platform Android, iOSor removing the platform annotation.packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt (2)
75-76: CredentialManager is recreated on every access.
CredentialManager.create(context)is called each time thecredentialManagerproperty is accessed. While this is likely lightweight, consider caching the instance for consistency and minor performance improvement.+ private var _credentialManager: CredentialManager? = null + private val credentialManager: CredentialManager - get() = CredentialManager.create(context) + get() = _credentialManager ?: CredentialManager.create(context).also { _credentialManager = it }
70-70: Consider cancelling the coroutine scope on module cleanup.The
mainScopeis never cancelled, which could lead to coroutine leaks if the module is destroyed while operations are in-flight. Consider implementingOnDestroyto cancel pending coroutines.-class ClerkGoogleSignInModule : Module() { +class ClerkGoogleSignInModule : Module() { private var webClientId: String? = null private var hostedDomain: String? = null private var autoSelectEnabled: Boolean = false - private val mainScope = CoroutineScope(Dispatchers.Main) + private val mainScope = CoroutineScope(Dispatchers.Main + SupervisorJob()) + + override fun definition() = ModuleDefinition { + Name("ClerkGoogleSignIn") + + OnDestroy { + mainScope.cancel() + }packages/expo/src/hooks/useSignInWithGoogle.android.ts (1)
154-167: Consider extracting Clerk error type checking into a helper.The
isClerkErrorandhasExternalAccountNotFoundErrorchecks are verbose. Consider creating a reusable type guard function for Clerk errors.// In a shared utils file function isClerkErrorWithCode(error: unknown, code: string): boolean { if (!error || typeof error !== 'object') return false; if (!('clerkError' in error) || (error as { clerkError: boolean }).clerkError !== true) return false; if (!('errors' in error) || !Array.isArray((error as { errors: unknown[] }).errors)) return false; return (error as { errors: Array<{ code: string }> }).errors.some(err => err.code === code); } // Usage if (isClerkErrorWithCode(signInError, 'external_account_not_found')) { // ... }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt(1 hunks)packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts(1 hunks)packages/expo/src/google-one-tap/types.ts(1 hunks)packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts(1 hunks)packages/expo/src/hooks/useSignInWithGoogle.android.ts(1 hunks)packages/expo/src/hooks/useSignInWithGoogle.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/expo/src/google-one-tap/types.ts
- packages/expo/src/hooks/useSignInWithGoogle.ts
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
All code must pass ESLint checks with the project's configuration
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use Prettier for consistent code formatting
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
packages/**/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
TypeScript is required for all packages
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Follow established naming conventions (PascalCase for components, camelCase for variables)
Prefer importing types from
@clerk/shared/typesinstead of the deprecated@clerk/typesalias
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
packages/**/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
packages/**/src/**/*.{ts,tsx,js,jsx}: Maintain comprehensive JSDoc comments for public APIs
Use tree-shaking friendly exports
Validate all inputs and sanitize outputs
All public APIs must be documented with JSDoc
Use dynamic imports for optional features
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Implement proper logging with different levels
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{test,spec}.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
**/*.{test,spec}.{ts,tsx,js,jsx}: Unit tests are required for all new functionality
Verify proper error handling and edge cases
Include tests for all new features
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts
**/*.ts?(x)
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use proper TypeScript error types
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{test,spec,e2e}.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use real Clerk instances for integration tests
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)
**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoidanytype - preferunknownwhen type is uncertain, then narrow with type guards
Implement type guards forunknowntypes using the patternfunction isType(value: unknown): value is Type
Useinterfacefor object shapes that might be extended
Usetypefor unions, primitives, and computed types
Preferreadonlyproperties for immutable data structures
Useprivatefor internal implementation details in classes
Useprotectedfor inheritance hierarchies
Usepublicexplicitly for clarity in public APIs
Use mixins for shared behavior across unrelated classes in TypeScript
Use generic constraints with bounded type parameters like<T extends { id: string }>
Use utility types likeOmit,Partial, andPickfor data transformation instead of manual type construction
Use discriminated unions instead of boolean flags for state management and API responses
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation at the type level
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Document functions with JSDoc comments including @param, @returns, @throws, and @example tags
Create custom error classes that extend Error for specific error types
Use the Result pattern for error handling instead of throwing exceptions
Use optional chaining (?.) and nullish coalescing (??) operators for safe property access
Let TypeScript infer obvious types to reduce verbosity
Useconst assertionswithas constfor literal types
Usesatisfiesoperator for type checking without widening types
Declare readonly arrays and objects for immutable data structures
Use spread operator and array spread for immutable updates instead of mutations
Use lazy loading for large types...
Files:
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.tspackages/expo/src/hooks/useSignInWithGoogle.android.tspackages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
🧬 Code graph analysis (2)
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts (1)
packages/expo/src/hooks/useSignInWithGoogle.android.ts (1)
useSignInWithGoogle(63-212)
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts (1)
packages/expo/src/google-one-tap/types.ts (8)
ConfigureParams(4-31)SignInParams(36-49)OneTapResponse(146-146)CreateAccountParams(54-60)ExplicitSignInParams(65-71)CancelledResponse(130-133)NoSavedCredentialFound(138-141)OneTapSuccessResponse(112-125)
🪛 detekt (1.23.8)
packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt
[warning] 121-121: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
[warning] 123-123: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
[warning] 165-165: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
[warning] 167-167: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
[warning] 208-208: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Build Packages
- GitHub Check: Formatting | Dedupe | Changeset
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: semgrep-cloud-platform/scan
- GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (7)
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts (2)
21-30: Lazy-loading pattern is well-implemented.The lazy-load approach prevents crashes when the native module is unavailable (e.g., on web or in test environments). The module is cached after first load, which is efficient.
56-63: Well-implemented type guard with proper narrowing.The
isErrorWithCodefunction correctly narrowsunknownto a typed error object using safe property checks. This follows TypeScript best practices for type guards.packages/expo/android/src/main/java/expo/modules/clerk/googlesignin/ClerkGoogleSignInModule.kt (2)
121-129: Exception handling is appropriate despite static analysis warning.The static analysis flags "swallowed exceptions," but these are correctly converted to typed promise rejections with meaningful error codes (
SIGN_IN_CANCELLED,NO_SAVED_CREDENTIAL_FOUND). The user-facing error semantics are preserved. For enhanced debugging, you could optionally log the original exception before rejecting.
242-244: The review comment's core premise is incorrect, but the underlying concern is valid.Per Google's official documentation,
GoogleIdTokenCredential.idis the email address, not a separate identifier. The review comment incorrectly states "The Google ID may differ from the email address."However, there is a legitimate semantic issue: using the email address for both the
idandidfield should contain the user's unique Google identifier (thesubclaim from the JWT), whileidTokenis available and could be decoded to extract the proper unique identifier."user" to mapOf( "id" to googleIdTokenCredential.id, "email" to googleIdTokenCredential.id,Consider extracting the unique user ID from the
idTokenJWT claims for theidfield, rather than using the email address.Likely an incorrect or invalid review comment.
packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts (1)
242-291: Good coverage of theexternal_account_not_foundfallback path.This test addresses the previously noted missing coverage. It properly validates that when
signIn.createthrows anexternal_account_not_founderror, the hook falls back tosignUp.createwith the token and unsafeMetadata. However, ensure the mocks are corrected so this test actually exercises the real code path.packages/expo/src/hooks/useSignInWithGoogle.android.ts (2)
22-62: Comprehensive JSDoc with clear example.The documentation clearly explains the hook's purpose, features, and usage pattern. The example demonstrates proper error handling and session activation.
209-212: Clean hook API design.The hook follows React conventions by returning a stable object with the
startGoogleAuthenticationFlowfunction. The return type is consistent across all execution paths.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (3)
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts (3)
14-31: Lazy native-module loading pattern looks good; consider clarifying the comment.The
ClerkGoogleSignInNativeModuleinterface andgetNativeModulelazy initializer are well-typed and align with Expo’srequireNativeModuleusage. The comment “avoid crashes when not available” can be a bit misleading though — you’re primarily avoiding an import‑time crash on platforms where the native module doesn’t exist, but a call togetNativeModule()will still throw if the module is missing.You might tweak the comment to something like “Lazy‑load the native module to avoid crashes at import time on unsupported platforms” to better set expectations, but the implementation itself is solid.
57-64: TightenisErrorWithCodeguard or relax the return type to avoid lying aboutmessage.Right now the guard promises
error is { code: string; message: string }but only validates thecodeproperty. That means callers could safely accesserror.messageas astringeven when it’s missing or non‑string at runtime.Either of these adjustments would make the guard accurate:
- Option A – Validate
messageas well (keeps current type):-export function isErrorWithCode(error: unknown): error is { code: string; message: string } { +export function isErrorWithCode(error: unknown): error is { code: string; message: string } { return ( error !== null && typeof error === 'object' && - 'code' in error && - typeof (error as { code: unknown }).code === 'string' + 'code' in error && + typeof (error as { code: unknown }).code === 'string' && + 'message' in error && + typeof (error as { message: unknown }).message === 'string' ); }
- Option B – Relax the type so only
codeis guaranteed (simpler, since you don’t usemessage):-export function isErrorWithCode(error: unknown): error is { code: string; message: string } { +export function isErrorWithCode(error: unknown): error is { code: string } { return ( error !== null && typeof error === 'object' && 'code' in error && typeof (error as { code: unknown }).code === 'string' ); }Given current usage only checks
error.code, Option B is probably the minimal change.
66-193: Core API shape and error mapping look solid; consider a small helper to dedupe error-to-response mapping.The
ClerkGoogleOneTapSignInmethods are well-typed, documented, and provide a clean Promise‑based API.params?: …plusparams ?? {}is a nice ergonomic choice for callers, and the mapping of known error codes (SIGN_IN_CANCELLED,NO_SAVED_CREDENTIAL_FOUND) to structuredOneTapResponseshapes is clear.There is some duplication in how you translate
error.codeinto{ type: 'cancelled' | 'noSavedCredentialFound', data: null }acrosssignIn,createAccount, andpresentExplicitSignIn. A tiny internal helper would reduce drift if more codes are added later; for example:+function mapErrorToOneTapResponse(error: unknown): OneTapResponse | null { + if (!isErrorWithCode(error)) { + return null; + } + + if (error.code === 'SIGN_IN_CANCELLED') { + return { type: 'cancelled', data: null }; + } + + if (error.code === 'NO_SAVED_CREDENTIAL_FOUND') { + return { type: 'noSavedCredentialFound', data: null }; + } + + return null; +} + export const ClerkGoogleOneTapSignIn = { ... async signIn(params?: SignInParams): Promise<OneTapResponse> { try { return await getNativeModule().signIn(params ?? {}); } catch (error) { - if (isErrorWithCode(error)) { - if (error.code === 'SIGN_IN_CANCELLED') { - return { type: 'cancelled', data: null }; - } - if (error.code === 'NO_SAVED_CREDENTIAL_FOUND') { - return { type: 'noSavedCredentialFound', data: null }; - } - } + const mapped = mapErrorToOneTapResponse(error); + if (mapped) return mapped; throw error; } }, ... async createAccount(params?: CreateAccountParams): Promise<OneTapResponse> { try { return await getNativeModule().createAccount(params ?? {}); } catch (error) { - if (isErrorWithCode(error)) { - if (error.code === 'SIGN_IN_CANCELLED') { - return { type: 'cancelled', data: null }; - } - if (error.code === 'NO_SAVED_CREDENTIAL_FOUND') { - return { type: 'noSavedCredentialFound', data: null }; - } - } + const mapped = mapErrorToOneTapResponse(error); + if (mapped) return mapped; throw error; } }, ... async presentExplicitSignIn(params?: ExplicitSignInParams): Promise<OneTapResponse> { try { return await getNativeModule().presentExplicitSignIn(params ?? {}); } catch (error) { - if (isErrorWithCode(error)) { - if (error.code === 'SIGN_IN_CANCELLED') { - return { type: 'cancelled', data: null }; - } - } + const mapped = mapErrorToOneTapResponse(error); + if (mapped) return mapped; throw error; } }, ... };Not mandatory, but it keeps the behavior consistent as you evolve the error surface.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
.changeset/brave-clouds-swim.md(1 hunks)packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
All code must pass ESLint checks with the project's configuration
Files:
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use Prettier for consistent code formatting
Files:
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
packages/**/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
TypeScript is required for all packages
Files:
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Follow established naming conventions (PascalCase for components, camelCase for variables)
Prefer importing types from
@clerk/shared/typesinstead of the deprecated@clerk/typesalias
Files:
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
packages/**/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
packages/**/src/**/*.{ts,tsx,js,jsx}: Maintain comprehensive JSDoc comments for public APIs
Use tree-shaking friendly exports
Validate all inputs and sanitize outputs
All public APIs must be documented with JSDoc
Use dynamic imports for optional features
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Implement proper logging with different levels
Files:
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.ts?(x)
📄 CodeRabbit inference engine (.cursor/rules/development.mdc)
Use proper TypeScript error types
Files:
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)
**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoidanytype - preferunknownwhen type is uncertain, then narrow with type guards
Implement type guards forunknowntypes using the patternfunction isType(value: unknown): value is Type
Useinterfacefor object shapes that might be extended
Usetypefor unions, primitives, and computed types
Preferreadonlyproperties for immutable data structures
Useprivatefor internal implementation details in classes
Useprotectedfor inheritance hierarchies
Usepublicexplicitly for clarity in public APIs
Use mixins for shared behavior across unrelated classes in TypeScript
Use generic constraints with bounded type parameters like<T extends { id: string }>
Use utility types likeOmit,Partial, andPickfor data transformation instead of manual type construction
Use discriminated unions instead of boolean flags for state management and API responses
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation at the type level
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Document functions with JSDoc comments including @param, @returns, @throws, and @example tags
Create custom error classes that extend Error for specific error types
Use the Result pattern for error handling instead of throwing exceptions
Use optional chaining (?.) and nullish coalescing (??) operators for safe property access
Let TypeScript infer obvious types to reduce verbosity
Useconst assertionswithas constfor literal types
Usesatisfiesoperator for type checking without widening types
Declare readonly arrays and objects for immutable data structures
Use spread operator and array spread for immutable updates instead of mutations
Use lazy loading for large types...
Files:
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (34)
- GitHub Check: Integration Tests (nextjs, chrome, 15)
- GitHub Check: Integration Tests (machine, chrome)
- GitHub Check: Integration Tests (custom, chrome)
- GitHub Check: Integration Tests (nextjs, chrome, 16)
- GitHub Check: Integration Tests (nextjs, chrome, 15, RQ)
- GitHub Check: Integration Tests (quickstart, chrome, 16)
- GitHub Check: Integration Tests (quickstart, chrome, 15)
- GitHub Check: Integration Tests (machine, chrome, RQ)
- GitHub Check: Integration Tests (nextjs, chrome, 14)
- GitHub Check: Integration Tests (sessions, chrome)
- GitHub Check: Integration Tests (elements, chrome)
- GitHub Check: Integration Tests (vue, chrome)
- GitHub Check: Integration Tests (billing, chrome, RQ)
- GitHub Check: Integration Tests (handshake, chrome)
- GitHub Check: Integration Tests (express, chrome)
- GitHub Check: Integration Tests (tanstack-react-start, chrome)
- GitHub Check: Integration Tests (nuxt, chrome)
- GitHub Check: Integration Tests (react-router, chrome)
- GitHub Check: Integration Tests (generic, chrome)
- GitHub Check: Integration Tests (ap-flows, chrome)
- GitHub Check: Integration Tests (billing, chrome)
- GitHub Check: Integration Tests (sessions:staging, chrome)
- GitHub Check: Integration Tests (localhost, chrome)
- GitHub Check: Integration Tests (astro, chrome)
- GitHub Check: Integration Tests (handshake:staging, chrome)
- GitHub Check: Integration Tests (expo-web, chrome)
- GitHub Check: Unit Tests (22, shared, clerk-js, RQ)
- GitHub Check: Publish with pkg-pr-new
- GitHub Check: Unit Tests (22, **)
- GitHub Check: Static analysis
- GitHub Check: Formatting | Dedupe | Changeset
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: semgrep-cloud-platform/scan
- GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (3)
.changeset/brave-clouds-swim.md (1)
1-5: Changeset entry is well-formed and correctly categorized.The file follows the standard changeset format with the appropriate minor version bump for @clerk/clerk-expo and provides a clear, concise description of the feature addition. The version level correctly reflects the non-breaking nature of the new native Google Sign-In support.
packages/expo/src/google-one-tap/ClerkGoogleOneTapSignIn.ts (2)
33-52: Response discriminant type guards are concise and correct.The three guards (
isCancelledResponse,isNoSavedCredentialFoundResponse,isSuccessResponse) cleanly narrowOneTapResponsebased on thetypediscriminant and should work well with a discriminated union in./types. This is a nice, ergonomic API surface for consumers.
195-205: Type re-exports are clean and tree-shaking–friendly.Re-exporting the public types from
./typesin a groupedexport type { … }keeps the surface area discoverable and maintains good tree-shaking characteristics, while also aligning with the JSDoc’d public API.
mikepitre left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Swift code looks fine to me!
packages/expo/android/build.gradle Outdated
| implementation project(':expo-modules-core') | ||
| | ||
| // Credential Manager for Google Sign-In with nonce support | ||
| implementation "androidx.credentials:credentials:1.3.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unrelated but you can use a version catalog (https://docs.gradle.org/current/userguide/version_catalogs.html) and then setup automated updates for these dependencies
| !snapshot |
| !snapshot |
| Hey @chriscanin - the snapshot version command generated the following package versions:
Tip: Use the snippet copy button below to quickly install the required packages. npm i @clerk/agent-toolkit@0.2.9-snapshot.v20251217165918 --save-exact
npm i @clerk/astro@3.0.0-snapshot.v20251217165918 --save-exact
npm i @clerk/backend@3.0.0-snapshot.v20251217165918 --save-exact
npm i @clerk/chrome-extension@3.0.0-snapshot.v20251217165918 --save-exact
npm i @clerk/clerk-js@6.0.0-snapshot.v20251217165918 --save-exact
npm i @clerk/dev-cli@1.0.0-snapshot.v20251217165918 --save-exact
npm i @clerk/expo@3.0.0-snapshot.v20251217165918 --save-exact
npm i @clerk/expo-passkeys@1.0.0-snapshot.v20251217165918 --save-exact
npm i @clerk/express@2.0.0-snapshot.v20251217165918 --save-exact
npm i @clerk/fastify@2.6.9-snapshot.v20251217165918 --save-exact
npm i @clerk/localizations@4.0.0-snapshot.v20251217165918 --save-exact
npm i @clerk/nextjs@7.0.0-snapshot.v20251217165918 --save-exact
npm i @clerk/nuxt@2.0.0-snapshot.v20251217165918 --save-exact
npm i @clerk/react@6.0.0-snapshot.v20251217165918 --save-exact
npm i @clerk/react-router@3.0.0-snapshot.v20251217165918 --save-exact
npm i @clerk/shared@4.0.0-snapshot.v20251217165918 --save-exact
npm i @clerk/tanstack-react-start@1.0.0-snapshot.v20251217165918 --save-exact
npm i @clerk/testing@2.0.0-snapshot.v20251217165918 --save-exact
npm i @clerk/ui@1.0.0-snapshot.v20251217165918 --save-exact
npm i @clerk/upgrade@2.0.0-snapshot.v20251217165918 --save-exact
npm i @clerk/vue@2.0.0-snapshot.v20251217165918 --save-exact |
- Changed the export path in app.plugin.js to point to the new distribution directory. - Removed the build:plugin script and unnecessary entries from package.json, streamlining the configuration. These changes are in effort to continue using tsup. Resolves: https://github.com/clerk/javascript/pull/7208/changes#r2628292077 and https://github.com/clerk/javascript/pull/7208/changes#r2628293763
| !snapshot |
| Hey @chriscanin - the snapshot version command generated the following package versions:
Tip: Use the snippet copy button below to quickly install the required packages. npm i @clerk/agent-toolkit@0.2.9-snapshot.v20251217194100 --save-exact
npm i @clerk/astro@3.0.0-snapshot.v20251217194100 --save-exact
npm i @clerk/backend@3.0.0-snapshot.v20251217194100 --save-exact
npm i @clerk/chrome-extension@3.0.0-snapshot.v20251217194100 --save-exact
npm i @clerk/clerk-js@6.0.0-snapshot.v20251217194100 --save-exact
npm i @clerk/dev-cli@1.0.0-snapshot.v20251217194100 --save-exact
npm i @clerk/expo@3.0.0-snapshot.v20251217194100 --save-exact
npm i @clerk/expo-passkeys@1.0.0-snapshot.v20251217194100 --save-exact
npm i @clerk/express@2.0.0-snapshot.v20251217194100 --save-exact
npm i @clerk/fastify@2.6.9-snapshot.v20251217194100 --save-exact
npm i @clerk/localizations@4.0.0-snapshot.v20251217194100 --save-exact
npm i @clerk/nextjs@7.0.0-snapshot.v20251217194100 --save-exact
npm i @clerk/nuxt@2.0.0-snapshot.v20251217194100 --save-exact
npm i @clerk/react@6.0.0-snapshot.v20251217194100 --save-exact
npm i @clerk/react-router@3.0.0-snapshot.v20251217194100 --save-exact
npm i @clerk/shared@4.0.0-snapshot.v20251217194100 --save-exact
npm i @clerk/tanstack-react-start@1.0.0-snapshot.v20251217194100 --save-exact
npm i @clerk/testing@2.0.0-snapshot.v20251217194100 --save-exact
npm i @clerk/ui@1.0.0-snapshot.v20251217194100 --save-exact
npm i @clerk/upgrade@2.0.0-snapshot.v20251217194100 --save-exact
npm i @clerk/vue@2.0.0-snapshot.v20251217194100 --save-exact |
…Expo plugin - Changed the import of package.json from require to ES module syntax in order to fix the no "require" imports allowed eslint warning.
dstaley left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one note about SignUpUnsafeMetadata, but otherwise LGTM!
| !snapshot |
| Hey @chriscanin - the snapshot version command generated the following package versions:
Tip: Use the snippet copy button below to quickly install the required packages. npm i @clerk/agent-toolkit@0.2.9-snapshot.v20251218165926 --save-exact
npm i @clerk/astro@3.0.0-snapshot.v20251218165926 --save-exact
npm i @clerk/backend@3.0.0-snapshot.v20251218165926 --save-exact
npm i @clerk/chrome-extension@3.0.0-snapshot.v20251218165926 --save-exact
npm i @clerk/clerk-js@6.0.0-snapshot.v20251218165926 --save-exact
npm i @clerk/dev-cli@1.0.0-snapshot.v20251218165926 --save-exact
npm i @clerk/expo@3.0.0-snapshot.v20251218165926 --save-exact
npm i @clerk/expo-passkeys@1.0.0-snapshot.v20251218165926 --save-exact
npm i @clerk/express@2.0.0-snapshot.v20251218165926 --save-exact
npm i @clerk/fastify@2.6.9-snapshot.v20251218165926 --save-exact
npm i @clerk/localizations@4.0.0-snapshot.v20251218165926 --save-exact
npm i @clerk/nextjs@7.0.0-snapshot.v20251218165926 --save-exact
npm i @clerk/nuxt@2.0.0-snapshot.v20251218165926 --save-exact
npm i @clerk/react@6.0.0-snapshot.v20251218165926 --save-exact
npm i @clerk/react-router@3.0.0-snapshot.v20251218165926 --save-exact
npm i @clerk/shared@4.0.0-snapshot.v20251218165926 --save-exact
npm i @clerk/tanstack-react-start@1.0.0-snapshot.v20251218165926 --save-exact
npm i @clerk/testing@2.0.0-snapshot.v20251218165926 --save-exact
npm i @clerk/ui@1.0.0-snapshot.v20251218165926 --save-exact
npm i @clerk/upgrade@2.0.0-snapshot.v20251218165926 --save-exact
npm i @clerk/vue@2.0.0-snapshot.v20251218165926 --save-exact |
| !snapshot |
| Hey @chriscanin - the snapshot version command generated the following package versions:
Tip: Use the snippet copy button below to quickly install the required packages. npm i @clerk/agent-toolkit@0.2.9-snapshot.v20251218183643 --save-exact
npm i @clerk/astro@3.0.0-snapshot.v20251218183643 --save-exact
npm i @clerk/backend@3.0.0-snapshot.v20251218183643 --save-exact
npm i @clerk/chrome-extension@3.0.0-snapshot.v20251218183643 --save-exact
npm i @clerk/clerk-js@6.0.0-snapshot.v20251218183643 --save-exact
npm i @clerk/dev-cli@1.0.0-snapshot.v20251218183643 --save-exact
npm i @clerk/expo@3.0.0-snapshot.v20251218183643 --save-exact
npm i @clerk/expo-passkeys@1.0.0-snapshot.v20251218183643 --save-exact
npm i @clerk/express@2.0.0-snapshot.v20251218183643 --save-exact
npm i @clerk/fastify@2.6.9-snapshot.v20251218183643 --save-exact
npm i @clerk/localizations@4.0.0-snapshot.v20251218183643 --save-exact
npm i @clerk/nextjs@7.0.0-snapshot.v20251218183643 --save-exact
npm i @clerk/nuxt@2.0.0-snapshot.v20251218183643 --save-exact
npm i @clerk/react@6.0.0-snapshot.v20251218183643 --save-exact
npm i @clerk/react-router@3.0.0-snapshot.v20251218183643 --save-exact
npm i @clerk/shared@4.0.0-snapshot.v20251218183643 --save-exact
npm i @clerk/tanstack-react-start@1.0.0-snapshot.v20251218183643 --save-exact
npm i @clerk/testing@2.0.0-snapshot.v20251218183643 --save-exact
npm i @clerk/ui@1.0.0-snapshot.v20251218183643 --save-exact
npm i @clerk/upgrade@2.0.0-snapshot.v20251218183643 --save-exact
npm i @clerk/vue@2.0.0-snapshot.v20251218183643 --save-exact |
Description
This PR implements native Google Sign-In support for Expo iOS and Android apps using the
@react-native-google-signin/google-signinlibrary. This provides a seamless native authentication experience for users signing in with their Google accounts.What's New
useSignInWithGoogle()- Provides native Google Sign-In functionality for iOS and AndroiduseSignInWithGoogle.ios.ts) and Android (useSignInWithGoogle.android.ts)unsafeMetadataduring authenticationKey Features
SIGN_IN_CANCELLEDand-5)Files Changed
packages/expo/src/hooks/useSignInWithGoogle.ts- Cross-platform stub with helpful error messagepackages/expo/src/hooks/useSignInWithGoogle.ios.ts- iOS-specific implementationpackages/expo/src/hooks/useSignInWithGoogle.android.ts- Android-specific implementationpackages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts- Comprehensive unit testspackages/expo/src/hooks/index.ts- Export new hookpackages/expo/package.json- Add optional peer dependencypackages/expo/vitest.setup.mts- Add test mocks for expo-modules-coreImplementation Details
iOS Implementation:
expo-apple-authenticationfor native Sign-Inoauth_token_applestrategyAndroid Implementation:
@react-native-google-signin/google-signinlibrarygoogle_one_tapstrategy with ID tokenBoth implementations:
{ createdSessionId, setActive, signIn, signUp }unsafeMetadataparameterBreaking Changes
None - this is a new feature.
Test Plan
pnpm test --filter @clerk/clerk-expo)pnpm build)Documentation
Related documentation PR: [Link to clerk-docs PR]
Checklist
pnpm testruns as expected.pnpm buildruns as expected.Type of change
Summary by CodeRabbit
New Features
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.