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
5 changes: 5 additions & 0 deletions .changeset/simplify-expo-native-client-sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/expo': minor
---

Rewrite the sync between the Clerk JS client and the native Clerk SDK. Both now share a single device token stored by the native SDK, and startup makes fewer requests. This fixes a stale token in the app's `tokenCache` signing out a session that was signed in natively, and the two SDKs ending up on different tokens when both rotated the device token at the same time. Requires the matching native module that ships with the next native SDK versions.
103 changes: 0 additions & 103 deletions packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts

This file was deleted.

7 changes: 4 additions & 3 deletions packages/expo/src/hooks/__tests__/useSignInWithGoogle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@ vi.mock('react-native', () => {
vi.mock('../../specs/NativeClerkModule', () => {
return {
default: {
configure: vi.fn(),
getClientToken: vi.fn(),
syncClientStateFromJs: vi.fn(),
configureNative: vi.fn(),
getDeviceToken: vi.fn(),
setDeviceToken: vi.fn(),
refreshClient: vi.fn(),
},
};
});
Expand Down
100 changes: 0 additions & 100 deletions packages/expo/src/hooks/useNativeClientEvents.ts

This file was deleted.

66 changes: 18 additions & 48 deletions packages/expo/src/provider/ClerkProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,13 @@ import '../polyfills';

import type { ClerkProviderProps as ReactClerkProviderProps } from '@clerk/react';
import { InternalClerkProvider as ClerkReactProvider, type Ui } from '@clerk/react/internal';
import { useRef } from 'react';
import { useEffect, useMemo, useRef } from 'react';

import type { TokenCache } from '../cache/types';
import { ClerkExpoModule } from '../utils/native-module';
import { isNative, isWeb } from '../utils/runtime';
import { maybeCompleteAuthSession } from './maybeCompleteAuthSession';
import {
type DeviceTokenCacheListener,
NativeClientSync,
type NativeRefreshFromJsController,
useNativeClientBootstrap,
useNativeClientEventSync,
useSyncableTokenCache,
} from './nativeClientSync';
import { createNativeClientSync, type SyncableClerk } from './nativeClientSync';
import { getClerkInstance } from './singleton';
import type { BuildClerkOptions } from './singleton/types';

Expand Down Expand Up @@ -77,45 +71,32 @@ export function ClerkProvider<TUi extends Ui = Ui>(props: ClerkProviderProps<TUi
...rest
} = props;
const pk = publishableKey;
const nativeClientSyncEnabled = isNative() && !__experimental_disableNativeClientSync;
const tokenCacheListenersRef = useRef<Set<DeviceTokenCacheListener>>(new Set());
const suppressTokenCacheNotificationsRef = useRef(0);
const nativeRefreshFromJsControllerRef = useRef<NativeRefreshFromJsController | null>(null);
const syncableTokenCache = useSyncableTokenCache({
suppressTokenCacheNotificationsRef,
tokenCache,
tokenCacheListenersRef,
});
const nativeModule = isNative() && !__experimental_disableNativeClientSync ? ClerkExpoModule : null;
const tokenCacheRef = useRef(tokenCache);
tokenCacheRef.current = tokenCache;
const nativeClientSync = useMemo(
() => (nativeModule ? createNativeClientSync(nativeModule, pk, () => tokenCacheRef.current) : null),
[nativeModule, pk],
);

const clerkInstance = isNative()
? getClerkInstance({
publishableKey: pk,
tokenCache: syncableTokenCache,
tokenCache: nativeClientSync?.tokenCache ?? tokenCache,
proxyUrl,
domain,
__experimental_passkeys,
__experimental_resourceCache,
})
: null;

const suppressJsClientChangedRef = useRef(0);
const { isMountedRef, isNativeClientReady } = useNativeClientBootstrap({
enabled: nativeClientSyncEnabled,
publishableKey: pk,
nativeRefreshFromJsControllerRef,
suppressTokenCacheNotificationsRef,
tokenCache: syncableTokenCache,
clerkInstance,
});
useNativeClientEventSync({
enabled: nativeClientSyncEnabled && isNativeClientReady,
clerkInstance,
isMountedRef,
nativeRefreshFromJsControllerRef,
suppressJsClientChangedRef,
suppressTokenCacheNotificationsRef,
tokenCache: syncableTokenCache,
});
useEffect(
() =>
nativeClientSync && clerkInstance
? nativeClientSync.attach(clerkInstance as unknown as SyncableClerk)
: undefined,
[nativeClientSync, clerkInstance],
);

// Needed for `useOAuth` / `useSSO` to work correctly on web — must stay synchronous during render
// so the redirect URL is caught before children mount. Resolves to a no-op on native via the
Expand Down Expand Up @@ -144,17 +125,6 @@ export function ClerkProvider<TUi extends Ui = Ui>(props: ClerkProviderProps<TUi
...(isNative() && { runtimeEnvironment: 'headless' as const }),
}}
>
{nativeClientSyncEnabled && (
<NativeClientSync
enabled={isNativeClientReady}
clerkInstance={clerkInstance}
nativeRefreshFromJsControllerRef={nativeRefreshFromJsControllerRef}
suppressJsClientChangedRef={suppressJsClientChangedRef}
suppressTokenCacheNotificationsRef={suppressTokenCacheNotificationsRef}
tokenCache={syncableTokenCache}
tokenCacheListenersRef={tokenCacheListenersRef}
/>
)}
{children}
</ClerkReactProvider>
);
Expand Down
Loading
Loading