javascript icon indicating copy to clipboard operation
javascript copied to clipboard

feat(clerk-js): Stale-while-revalidate session token

Open jacekradko opened this issue 3 months ago • 4 comments

Description

Return a cached token and schedule background refresh if the token is read while in the expiration leeway timeframe.

Fixes: USER-4087

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:

Summary by CodeRabbit

Release Notes

  • New Features

    • Added refreshIfStale option to token retrieval API, enabling proactive fresh token fetching when cached token is within refresh leeway.
    • Implemented stale-while-revalidate behavior: immediately returns cached token while automatically refreshing in the background.
  • Improvements

    • Enhanced token expiration handling with improved leeway calculations for more reliable token freshness guarantees.

✏️ Tip: You can customize this high-level summary in your review settings.

jacekradko avatar Nov 26 '25 03:11 jacekradko

⚠️ No Changeset found

Latest commit: 441532faf7642b4ab2ceb6fe9814e0baef44e66c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

changeset-bot[bot] avatar Nov 26 '25 03:11 changeset-bot[bot]

Walkthrough

The pull request introduces stale-while-revalidate (SWR) semantics to the token caching system. Instead of immediately evicting stale tokens, the cache now returns them with a needsRefresh flag, enabling immediate token availability while triggering background refresh. This prevents artificial latency when tokens are accessed in hot paths.

Changes

Cohort / File(s) Summary
Token cache core logic
packages/clerk-js/src/core/tokenCache.ts
Modified get() to return TokenCacheGetResult object with entry and needsRefresh flag. Implements SWR by returning stale tokens with refresh signals instead of evicting them. Added resolvedToken field to cache entries for synchronous reads and imported POLLER_INTERVAL_IN_MS for TTL alignment.
Session token retrieval
packages/clerk-js/src/core/resources/Session.ts
Added refreshIfStale option to token retrieval and refactored token fetching into private methods: #fetchToken, #createTokenResolver, and #dispatchTokenEvents. Updated to handle new cache result shape and resolve tokens from either pre-resolved values or token resolvers.
Token type definitions
packages/shared/src/types/session.ts
Added refreshIfStale?: boolean option to GetTokenOptions and reordered fields for consistency.
Session cookie polling
packages/clerk-js/src/core/auth/SessionCookiePoller.ts
Exported new constant POLLER_INTERVAL_IN_MS (5 seconds) and updated internal polling timer to use it.
Cookie service
packages/clerk-js/src/core/auth/AuthCookieService.ts
Updated to use getToken({ refreshIfStale: true }) for proactive token refresh when updating cookies.
Test suite
packages/clerk-js/src/core/__tests__/tokenCache.test.ts, packages/clerk-js/src/core/resources/__tests__/Session.test.ts
Updated all cache lookup assertions to access result?.entry and result?.needsRefresh. Added extensive new test cases verifying SWR behavior, leeway handling, token resolution timing, and multi-session isolation with the new cache API shape.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Session
    participant TokenCache
    participant Poller
    participant Backend

    Client->>Session: getToken({ refreshIfStale: true })
    activate Session
    Session->>TokenCache: get(cacheKey)
    activate TokenCache
    alt Token exists and valid
        TokenCache->>TokenCache: Check remaining TTL vs leeway
        alt Within leeway threshold
            TokenCache-->>Session: { entry, needsRefresh: true }
        else Valid with buffer
            TokenCache-->>Session: { entry, needsRefresh: false }
        end
    else Token stale or missing
        TokenCache-->>Session: undefined
    end
    deactivate TokenCache
    
    alt Token returned with needsRefresh=true
        Session->>Session: Return cached token immediately
        Session-->>Client: token
        par Background Refresh
            Session->>Backend: Fetch fresh token
            Backend-->>Session: New token
            Session->>TokenCache: Update cache with fresh token
            Session->>Poller: Signal refresh complete
        end
    else No token or refreshIfStale=false
        Session->>Backend: Fetch fresh token
        Backend-->>Session: New token
        Session->>TokenCache: Cache new token + resolver
        Session-->>Client: token
    end
    deactivate Session

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Token cache API change: The get() method's return type signature change from TokenCacheEntry | undefined to TokenCacheGetResult | undefined requires verification across all call sites and test assertions.
  • SWR state machine logic: The leeway and TTL calculations in tokenCache.ts (including needsRefresh determination based on remaining TTL, poller interval alignment, and edge cases) warrant careful review.
  • Session token refactoring: The introduction of private methods #fetchToken, #createTokenResolver, and #dispatchTokenEvents consolidates token fetching; verify event dispatch order and resolver lifecycle.
  • Multi-path token resolution: Ensure token resolution from pre-resolved values vs. awaiting resolvers is handled consistently across all code paths.
  • Test coverage density: Substantial test additions for SWR scenarios, leeway variations, and concurrent requests; verify test assumptions align with intended behavior.

Poem

🐰 Hops of joy with tokens swift,
No more waiting—here's the gift!
Stale but spry, we fetch anew,
While background gnomes refresh for you!

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(clerk-js): Stale-while-revalidate session token' accurately describes the main feature being implemented - adding stale-while-revalidate behavior for session tokens.
Linked Issues check ✅ Passed All objectives from USER-4087 are met: cache entries are no longer deleted during leeway [tokenCache.ts], cached tokens are returned even within leeway [Session.ts], background refresh is performed asynchronously [SessionCookiePoller.ts, Session.ts], and latency is eliminated by immediate token return.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing stale-while-revalidate behavior: core cache logic [tokenCache.ts], session token handling [Session.ts], cookie polling [SessionCookiePoller.ts], cookie service updates [AuthCookieService.ts], type definitions [session.ts], and comprehensive test coverage aligning with the feature.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • [ ] 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • [ ] Create PR with unit tests
  • [ ] Post copyable unit tests in a comment
  • [ ] Commit unit tests in branch feat/stale-while-revalidate-token

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

coderabbitai[bot] avatar Nov 26 '25 03:11 coderabbitai[bot]

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

Project Deployment Review Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Dec 17, 2025 1:38pm

vercel[bot] avatar Nov 26 '25 03:11 vercel[bot]

Open in StackBlitz

@clerk/agent-toolkit

npm i https://pkg.pr.new/@clerk/agent-toolkit@7317
@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@7317
@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@7317
@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@7317
@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@7317
@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@7317
@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@7317
@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@7317
@clerk/express

npm i https://pkg.pr.new/@clerk/express@7317
@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@7317
@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@7317
@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@7317
@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@7317
@clerk/react

npm i https://pkg.pr.new/@clerk/react@7317
@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@7317
@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@7317
@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@7317
@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@7317
@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@7317
@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@7317
@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@7317

commit: 441532f

pkg-pr-new[bot] avatar Dec 02 '25 15:12 pkg-pr-new[bot]