Skip to content

feat(expo-native-components): add single-token client sync bridge - #9957

Draft
mikepitre wants to merge 2 commits into
mike/expo-native-packagefrom
mike/expo-native-sync-bridge
Draft

mikepitre wants to merge 2 commits into
mike/expo-native-packagefrom
mike/expo-native-sync-bridge

Conversation

@mikepitre

@mikepitre mikepitre commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Description

Stacked on #9955. Adds the native half of the single-token client sync to @clerk/expo-native-components. The JS engine that consumes it lands in #9956.

The native SDK's stored device token is the only token. JS reads it before each FAPI request and writes rotated tokens back with compare-and-set. Both sides exchange payload-free invalidations and each refetches its own client.

The change is additive. configure, getClientToken, syncClientStateFromJs, and clerkNativeClientChanged still behave as before, so the current JS engine keeps working. The JS engine PR removes them later. Auth-flow and biometric functions are untouched.

New module surface (iOS and Android):

  • configureNative(publishableKey, seedDeviceToken): Promise<void>: configures the SDK with today's options (keychain service and host SDK header on iOS; foreground refresh disabled and custom headers on Android). If native has no stored token, it adopts a non-empty seed with compare-and-set against null. Otherwise native keeps its own token. It resolves without waiting for the client to load. Calling it again with the same key is a no-op. A different key goes through Clerk.reconfigure / Clerk.switchConfiguration, as configure does today. After adopting a seed it starts a background refreshClient(), so native loads the seeded client even if its initial load was fenced off by the token change. It does not emit clerkNativeClientInvalidated: the baseline is taken after configuration and seed adoption. It is named configureNative to avoid clashing with the existing configure. The JS engine PR can rename it once the old path is gone.
  • getDeviceToken(): Promise<string | null>: Clerk.shared.deviceToken / Clerk.getDeviceToken(). Resolves null before configuration.
  • setDeviceToken(token, expected): Promise<boolean>: Clerk.shared.setDeviceToken(_:expected:) / Clerk.setDeviceToken(token, expected). Rejects with E_NOT_CONFIGURED, E_INVALID_DEVICE_TOKEN (empty or blank token), E_CANCELLED (iOS only) or E_SET_DEVICE_TOKEN_FAILED.
  • refreshClient(): Promise<void>: rejects with E_NOT_CONFIGURED or E_REFRESH_CLIENT_FAILED.
  • clerkNativeClientInvalidated event, with no payload. It fires when this fingerprint of native state changes: client id, lastActiveSessionId, each session's id and status, the active user's id and updatedAt, and the device token. Changes are coalesced to at most one event per main-loop turn, and a change that reverts within the same turn is not emitted. A token that JS itself wrote with a successful setDeviceToken is not echoed back as an invalidation. That echo would make JS refetch after every rotation.
    • iOS observes Clerk.shared.client with withObservationTracking. It also re-checks the fingerprint after setDeviceToken and refreshClient, because deviceToken itself is not observable.
    • Android collects Clerk.clientFlow and re-checks the token on every emission and after setDeviceToken / refreshClient. clerk-android has no public token-change listener.

The JS Spec / ClerkExpoNativeModule types in @clerk/expo gain these functions as optional members, so the current engine still type-checks.

SDK dependencies: this needs the unreleased framework compare-and-set APIs from clerk/clerk-ios#583 and clerk/clerk-android#964.

  • iOS: ClerkExpo.podspec pins clerk-ios to the mike/framework-set-device-token branch. It must be switched back to an exact released version before merge.
  • Android: build.gradle still declares clerk-android 1.1.9, which does not have Clerk.setDeviceToken. The Android build needs clerk-android#964 released, and the version bumped, before merge. Until then the Android native build in CI will fail. It was compiled locally against a mavenLocal snapshot of that branch.

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

🤖 Generated with Claude Code

@vercel

vercel Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
clerk-js-sandbox Ready Ready Preview Sep 28, 2026 8:26pm UTC
swingset Ready Ready Preview Sep 28, 2026 8:26pm UTC

Request Review

@changeset-bot

changeset-bot Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fa311e9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@clerk/expo-native-components Minor

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

@coderabbitai

coderabbitai Bot commented Sep 27, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Comment @coderabbitai help to get the list of available commands.

mikepitre and others added 2 commits September 28, 2026 16:09
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mikepitre
mikepitre force-pushed the mike/expo-native-sync-bridge branch from 8ad6eb2 to fa311e9 Compare September 28, 2026 20:23
@mikepitre mikepitre changed the title feat(expo-native): add single-token client sync bridge feat(expo-native-components): add single-token client sync bridge Sep 28, 2026

This branch was successfully deployed

2 active deployments
Preview – swingset — fa311e94 Deployed Sep 28, 2026 by vercel[bot]
Preview – clerk-js-sandbox — fa311e94 Deployed Sep 28, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant