Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/expo-google-signin-package.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@clerk/expo': major
'@clerk/expo-google-signin': minor

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we add @clerk/expo-google-signin to the API Changes/break-check coverage too? @clerk/expo and @clerk/expo-passkeys are already tracked there, so it seems like this new public package should get the same declaration snapshot coverage.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch, I will add it

---

Move native Google Sign-In out of `@clerk/expo` and into `@clerk/expo-google-signin`.

Apps using native Google Sign-In should install `@clerk/expo-google-signin`, add it to the Expo config plugin list alongside `@clerk/expo`, and rebuild their native app. The `@clerk/expo/google` import path continues to re-export `useSignInWithGoogle`, but now requires `@clerk/expo-google-signin` to be installed.
39 changes: 39 additions & 0 deletions packages/expo-google-signin/android/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'maven-publish'

group = 'expo.modules.clerk.googlesignin'
version = '1.0.0'

def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
apply from: expoModulesCorePlugin
applyKotlinExpoModulesCorePlugin()
useCoreDependencies()
useExpoPublishing()

buildscript {
ext.safeExtGet = { prop, fallback ->
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
}

android {
namespace "expo.modules.clerk.googlesignin"

compileSdkVersion safeExtGet("compileSdkVersion", 36)

defaultConfig {
minSdkVersion safeExtGet("minSdkVersion", 24)
targetSdkVersion safeExtGet("targetSdkVersion", 36)
versionCode 1
versionName "1.0.0"
}
}

dependencies {
implementation project(':expo-modules-core')
implementation "androidx.credentials:credentials:1.3.0"
implementation "androidx.credentials:credentials-play-services-auth:1.3.0"
implementation "com.google.android.libraries.identity.googleid:googleid:1.1.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
5 changes: 5 additions & 0 deletions packages/expo-google-signin/app.plugin.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { ConfigPlugin } from '@expo/config-plugins';

declare const withClerkExpoGoogleSignIn: ConfigPlugin;

export = withClerkExpoGoogleSignIn;
32 changes: 32 additions & 0 deletions packages/expo-google-signin/app.plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const { withInfoPlist, createRunOncePlugin } = require('@expo/config-plugins');
const pkg = require('./package.json');

const withClerkExpoGoogleSignIn = config => {
const iosUrlScheme =
process.env.EXPO_PUBLIC_CLERK_GOOGLE_IOS_URL_SCHEME ||
(config.extra && config.extra.EXPO_PUBLIC_CLERK_GOOGLE_IOS_URL_SCHEME);

if (!iosUrlScheme) {
return config;
}

return withInfoPlist(config, modConfig => {
if (!Array.isArray(modConfig.modResults.CFBundleURLTypes)) {
modConfig.modResults.CFBundleURLTypes = [];
}

const schemeExists = modConfig.modResults.CFBundleURLTypes.some(urlType =>
urlType.CFBundleURLSchemes?.includes(iosUrlScheme),
);

if (!schemeExists) {
modConfig.modResults.CFBundleURLTypes.push({
CFBundleURLSchemes: [iosUrlScheme],
});
}

return modConfig;
});
};

module.exports = createRunOncePlugin(withClerkExpoGoogleSignIn, pkg.name, pkg.version);
9 changes: 9 additions & 0 deletions packages/expo-google-signin/expo-module.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"platforms": ["apple", "android"],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should the Expo native matrix exercise this package as part of the split? The current workflow still only triggers on packages/expo/** and only packs/installs @clerk/expo, so it proves the main package still prebuilds but not that the new native Google Sign-In package works in a clean app.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oh yeah I think that's one way of testing it! will add

"apple": {
"modules": ["ClerkGoogleSignInModule"]
},
"android": {
"modules": ["expo.modules.clerk.googlesignin.ClerkGoogleSignInModule"]
}
}
73 changes: 73 additions & 0 deletions packages/expo-google-signin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"name": "@clerk/expo-google-signin",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we do anything for the first-publish path here, or is the plan to force this check once and publish immediately after merge?

pkg-pr-new --compact --pnpm ./packages/* is failing because @clerk/expo-google-signin does not exist on npm yet. If this might sit on main before the first publish, should we exclude it from compact previews or make that workflow tolerate brand-new packages?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good question! We need a manual publish for a pre-release version so that pkg-pr-new can pick it up (it wants a package available on NPM already).

@dominic-clerk similar to electron and electron-passkeys, can I request for @clerk/expo-google-signin to be manually published? 🙏🏼

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@wobsoriano Done! ✅

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks Dominic!

"version": "0.0.0",
"description": "Native Google Sign-In library to be used with Clerk for Expo",
"keywords": [
"react-native",
"expo",
"google-signin",
"clerk"
],
"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/expo-google-signin"
},
"license": "MIT",
"author": "Clerk",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist",
"android",
"ios",
"expo-module.config.json",
"app.plugin.js",
"app.plugin.d.ts"
],
"scripts": {
"build": "tsup",
"build:declarations": "tsc -p tsconfig.declarations.json",
"clean": "rimraf ./dist",
"dev": "tsup --watch",
"dev:pub": "pnpm dev -- --env.publish",
"format": "node ../../scripts/format-package.mjs",
"format:check": "node ../../scripts/format-package.mjs --check",
"lint": "eslint src",
"test": "vitest run"
},
"dependencies": {
"@clerk/react": "workspace:^",
"@clerk/shared": "workspace:^",
"tslib": "catalog:repo"
},
"devDependencies": {
"@expo/config-plugins": "^54.0.4",
"@types/react": "catalog:react",
"expo": "~54.0.34",
"expo-constants": "^18.0.13",
"expo-crypto": "^15.0.9",
"react": "catalog:react",
"react-native": "^0.85.2",
"tsup": "catalog:repo"
},
"peerDependencies": {
"expo": ">=53 <57",
"expo-constants": ">=12",
"expo-crypto": ">=12",
"react": "^18.0.0 || ^19.0.0",
"react-native": ">=0.75"
Comment on lines +51 to +63

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Which React Native version does Expo SDK 54 require, and is react-native 0.85 compatible with expo 54?

💡 Result:

Expo SDK 54 requires React Native 0.81 [1][2]. It is not compatible with React Native 0.85 [3][4]. Each version of Expo SDK is designed to work with a specific React Native version, and using mismatched versions is generally not supported [3]. React Native 0.85 was introduced as the target version for Expo SDK 56 [3][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the package manifest and nearby workspace context.
sed -n '1,220p' packages/expo-google-signin/package.json
printf '\n---\n'
git ls-files 'packages/expo-google-signin/*' | sed -n '1,120p'

Repository: clerk/javascript

Length of output: 3499


Align the Expo devDependency versions. Expo SDK 54 ships with React Native 0.81, so react-native@^0.85.2 is out of sync here and can break local installs or builds for this package.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/expo-google-signin/package.json` around lines 51 - 63, The
devDependency versions in the package.json for expo-google-signin are misaligned
with the Expo SDK being targeted, specifically the react-native version is too
new for SDK 54. Update the expo-related devDependencies in this package to match
the Expo 54-compatible versions, and keep the peerDependencies range broad
enough to support the intended SDKs; use the existing package.json dependency
block and peerDependencies block as the place to align the versions.

},
"peerDependenciesMeta": {
"expo-constants": {
"optional": true
}
},
"publishConfig": {
"access": "public"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ vi.mock('@clerk/shared/error', async importOriginal => {
};
});

vi.mock('../../google-one-tap', async importOriginal => {
vi.mock('../google-one-tap', async importOriginal => {
const actual = await importOriginal();
return {
...actual,
Expand All @@ -46,17 +46,7 @@ vi.mock('react-native', () => {
};
});

vi.mock('../../specs/NativeClerkModule', () => {
return {
default: {
configure: vi.fn(),
getClientToken: vi.fn(),
syncClientStateFromJs: vi.fn(),
},
};
});

vi.mock('../../specs/NativeClerkGoogleSignIn', () => {
vi.mock('../specs/NativeClerkGoogleSignIn', () => {
return {
default: {
configure: vi.fn(),
Expand Down Expand Up @@ -127,25 +117,6 @@ describe('useSignInWithGoogle', () => {
});

describe('startGoogleAuthenticationFlow', () => {
test('should warn once in development about the upcoming package split', () => {
const originalDev = globalThis.__DEV__;
globalThis.__DEV__ = true;
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);

try {
renderHook(() => useSignInWithGoogle());
renderHook(() => useSignInWithGoogle());

expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
expect(consoleWarnSpy).toHaveBeenCalledWith(
'Clerk: In the next major version, native Google Sign-In will require installing @clerk/expo-google-signin and adding its Expo config plugin. The @clerk/expo/google import path will continue to work.',
);
} finally {
consoleWarnSpy.mockRestore();
globalThis.__DEV__ = originalDev;
}
});

test('should return the hook with startGoogleAuthenticationFlow function', () => {
const { result } = renderHook(() => useSignInWithGoogle());

Expand Down
1 change: 1 addition & 0 deletions packages/expo-google-signin/src/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare const PACKAGE_NAME: string;
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function getNativeModule(): NonNullable<typeof NativeClerkGoogleSignIn> {
if (!NativeClerkGoogleSignIn) {
throw new Error(
'ClerkGoogleSignIn native module is not available. ' +
'Ensure the @clerk/expo plugin is added to your app.json and you have run a development build.',
'Ensure the @clerk/expo-google-signin plugin is added to your app.json and you have run a development build.',
);
}
return NativeClerkGoogleSignIn;
Expand Down
5 changes: 5 additions & 0 deletions packages/expo-google-signin/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { useSignInWithGoogle } from './useSignInWithGoogle';
export type {
StartGoogleAuthenticationFlowParams,
StartGoogleAuthenticationFlowReturnType,
} from './useSignInWithGoogle.types';
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,9 @@ export type {
* - Built-in nonce support for replay attack protection
* - No additional dependencies required
*
* In the next major version, apps using native Google Sign-In will need to install
* `@clerk/expo-google-signin` alongside `@clerk/expo` and add its Expo config plugin.
*
* @example
* ```tsx
* import { useSignInWithGoogle } from '@clerk/expo';
* import { useSignInWithGoogle } from '@clerk/expo-google-signin';
* import { Button } from 'react-native';
*
* function GoogleSignInButton() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,9 @@ export type {
* - Built-in nonce support for replay attack protection
* - No additional dependencies required
*
* In the next major version, apps using native Google Sign-In will need to install
* `@clerk/expo-google-signin` alongside `@clerk/expo` and add its Expo config plugin.
*
* @example
* ```tsx
* import { useSignInWithGoogle } from '@clerk/expo';
* import { useSignInWithGoogle } from '@clerk/expo-google-signin';
* import { Button } from 'react-native';
*
* function GoogleSigninButton() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { useClerk } from '@clerk/react';
import { isClerkAPIResponseError } from '@clerk/shared/error';
import { buildErrorThrower, isClerkAPIResponseError } from '@clerk/shared/error';
import { eventMethodCalled } from '@clerk/shared/telemetry';
import type { ClientResource, SetActive } from '@clerk/shared/types';

import { ClerkGoogleOneTapSignIn, isErrorWithCode, isSuccessResponse } from '../google-one-tap';
import { errorThrower } from '../utils/errors';
import { ClerkGoogleOneTapSignIn, isErrorWithCode, isSuccessResponse } from './google-one-tap';
import type {
StartGoogleAuthenticationFlowParams,
StartGoogleAuthenticationFlowReturnType,
} from './useSignInWithGoogle.types';

const errorThrower = buildErrorThrower({ packageName: PACKAGE_NAME });

export type GoogleClientIds = {
webClientId: string;
iosClientId?: string;
Expand All @@ -24,19 +25,6 @@ type PlatformConfig = {
requiresIosClientId: boolean;
};

let hasWarnedAboutGoogleSignInPackage = false;

function warnAboutGoogleSignInPackageMigration() {
if (!__DEV__ || hasWarnedAboutGoogleSignInPackage) {
return;
}

hasWarnedAboutGoogleSignInPackage = true;
console.warn(
'Clerk: In the next major version, native Google Sign-In will require installing @clerk/expo-google-signin and adding its Expo config plugin. The @clerk/expo/google import path will continue to work.',
);
}

/**
* Helper to get Google client IDs from expo-constants or process.env.
* Dynamically imports expo-constants to keep it optional.
Expand Down Expand Up @@ -73,8 +61,6 @@ async function getGoogleClientIds(): Promise<{ webClientId?: string; iosClientId
*/
export function createUseSignInWithGoogle(platformConfig: PlatformConfig) {
return function useSignInWithGoogle() {
warnAboutGoogleSignInPackageMigration();

const clerk = useClerk();

clerk.telemetry?.record(eventMethodCalled('useSignInWithGoogle'));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { buildErrorThrower } from '@clerk/shared/error';
import type { SetActive, SignInResource, SignUpResource } from '@clerk/shared/types';

import { errorThrower } from '../utils/errors';
const errorThrower = buildErrorThrower({ packageName: PACKAGE_NAME });

type SignUpUnsafeMetadata = Record<string, unknown>;

Expand All @@ -21,9 +22,6 @@ export type StartGoogleAuthenticationFlowReturnType = {
* Native Google Authentication is only available on iOS and Android.
* For web platforms, use the OAuth-based Google Sign-In flow instead via useSSO.
*
* In the next major version, apps using native Google Sign-In will need to install
* `@clerk/expo-google-signin` alongside `@clerk/expo` and add its Expo config plugin.
*
* @example
* ```tsx
* import { useSSO } from '@clerk/expo';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { SetActive, SignInResource, SignUpResource } from '@clerk/shared/types';

type SignUpUnsafeMetadata = Record<string, unknown>;

export type StartGoogleAuthenticationFlowParams = {
unsafeMetadata?: SignUpUnsafeMetadata;
};
Comment on lines +3 to 7

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Reuse the shared SignUpUnsafeMetadata contract.

Record<string, unknown> freezes this field to a plain record and drops any app-specific augmentation on the shared SignUpUnsafeMetadata interface. Please reference the ambient shared type here instead of redefining it locally.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/expo-google-signin/src/useSignInWithGoogle.types.ts` around lines 3
- 7, The StartGoogleAuthenticationFlowParams type currently redeclares
SignUpUnsafeMetadata as a plain Record, which bypasses the shared ambient
contract and any app-specific augmentation. Update useSignInWithGoogle.types so
StartGoogleAuthenticationFlowParams references the shared SignUpUnsafeMetadata
symbol directly instead of defining a local alias, keeping the unsafeMetadata
field aligned with the common contract.

Expand Down
14 changes: 14 additions & 0 deletions packages/expo-google-signin/tsconfig.declarations.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"skipLibCheck": true,
"incremental": false,
"noEmit": false,
"declaration": true,
"emitDeclarationOnly": true,
"declarationMap": true,
"sourceMap": false,
"declarationDir": "./dist"
},
"exclude": ["**/__tests__/**/*", "app.plugin.js"]
}
27 changes: 27 additions & 0 deletions packages/expo-google-signin/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"compilerOptions": {
"allowJs": true,
"declaration": true,
"declarationMap": false,
"esModuleInterop": true,
"importHelpers": true,
"incremental": true,
"jsx": "react-jsx",
"lib": ["ESNext", "dom"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"noEmitOnError": false,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"outDir": "dist",
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": false,
"strict": true,
"target": "ES2019",
"types": ["node"],
"rootDir": "./src"
},
"include": ["src", "app.plugin.js"]
}
Loading
Loading