From f7ad95179eb1e1ba40e70cec5e98ab3ba071b674 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Tue, 11 Aug 2026 07:43:15 -0700 Subject: [PATCH 1/4] fix(expo): Allow disabling native client sync --- .changeset/calm-pandas-sync.md | 5 ++++ packages/expo/src/provider/ClerkProvider.tsx | 14 ++++++++-- .../ClerkProvider.nativeClientSync.test.tsx | 27 +++++++++++++++++++ .../expo/src/provider/nativeClientSync.tsx | 12 ++++++++- 4 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 .changeset/calm-pandas-sync.md diff --git a/.changeset/calm-pandas-sync.md b/.changeset/calm-pandas-sync.md new file mode 100644 index 00000000000..ed902729a5c --- /dev/null +++ b/.changeset/calm-pandas-sync.md @@ -0,0 +1,5 @@ +--- +'@clerk/expo': patch +--- + +Add an internal `ClerkProvider` option to disable native client synchronization when Clerk native components are not used. diff --git a/packages/expo/src/provider/ClerkProvider.tsx b/packages/expo/src/provider/ClerkProvider.tsx index 720f424031c..686aa10bd06 100644 --- a/packages/expo/src/provider/ClerkProvider.tsx +++ b/packages/expo/src/provider/ClerkProvider.tsx @@ -49,6 +49,13 @@ export type ClerkProviderProps = Omit(props: ClerkProviderProps>(new Set()); const suppressTokenCacheNotificationsRef = useRef(0); const nativeRefreshFromJsControllerRef = useRef(null); @@ -91,6 +100,7 @@ export function ClerkProvider(props: ClerkProviderProps(props: ClerkProviderProps(props: ClerkProviderProps - {isNative() && ( + {nativeClientSyncEnabled && ( { }); }); + test('does not start native client synchronization when disabled', async () => { + const originalHandleUnauthenticated = mocks.clerkInstance.handleUnauthenticated; + + render( + , + ); + + await act(async () => { + await Promise.resolve(); + }); + + expect(mocks.configure).not.toHaveBeenCalled(); + expect(mocks.getClientToken).not.toHaveBeenCalled(); + expect(mocks.clerkInstance.addListener).not.toHaveBeenCalled(); + expect(mocks.clerkInstance.handleUnauthenticated).toBe(originalHandleUnauthenticated); + + await act(async () => { + await mocks.clerkOptions?.tokenCache?.saveToken(CLERK_CLIENT_JWT_KEY, 'client-token'); + }); + + expect(mocks.syncClientStateFromJs).not.toHaveBeenCalled(); + }); + test('configures native once with the cached device token during StrictMode bootstrap', async () => { mocks.tokenCache.getToken.mockResolvedValue('client-token'); mocks.getClientToken.mockResolvedValue('client-token'); diff --git a/packages/expo/src/provider/nativeClientSync.tsx b/packages/expo/src/provider/nativeClientSync.tsx index 5c5bca2c978..6eb91627a9a 100644 --- a/packages/expo/src/provider/nativeClientSync.tsx +++ b/packages/expo/src/provider/nativeClientSync.tsx @@ -962,12 +962,14 @@ function waitForClerkInstanceLoad(clerkInstance: SyncableClerkInstance): Promise } export function useNativeClientBootstrap({ + enabled, publishableKey, nativeRefreshFromJsControllerRef, suppressTokenCacheNotificationsRef, tokenCache, clerkInstance, }: { + enabled: boolean; publishableKey: string; nativeRefreshFromJsControllerRef: MutableRefObject; suppressTokenCacheNotificationsRef: MutableRefObject; @@ -982,6 +984,7 @@ export function useNativeClientBootstrap({ isMountedRef.current = true; if ( + enabled && (Platform.OS === 'ios' || Platform.OS === 'android') && publishableKey && startedPublishableKeyRef.current !== publishableKey @@ -1083,7 +1086,14 @@ export function useNativeClientBootstrap({ return () => { isMountedRef.current = false; }; - }, [publishableKey, nativeRefreshFromJsControllerRef, suppressTokenCacheNotificationsRef, tokenCache, clerkInstance]); + }, [ + enabled, + publishableKey, + nativeRefreshFromJsControllerRef, + suppressTokenCacheNotificationsRef, + tokenCache, + clerkInstance, + ]); return { isMountedRef, From d77ed1873681379126445379da55e4fab65f70bf Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Tue, 11 Aug 2026 07:54:18 -0700 Subject: [PATCH 2/4] chore: remove unused maestro flow --- integration/templates/expo-native/App.tsx | 6 -- .../expo-native/components/E2EControls.tsx | 57 ------------ .../expo-native/components/JsSignInForm.tsx | 92 ------------------- .../modules/e2e-hooks/android/build.gradle | 48 ---------- .../java/com/clerk/e2ehooks/E2EHooksModule.kt | 35 ------- .../modules/e2e-hooks/expo-module.config.json | 6 -- ...sion-survives-native-token-divergence.yaml | 44 --------- 7 files changed, 288 deletions(-) delete mode 100644 integration/templates/expo-native/components/E2EControls.tsx delete mode 100644 integration/templates/expo-native/components/JsSignInForm.tsx delete mode 100644 integration/templates/expo-native/modules/e2e-hooks/android/build.gradle delete mode 100644 integration/templates/expo-native/modules/e2e-hooks/android/src/main/java/com/clerk/e2ehooks/E2EHooksModule.kt delete mode 100644 integration/templates/expo-native/modules/e2e-hooks/expo-module.config.json delete mode 100644 integration/tests/expo-native/flows/session-survives-native-token-divergence.yaml diff --git a/integration/templates/expo-native/App.tsx b/integration/templates/expo-native/App.tsx index 967814e9f68..e79f6f26443 100644 --- a/integration/templates/expo-native/App.tsx +++ b/integration/templates/expo-native/App.tsx @@ -4,9 +4,7 @@ import { tokenCache } from '@clerk/expo/token-cache'; import { useState } from 'react'; import { Button, Modal, StyleSheet, Text, View } from 'react-native'; -import { E2EControls } from './components/E2EControls'; import { GoogleSignInButton } from './components/GoogleSignInButton'; -import { JsSignInForm } from './components/JsSignInForm'; const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY; @@ -19,7 +17,6 @@ function NativeBuildFixture() { const { user } = useUser(); const [isAuthOpen, setIsAuthOpen] = useState(false); const [isProfileOpen, setIsProfileOpen] = useState(false); - const [e2eStatus, setE2eStatus] = useState(null); return ( @@ -36,9 +33,6 @@ function NativeBuildFixture() { onPress={() => setIsAuthOpen(true)} /> {!isSignedIn && } - {!isSignedIn && } - {isSignedIn && } - {e2eStatus && {e2eStatus}} {isSignedIn && (