Skip to content

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

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

mikepitre wants to merge 6 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 Oct 2, 2026 4:05pm UTC
swingset Ready Ready Preview Oct 2, 2026 4:05pm UTC

Request Review

@changeset-bot

changeset-bot Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: cb9e44e

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
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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

@github-actions github-actions Bot added the expo label Sep 27, 2026
@mikepitre
mikepitre force-pushed the mike/expo-native-sync-bridge branch from 17b87b7 to 8ad6eb2 Compare September 28, 2026 19:35
@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
@mikepitre
mikepitre force-pushed the mike/expo-native-package branch from 170d56f to 2c6f690 Compare September 29, 2026 22:11
@mikepitre
mikepitre force-pushed the mike/expo-native-sync-bridge branch from fa311e9 to fc1085a Compare September 29, 2026 22:11
mikepitre and others added 2 commits September 30, 2026 09:51
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…idge

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…idge

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…idge

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

# Conflicts:
#	packages/expo-native-components/ios/ClerkExpo.podspec

This branch was successfully deployed

2 active deployments
Preview – swingset — cb9e44ec Deployed Oct 2, 2026 by vercel[bot]
Preview – clerk-js-sandbox — cb9e44ec Deployed Oct 2, 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