Skip to content
Draft
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
---

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`.
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"],
"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",
"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"
},
"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. 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`.
*
* @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`.
*
* @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,14 +1,15 @@
import { useClerk } from '@clerk/react';
import { isClerkAPIResponseError } from '@clerk/shared/error';
import { buildErrorThrower, isClerkAPIResponseError } from '@clerk/shared/error';
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 @@ -23,19 +24,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. 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 @@ -72,8 +60,6 @@ async function getGoogleClientIds(): Promise<{ webClientId?: string; iosClientId
*/
export function createUseSignInWithGoogle(platformConfig: PlatformConfig) {
return function useSignInWithGoogle() {
warnAboutGoogleSignInPackageMigration();

const clerk = useClerk();

async function startGoogleAuthenticationFlow(
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`.
*
* @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;
};
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