Skip to content

fix(shared): correct isValidBrowser/isBrowserOnline for React Native - #8086

Closed
chriscanin wants to merge 1 commit into
mainfrom
chris/fix-gettoken-template-offline-rn
Closed

fix(shared): correct isValidBrowser/isBrowserOnline for React Native#8086
chriscanin wants to merge 1 commit into
mainfrom
chris/fix-gettoken-template-offline-rn

Conversation

@chriscanin

@chriscaninchriscanin commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • isValidBrowser() and isBrowserOnline() in @clerk/shared were returning false when window/navigator are undefined (React Native environments)
  • Core-3's token fetching checks these functions before making FAPI calls — returning false caused getToken({ template: '...' }) to silently fail on React Native while getToken() (which reads from local cache) continued to work
  • Fix: return true in non-browser environments since React Native has its own networking layer and should not be treated as a bot or offline

Reported by

Bledar via Discord — getToken({ template: 'one_hour_token' }) returns nothing on Expo after upgrading to latest Clerk

Test plan

  • Updated existing browser.spec.ts test to verify isValidBrowser() returns true when window is undefined
  • All 983 tests in @clerk/shared pass
  • Verify getToken({ template }) works on React Native after fix

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed token retrieval with templates in React Native and other non-browser environments. Non-browser runtimes are now correctly recognized as valid execution contexts, ensuring template-based token operations function properly across React Native, server-side rendering, and similar platforms. This resolves compatibility issues that previously prevented proper authentication token management in these environments.

…owser environments
In React Native, there is no `window` or `navigator`, so `isValidBrowser()` and
`isBrowserOnline()` were returning `false`. This caused core-3's token fetching
to skip FAPI calls for templated tokens (`getToken({ template })`) since it
believed the environment was invalid/offline.
Non-browser runtimes like React Native have their own networking layer and should
not be treated as bots or offline. Return `true` when browser APIs are absent.
@vercel

vercelBot commented Mar 16, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentMar 16, 2026 4:38pm

Request Review

@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c3e2e81

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

This PR includes changesets to release 21 packages
NameType
@clerk/sharedPatch
@clerk/agent-toolkitPatch
@clerk/astroPatch
@clerk/backendPatch
@clerk/chrome-extensionPatch
@clerk/clerk-jsPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/uiPatch
@clerk/vuePatch

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

@pkg-pr-new

Copy link
Copy Markdown

Open in StackBlitz

@clerk/agent-toolkit

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

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8086

@clerk/dev-cli

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

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8086

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8086

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8086

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: c3e2e81

@chriscaninchriscanin self-assigned this Mar 16, 2026
@coderabbitai

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This change modifies the behavior of browser detection functions in the shared package to support token retrieval in React Native environments. The isValidBrowser() and isBrowserOnline() functions in browser.ts are updated to return true when running in non-browser environments instead of false. Corresponding test expectations are updated to reflect this new behavior. A changeset file documents this as a patch release for "@clerk/shared".

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe pull request title clearly and specifically describes the main change: fixing isValidBrowser and isBrowserOnline behavior for React Native environments.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Tip

CodeRabbit can use Trivy to scan for security misconfigurations and secrets in Infrastructure as Code files.

Add a .trivyignore file to your project to customize which findings Trivy reports.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/browser.ts`:
- Around line 60-63: The functions isValidBrowser() and isBrowserOnline() should
not return true for all non-navigator runtimes; narrow the "true" case to only
React Native. Update isValidBrowser() and isBrowserOnline() to detect React
Native explicitly (e.g., check navigator?.product === 'ReactNative' or another
RN-specific signal) and return true only in that case, otherwise return false
for generic non-browser runtimes so code like the guard in webauthn.ts
("isValidBrowser() && typeof window.PublicKeyCredential === 'function'") does
not attempt to access window in SSR/Node environments; ensure the detection
logic is used in the existing functions named isValidBrowser and
isBrowserOnline.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6d624320-01f8-40cd-9142-3372eccc1f4f

📥 Commits

Reviewing files that changed from the base of the PR and between da2b239 and c3e2e81.

📒 Files selected for processing (3)
  • .changeset/fix-gettoken-template-rn.md
  • packages/shared/src/__tests__/browser.spec.ts
  • packages/shared/src/browser.ts

Comment on lines +60 to +63
// Not in a browser environment (e.g. React Native, SSR).
// Return true so non-browser runtimes are not incorrectly
// treated as bots or automated browsers.
return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"1) Find call sites that use isValidBrowser() as a browser/window guard:"
rg -nP --type=ts --type=tsx -C3 'isValidBrowser\(\)\s*&&\s*(typeof\s+window\.|window\.)'echoecho"2) Inspect key downstream paths impacted by non-browser=true behavior:"
rg -nP -C3 '\bisBrowserOnline\(\)|\bisValidBrowserOnline\(\)' \
packages/clerk-js/src/core/resources/Session.ts \
packages/clerk-js/src/core/fapiClient.ts \
packages/shared/src/webauthn.ts
echoecho"Expected:"echo"- If any server-side/SSR path relies on isValidBrowser() to guard window access,"echo" current change is unsafe and should be narrowed to RN detection."

Repository: clerk/javascript

Length of output: 176


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Step 1: Check actual code in browser.ts around lines 60-63 and 76-79 ==="
cat -n packages/shared/src/browser.ts | sed -n '50,85p'echoecho"=== Step 2: Find all imports and usages of isValidBrowser ==="
rg -n 'isValidBrowser' packages/ --max-count=20
echoecho"=== Step 3: Find all imports and usages of isBrowserOnline ==="
rg -n 'isBrowserOnline' packages/ --max-count=20
echoecho"=== Step 4: Check webauthn.ts guard pattern ==="
cat -n packages/shared/src/webauthn.ts | head -20

Repository: clerk/javascript

Length of output: 7354


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Test expectations for isValidBrowser with no navigator ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '40,75p'echoecho"=== Check if webauthn.ts has any build-time guards or SSR exclusion ==="
grep -r "webauthn" packages/ --include="*.ts" --include="*.tsx" --include="*.json"| grep -E "(exclude|server|ssr|node)"||echo"No SSR-specific guards found"echoecho"=== Check for React Native detection utility in codebase ==="
rg -n "ReactNative|react.native|react-native" packages/shared/src/ | head -20
echoecho"=== Check inBrowser() implementation ==="
cat -n packages/shared/src/browser.ts | sed -n '1,50p'

Repository: clerk/javascript

Length of output: 3163


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Where is isWebAuthnSupported() actually called? ==="
rg -rn "isWebAuthnSupported|webauthn" packages/ --type=ts --type=tsx --include="*.ts" --include="*.tsx"| grep -v "node_modules"| grep -v "test"| head -30
echoecho"=== Check if webauthn module is conditionally loaded ==="
rg -rn "from.*webauthn|import.*webauthn" packages/ --include="*.ts" --include="*.tsx"| grep -v testechoecho"=== Full webauthn.ts to understand usage pattern ==="
cat packages/shared/src/webauthn.ts

Repository: clerk/javascript

Length of output: 574


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Where is isWebAuthnSupported() or webauthn functions called? ==="
rg -n "isWebAuthnSupported|isWebAuthnAutofillSupported" packages/ echoecho"=== Check all webauthn imports ==="
rg -n "from.*webauthn|import.*webauthn" packages/
echoecho"=== Full webauthn.ts content ==="
cat packages/shared/src/webauthn.ts

Repository: clerk/javascript

Length of output: 8269


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check if webauthn.ts is server-side accessible or client-only ==="
rg -n "webauthn" packages/shared/package.json packages/shared/tsconfig.json 2>/dev/null ||echo"Not in explicit exports"echoecho"=== Check how webauthn.ts is exported from `@clerk/shared` ==="
cat packages/shared/package.json | grep -A 20 '"exports"'echoecho"=== Look at complete test for isValidBrowser with no navigator ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '41,47p'echoecho"=== Does the test actually expect this behavior? Look at comment ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '40,50p'echoecho"=== Check if there's conditional execution or build-time webauthn exclusion ==="
grep -r "webauthn" packages/clerk-js/src --include="*.json" --include="*.config.*"2>/dev/null ||echo"No special build config for webauthn"echoecho"=== Key: check if window.PublicKeyCredential will actually throw in SSR ==="echo"Testing: in Node.js SSR, accessing window.PublicKeyCredential"
node -e "try { console.log(typeof window.PublicKeyCredential); } catch(e) { console.log('Error:', e.message); }"

Repository: clerk/javascript

Length of output: 2066


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Final verification: Can isWebAuthnSupported ever run in SSR? ==="echo"Check if SignIn/Session resources are server-side initialized:"
rg -n "new SignIn\|new Session\|__internal_isWebAuthnSupported" packages/clerk-js/src/core/clerk.ts | head -15
echoecho"=== Confirm: in SSR with new isValidBrowser() change, this will crash ==="echo"Simulating: inBrowser()=false, navigator=null, then evaluating typeof window.PublicKeyCredential"
cat > /tmp/test.js << 'EOF'// Simulate isValidBrowser() with new changefunction isValidBrowser_NEW() { // In SSR: inBrowser() is false, so navigator becomes null const navigator = false ? null : null; // inBrowser() returns false if (!navigator) { return true; // NEW BEHAVIOR: return true for non-browser } return false;}// Now the guard in isWebAuthnSupportedfunction isWebAuthnSupported() { return ( isValidBrowser_NEW() && typeof window.PublicKeyCredential === 'function' // <-- will crash );}try { console.log("Result:", isWebAuthnSupported());} catch (e) { console.log("CRASH:", e.message);}EOF
node /tmp/test.js

Repository: clerk/javascript

Length of output: 191


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check if clerk resources are instantiated in SSR initialization ==="
rg -n "initializeClerk|new SignIn|new Session" packages/clerk-js/src/core/clerk.ts | head -5
echoecho"=== Confirm window check in isValidBrowser matches inBrowser pattern ==="
cat -n packages/shared/src/browser.ts | sed -n '57,66p'

Repository: clerk/javascript

Length of output: 131


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Verify: is webauthn always guarded with typeof checks on import side? ==="
cat -n packages/clerk-js/src/core/resources/SignIn.ts | sed -n '70,80p'echoecho"=== Check full context of how isWebAuthnSupported is used in SignIn ==="
cat -n packages/clerk-js/src/core/resources/SignIn.ts | sed -n '514,525p'echoecho"=== Simplified: In SSR, would isWebAuthnSupported() be called? ==="echo"Check if SignIn methods are accessible in SSR or only browser-side"
grep -n "class SignIn" packages/clerk-js/src/core/resources/SignIn.ts | head -1
</script>

Repository: clerk/javascript

Length of output: 1639


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check: When are webauthn functions evaluated? At load time or at runtime? ==="
cat -n packages/shared/src/webauthn.ts
echoecho"=== Check: Are these webauthn functions called during SSR server init? ==="echo"They're exported from `@clerk/shared` and imported in clerk-js"echo"But are they called during non-interactive SSR, or only during client-side flows?"echoecho"=== The critical question: Does the guard actually protect? ==="echo"In webauthn.ts line 8:"echo" isValidBrowser() && typeof window.PublicKeyCredential === 'function'"echo""echo"With new isValidBrowser() = true in SSR:"echo" true && typeof window.PublicKeyCredential === 'function'"echo" ^ This evaluates to the right side"echo" ^ But we're in Node.js where window is undefined"echo" -> ReferenceError: window is not defined"

Repository: clerk/javascript

Length of output: 1880


isValidBrowser() and isBrowserOnline() returning true for all non-navigator cases breaks webauthn guard semantics

Lines 60 and 76 returning true for all non-browser runtimes (not just React Native) creates a safety issue. In packages/shared/src/webauthn.ts:8–10, the guard pattern isValidBrowser() && typeof window.PublicKeyCredential === 'function' relies on the first condition being false in non-browser environments. With this change, SSR/Node.js environments will pass the first condition (true), then evaluate typeof window.PublicKeyCredential, which throws ReferenceError: window is not defined since typeof doesn't protect property access.

The test comment correctly identifies the intent ("React Native"), but the implementation conflates React Native with generic non-browser runtimes. Narrow the true return to React Native detection only:

+function isReactNativeRuntime(): boolean {+ return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';+}+
export function isValidBrowser(): boolean {
const navigator = inBrowser() ? window?.navigator : null;
if (!navigator) {
- return true;+ return isReactNativeRuntime();
}
return !userAgentIsRobot(navigator?.userAgent) && !navigator?.webdriver;
}
export function isBrowserOnline(): boolean {
const navigator = inBrowser() ? window?.navigator : null;
if (!navigator) {
- return true;+ return isReactNativeRuntime();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/shared/src/browser.ts` around lines 60 - 63, The functions
isValidBrowser() and isBrowserOnline() should not return true for all
non-navigator runtimes; narrow the "true" case to only React Native. Update
isValidBrowser() and isBrowserOnline() to detect React Native explicitly (e.g.,
check navigator?.product === 'ReactNative' or another RN-specific signal) and
return true only in that case, otherwise return false for generic non-browser
runtimes so code like the guard in webauthn.ts ("isValidBrowser() && typeof
window.PublicKeyCredential === 'function'") does not attempt to access window in
SSR/Node environments; ensure the detection logic is used in the existing
functions named isValidBrowser and isBrowserOnline.

@chriscanin

Copy link
Copy Markdown
ContributorAuthor

This PR is being closed in favor of #8084.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@chriscanin
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
fix(shared): correct isValidBrowser/isBrowserOnline for React Native by chriscanin · Pull Request #8086 · clerk/javascript · GitHub
Skip to content

fix(shared): correct isValidBrowser/isBrowserOnline for React Native - #8086

Closed
chriscanin wants to merge 1 commit into
mainfrom
chris/fix-gettoken-template-offline-rn
Closed

fix(shared): correct isValidBrowser/isBrowserOnline for React Native#8086
chriscanin wants to merge 1 commit into
mainfrom
chris/fix-gettoken-template-offline-rn

Conversation

@chriscanin

@chriscaninchriscanin commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • isValidBrowser() and isBrowserOnline() in @clerk/shared were returning false when window/navigator are undefined (React Native environments)
  • Core-3's token fetching checks these functions before making FAPI calls — returning false caused getToken({ template: '...' }) to silently fail on React Native while getToken() (which reads from local cache) continued to work
  • Fix: return true in non-browser environments since React Native has its own networking layer and should not be treated as a bot or offline

Reported by

Bledar via Discord — getToken({ template: 'one_hour_token' }) returns nothing on Expo after upgrading to latest Clerk

Test plan

  • Updated existing browser.spec.ts test to verify isValidBrowser() returns true when window is undefined
  • All 983 tests in @clerk/shared pass
  • Verify getToken({ template }) works on React Native after fix

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed token retrieval with templates in React Native and other non-browser environments. Non-browser runtimes are now correctly recognized as valid execution contexts, ensuring template-based token operations function properly across React Native, server-side rendering, and similar platforms. This resolves compatibility issues that previously prevented proper authentication token management in these environments.

…owser environments
In React Native, there is no `window` or `navigator`, so `isValidBrowser()` and
`isBrowserOnline()` were returning `false`. This caused core-3's token fetching
to skip FAPI calls for templated tokens (`getToken({ template })`) since it
believed the environment was invalid/offline.
Non-browser runtimes like React Native have their own networking layer and should
not be treated as bots or offline. Return `true` when browser APIs are absent.
@vercel

vercelBot commented Mar 16, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentMar 16, 2026 4:38pm

Request Review

@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c3e2e81

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

This PR includes changesets to release 21 packages
NameType
@clerk/sharedPatch
@clerk/agent-toolkitPatch
@clerk/astroPatch
@clerk/backendPatch
@clerk/chrome-extensionPatch
@clerk/clerk-jsPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/uiPatch
@clerk/vuePatch

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

@pkg-pr-new

Copy link
Copy Markdown

Open in StackBlitz

@clerk/agent-toolkit

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

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8086

@clerk/dev-cli

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

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8086

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8086

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8086

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: c3e2e81

@chriscaninchriscanin self-assigned this Mar 16, 2026
@coderabbitai

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This change modifies the behavior of browser detection functions in the shared package to support token retrieval in React Native environments. The isValidBrowser() and isBrowserOnline() functions in browser.ts are updated to return true when running in non-browser environments instead of false. Corresponding test expectations are updated to reflect this new behavior. A changeset file documents this as a patch release for "@clerk/shared".

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe pull request title clearly and specifically describes the main change: fixing isValidBrowser and isBrowserOnline behavior for React Native environments.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Tip

CodeRabbit can use Trivy to scan for security misconfigurations and secrets in Infrastructure as Code files.

Add a .trivyignore file to your project to customize which findings Trivy reports.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/browser.ts`:
- Around line 60-63: The functions isValidBrowser() and isBrowserOnline() should
not return true for all non-navigator runtimes; narrow the "true" case to only
React Native. Update isValidBrowser() and isBrowserOnline() to detect React
Native explicitly (e.g., check navigator?.product === 'ReactNative' or another
RN-specific signal) and return true only in that case, otherwise return false
for generic non-browser runtimes so code like the guard in webauthn.ts
("isValidBrowser() && typeof window.PublicKeyCredential === 'function'") does
not attempt to access window in SSR/Node environments; ensure the detection
logic is used in the existing functions named isValidBrowser and
isBrowserOnline.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6d624320-01f8-40cd-9142-3372eccc1f4f

📥 Commits

Reviewing files that changed from the base of the PR and between da2b239 and c3e2e81.

📒 Files selected for processing (3)
  • .changeset/fix-gettoken-template-rn.md
  • packages/shared/src/__tests__/browser.spec.ts
  • packages/shared/src/browser.ts

Comment on lines +60 to +63
// Not in a browser environment (e.g. React Native, SSR).
// Return true so non-browser runtimes are not incorrectly
// treated as bots or automated browsers.
return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"1) Find call sites that use isValidBrowser() as a browser/window guard:"
rg -nP --type=ts --type=tsx -C3 'isValidBrowser\(\)\s*&&\s*(typeof\s+window\.|window\.)'echoecho"2) Inspect key downstream paths impacted by non-browser=true behavior:"
rg -nP -C3 '\bisBrowserOnline\(\)|\bisValidBrowserOnline\(\)' \
packages/clerk-js/src/core/resources/Session.ts \
packages/clerk-js/src/core/fapiClient.ts \
packages/shared/src/webauthn.ts
echoecho"Expected:"echo"- If any server-side/SSR path relies on isValidBrowser() to guard window access,"echo" current change is unsafe and should be narrowed to RN detection."

Repository: clerk/javascript

Length of output: 176


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Step 1: Check actual code in browser.ts around lines 60-63 and 76-79 ==="
cat -n packages/shared/src/browser.ts | sed -n '50,85p'echoecho"=== Step 2: Find all imports and usages of isValidBrowser ==="
rg -n 'isValidBrowser' packages/ --max-count=20
echoecho"=== Step 3: Find all imports and usages of isBrowserOnline ==="
rg -n 'isBrowserOnline' packages/ --max-count=20
echoecho"=== Step 4: Check webauthn.ts guard pattern ==="
cat -n packages/shared/src/webauthn.ts | head -20

Repository: clerk/javascript

Length of output: 7354


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Test expectations for isValidBrowser with no navigator ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '40,75p'echoecho"=== Check if webauthn.ts has any build-time guards or SSR exclusion ==="
grep -r "webauthn" packages/ --include="*.ts" --include="*.tsx" --include="*.json"| grep -E "(exclude|server|ssr|node)"||echo"No SSR-specific guards found"echoecho"=== Check for React Native detection utility in codebase ==="
rg -n "ReactNative|react.native|react-native" packages/shared/src/ | head -20
echoecho"=== Check inBrowser() implementation ==="
cat -n packages/shared/src/browser.ts | sed -n '1,50p'

Repository: clerk/javascript

Length of output: 3163


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Where is isWebAuthnSupported() actually called? ==="
rg -rn "isWebAuthnSupported|webauthn" packages/ --type=ts --type=tsx --include="*.ts" --include="*.tsx"| grep -v "node_modules"| grep -v "test"| head -30
echoecho"=== Check if webauthn module is conditionally loaded ==="
rg -rn "from.*webauthn|import.*webauthn" packages/ --include="*.ts" --include="*.tsx"| grep -v testechoecho"=== Full webauthn.ts to understand usage pattern ==="
cat packages/shared/src/webauthn.ts

Repository: clerk/javascript

Length of output: 574


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Where is isWebAuthnSupported() or webauthn functions called? ==="
rg -n "isWebAuthnSupported|isWebAuthnAutofillSupported" packages/ echoecho"=== Check all webauthn imports ==="
rg -n "from.*webauthn|import.*webauthn" packages/
echoecho"=== Full webauthn.ts content ==="
cat packages/shared/src/webauthn.ts

Repository: clerk/javascript

Length of output: 8269


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check if webauthn.ts is server-side accessible or client-only ==="
rg -n "webauthn" packages/shared/package.json packages/shared/tsconfig.json 2>/dev/null ||echo"Not in explicit exports"echoecho"=== Check how webauthn.ts is exported from `@clerk/shared` ==="
cat packages/shared/package.json | grep -A 20 '"exports"'echoecho"=== Look at complete test for isValidBrowser with no navigator ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '41,47p'echoecho"=== Does the test actually expect this behavior? Look at comment ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '40,50p'echoecho"=== Check if there's conditional execution or build-time webauthn exclusion ==="
grep -r "webauthn" packages/clerk-js/src --include="*.json" --include="*.config.*"2>/dev/null ||echo"No special build config for webauthn"echoecho"=== Key: check if window.PublicKeyCredential will actually throw in SSR ==="echo"Testing: in Node.js SSR, accessing window.PublicKeyCredential"
node -e "try { console.log(typeof window.PublicKeyCredential); } catch(e) { console.log('Error:', e.message); }"

Repository: clerk/javascript

Length of output: 2066


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Final verification: Can isWebAuthnSupported ever run in SSR? ==="echo"Check if SignIn/Session resources are server-side initialized:"
rg -n "new SignIn\|new Session\|__internal_isWebAuthnSupported" packages/clerk-js/src/core/clerk.ts | head -15
echoecho"=== Confirm: in SSR with new isValidBrowser() change, this will crash ==="echo"Simulating: inBrowser()=false, navigator=null, then evaluating typeof window.PublicKeyCredential"
cat > /tmp/test.js << 'EOF'// Simulate isValidBrowser() with new changefunction isValidBrowser_NEW() { // In SSR: inBrowser() is false, so navigator becomes null const navigator = false ? null : null; // inBrowser() returns false if (!navigator) { return true; // NEW BEHAVIOR: return true for non-browser } return false;}// Now the guard in isWebAuthnSupportedfunction isWebAuthnSupported() { return ( isValidBrowser_NEW() && typeof window.PublicKeyCredential === 'function' // <-- will crash );}try { console.log("Result:", isWebAuthnSupported());} catch (e) { console.log("CRASH:", e.message);}EOF
node /tmp/test.js

Repository: clerk/javascript

Length of output: 191


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check if clerk resources are instantiated in SSR initialization ==="
rg -n "initializeClerk|new SignIn|new Session" packages/clerk-js/src/core/clerk.ts | head -5
echoecho"=== Confirm window check in isValidBrowser matches inBrowser pattern ==="
cat -n packages/shared/src/browser.ts | sed -n '57,66p'

Repository: clerk/javascript

Length of output: 131


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Verify: is webauthn always guarded with typeof checks on import side? ==="
cat -n packages/clerk-js/src/core/resources/SignIn.ts | sed -n '70,80p'echoecho"=== Check full context of how isWebAuthnSupported is used in SignIn ==="
cat -n packages/clerk-js/src/core/resources/SignIn.ts | sed -n '514,525p'echoecho"=== Simplified: In SSR, would isWebAuthnSupported() be called? ==="echo"Check if SignIn methods are accessible in SSR or only browser-side"
grep -n "class SignIn" packages/clerk-js/src/core/resources/SignIn.ts | head -1
</script>

Repository: clerk/javascript

Length of output: 1639


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check: When are webauthn functions evaluated? At load time or at runtime? ==="
cat -n packages/shared/src/webauthn.ts
echoecho"=== Check: Are these webauthn functions called during SSR server init? ==="echo"They're exported from `@clerk/shared` and imported in clerk-js"echo"But are they called during non-interactive SSR, or only during client-side flows?"echoecho"=== The critical question: Does the guard actually protect? ==="echo"In webauthn.ts line 8:"echo" isValidBrowser() && typeof window.PublicKeyCredential === 'function'"echo""echo"With new isValidBrowser() = true in SSR:"echo" true && typeof window.PublicKeyCredential === 'function'"echo" ^ This evaluates to the right side"echo" ^ But we're in Node.js where window is undefined"echo" -> ReferenceError: window is not defined"

Repository: clerk/javascript

Length of output: 1880


isValidBrowser() and isBrowserOnline() returning true for all non-navigator cases breaks webauthn guard semantics

Lines 60 and 76 returning true for all non-browser runtimes (not just React Native) creates a safety issue. In packages/shared/src/webauthn.ts:8–10, the guard pattern isValidBrowser() && typeof window.PublicKeyCredential === 'function' relies on the first condition being false in non-browser environments. With this change, SSR/Node.js environments will pass the first condition (true), then evaluate typeof window.PublicKeyCredential, which throws ReferenceError: window is not defined since typeof doesn't protect property access.

The test comment correctly identifies the intent ("React Native"), but the implementation conflates React Native with generic non-browser runtimes. Narrow the true return to React Native detection only:

+function isReactNativeRuntime(): boolean {+ return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';+}+
export function isValidBrowser(): boolean {
const navigator = inBrowser() ? window?.navigator : null;
if (!navigator) {
- return true;+ return isReactNativeRuntime();
}
return !userAgentIsRobot(navigator?.userAgent) && !navigator?.webdriver;
}
export function isBrowserOnline(): boolean {
const navigator = inBrowser() ? window?.navigator : null;
if (!navigator) {
- return true;+ return isReactNativeRuntime();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/shared/src/browser.ts` around lines 60 - 63, The functions
isValidBrowser() and isBrowserOnline() should not return true for all
non-navigator runtimes; narrow the "true" case to only React Native. Update
isValidBrowser() and isBrowserOnline() to detect React Native explicitly (e.g.,
check navigator?.product === 'ReactNative' or another RN-specific signal) and
return true only in that case, otherwise return false for generic non-browser
runtimes so code like the guard in webauthn.ts ("isValidBrowser() && typeof
window.PublicKeyCredential === 'function'") does not attempt to access window in
SSR/Node environments; ensure the detection logic is used in the existing
functions named isValidBrowser and isBrowserOnline.

@chriscanin

Copy link
Copy Markdown
ContributorAuthor

This PR is being closed in favor of #8084.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@chriscanin
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(shared): correct isValidBrowser/isBrowserOnline for React Native by chriscanin · Pull Request #8086 · clerk/javascript · GitHub
Skip to content

fix(shared): correct isValidBrowser/isBrowserOnline for React Native - #8086

Closed
chriscanin wants to merge 1 commit into
mainfrom
chris/fix-gettoken-template-offline-rn
Closed

fix(shared): correct isValidBrowser/isBrowserOnline for React Native#8086
chriscanin wants to merge 1 commit into
mainfrom
chris/fix-gettoken-template-offline-rn

Conversation

@chriscanin

@chriscaninchriscanin commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • isValidBrowser() and isBrowserOnline() in @clerk/shared were returning false when window/navigator are undefined (React Native environments)
  • Core-3's token fetching checks these functions before making FAPI calls — returning false caused getToken({ template: '...' }) to silently fail on React Native while getToken() (which reads from local cache) continued to work
  • Fix: return true in non-browser environments since React Native has its own networking layer and should not be treated as a bot or offline

Reported by

Bledar via Discord — getToken({ template: 'one_hour_token' }) returns nothing on Expo after upgrading to latest Clerk

Test plan

  • Updated existing browser.spec.ts test to verify isValidBrowser() returns true when window is undefined
  • All 983 tests in @clerk/shared pass
  • Verify getToken({ template }) works on React Native after fix

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed token retrieval with templates in React Native and other non-browser environments. Non-browser runtimes are now correctly recognized as valid execution contexts, ensuring template-based token operations function properly across React Native, server-side rendering, and similar platforms. This resolves compatibility issues that previously prevented proper authentication token management in these environments.

…owser environments
In React Native, there is no `window` or `navigator`, so `isValidBrowser()` and
`isBrowserOnline()` were returning `false`. This caused core-3's token fetching
to skip FAPI calls for templated tokens (`getToken({ template })`) since it
believed the environment was invalid/offline.
Non-browser runtimes like React Native have their own networking layer and should
not be treated as bots or offline. Return `true` when browser APIs are absent.
@vercel

vercelBot commented Mar 16, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentMar 16, 2026 4:38pm

Request Review

@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c3e2e81

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

This PR includes changesets to release 21 packages
NameType
@clerk/sharedPatch
@clerk/agent-toolkitPatch
@clerk/astroPatch
@clerk/backendPatch
@clerk/chrome-extensionPatch
@clerk/clerk-jsPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/uiPatch
@clerk/vuePatch

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

@pkg-pr-new

Copy link
Copy Markdown

Open in StackBlitz

@clerk/agent-toolkit

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

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8086

@clerk/dev-cli

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

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8086

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8086

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8086

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: c3e2e81

@chriscaninchriscanin self-assigned this Mar 16, 2026
@coderabbitai

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This change modifies the behavior of browser detection functions in the shared package to support token retrieval in React Native environments. The isValidBrowser() and isBrowserOnline() functions in browser.ts are updated to return true when running in non-browser environments instead of false. Corresponding test expectations are updated to reflect this new behavior. A changeset file documents this as a patch release for "@clerk/shared".

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe pull request title clearly and specifically describes the main change: fixing isValidBrowser and isBrowserOnline behavior for React Native environments.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Tip

CodeRabbit can use Trivy to scan for security misconfigurations and secrets in Infrastructure as Code files.

Add a .trivyignore file to your project to customize which findings Trivy reports.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/browser.ts`:
- Around line 60-63: The functions isValidBrowser() and isBrowserOnline() should
not return true for all non-navigator runtimes; narrow the "true" case to only
React Native. Update isValidBrowser() and isBrowserOnline() to detect React
Native explicitly (e.g., check navigator?.product === 'ReactNative' or another
RN-specific signal) and return true only in that case, otherwise return false
for generic non-browser runtimes so code like the guard in webauthn.ts
("isValidBrowser() && typeof window.PublicKeyCredential === 'function'") does
not attempt to access window in SSR/Node environments; ensure the detection
logic is used in the existing functions named isValidBrowser and
isBrowserOnline.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6d624320-01f8-40cd-9142-3372eccc1f4f

📥 Commits

Reviewing files that changed from the base of the PR and between da2b239 and c3e2e81.

📒 Files selected for processing (3)
  • .changeset/fix-gettoken-template-rn.md
  • packages/shared/src/__tests__/browser.spec.ts
  • packages/shared/src/browser.ts

Comment on lines +60 to +63
// Not in a browser environment (e.g. React Native, SSR).
// Return true so non-browser runtimes are not incorrectly
// treated as bots or automated browsers.
return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"1) Find call sites that use isValidBrowser() as a browser/window guard:"
rg -nP --type=ts --type=tsx -C3 'isValidBrowser\(\)\s*&&\s*(typeof\s+window\.|window\.)'echoecho"2) Inspect key downstream paths impacted by non-browser=true behavior:"
rg -nP -C3 '\bisBrowserOnline\(\)|\bisValidBrowserOnline\(\)' \
packages/clerk-js/src/core/resources/Session.ts \
packages/clerk-js/src/core/fapiClient.ts \
packages/shared/src/webauthn.ts
echoecho"Expected:"echo"- If any server-side/SSR path relies on isValidBrowser() to guard window access,"echo" current change is unsafe and should be narrowed to RN detection."

Repository: clerk/javascript

Length of output: 176


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Step 1: Check actual code in browser.ts around lines 60-63 and 76-79 ==="
cat -n packages/shared/src/browser.ts | sed -n '50,85p'echoecho"=== Step 2: Find all imports and usages of isValidBrowser ==="
rg -n 'isValidBrowser' packages/ --max-count=20
echoecho"=== Step 3: Find all imports and usages of isBrowserOnline ==="
rg -n 'isBrowserOnline' packages/ --max-count=20
echoecho"=== Step 4: Check webauthn.ts guard pattern ==="
cat -n packages/shared/src/webauthn.ts | head -20

Repository: clerk/javascript

Length of output: 7354


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Test expectations for isValidBrowser with no navigator ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '40,75p'echoecho"=== Check if webauthn.ts has any build-time guards or SSR exclusion ==="
grep -r "webauthn" packages/ --include="*.ts" --include="*.tsx" --include="*.json"| grep -E "(exclude|server|ssr|node)"||echo"No SSR-specific guards found"echoecho"=== Check for React Native detection utility in codebase ==="
rg -n "ReactNative|react.native|react-native" packages/shared/src/ | head -20
echoecho"=== Check inBrowser() implementation ==="
cat -n packages/shared/src/browser.ts | sed -n '1,50p'

Repository: clerk/javascript

Length of output: 3163


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Where is isWebAuthnSupported() actually called? ==="
rg -rn "isWebAuthnSupported|webauthn" packages/ --type=ts --type=tsx --include="*.ts" --include="*.tsx"| grep -v "node_modules"| grep -v "test"| head -30
echoecho"=== Check if webauthn module is conditionally loaded ==="
rg -rn "from.*webauthn|import.*webauthn" packages/ --include="*.ts" --include="*.tsx"| grep -v testechoecho"=== Full webauthn.ts to understand usage pattern ==="
cat packages/shared/src/webauthn.ts

Repository: clerk/javascript

Length of output: 574


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Where is isWebAuthnSupported() or webauthn functions called? ==="
rg -n "isWebAuthnSupported|isWebAuthnAutofillSupported" packages/ echoecho"=== Check all webauthn imports ==="
rg -n "from.*webauthn|import.*webauthn" packages/
echoecho"=== Full webauthn.ts content ==="
cat packages/shared/src/webauthn.ts

Repository: clerk/javascript

Length of output: 8269


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check if webauthn.ts is server-side accessible or client-only ==="
rg -n "webauthn" packages/shared/package.json packages/shared/tsconfig.json 2>/dev/null ||echo"Not in explicit exports"echoecho"=== Check how webauthn.ts is exported from `@clerk/shared` ==="
cat packages/shared/package.json | grep -A 20 '"exports"'echoecho"=== Look at complete test for isValidBrowser with no navigator ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '41,47p'echoecho"=== Does the test actually expect this behavior? Look at comment ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '40,50p'echoecho"=== Check if there's conditional execution or build-time webauthn exclusion ==="
grep -r "webauthn" packages/clerk-js/src --include="*.json" --include="*.config.*"2>/dev/null ||echo"No special build config for webauthn"echoecho"=== Key: check if window.PublicKeyCredential will actually throw in SSR ==="echo"Testing: in Node.js SSR, accessing window.PublicKeyCredential"
node -e "try { console.log(typeof window.PublicKeyCredential); } catch(e) { console.log('Error:', e.message); }"

Repository: clerk/javascript

Length of output: 2066


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Final verification: Can isWebAuthnSupported ever run in SSR? ==="echo"Check if SignIn/Session resources are server-side initialized:"
rg -n "new SignIn\|new Session\|__internal_isWebAuthnSupported" packages/clerk-js/src/core/clerk.ts | head -15
echoecho"=== Confirm: in SSR with new isValidBrowser() change, this will crash ==="echo"Simulating: inBrowser()=false, navigator=null, then evaluating typeof window.PublicKeyCredential"
cat > /tmp/test.js << 'EOF'// Simulate isValidBrowser() with new changefunction isValidBrowser_NEW() { // In SSR: inBrowser() is false, so navigator becomes null const navigator = false ? null : null; // inBrowser() returns false if (!navigator) { return true; // NEW BEHAVIOR: return true for non-browser } return false;}// Now the guard in isWebAuthnSupportedfunction isWebAuthnSupported() { return ( isValidBrowser_NEW() && typeof window.PublicKeyCredential === 'function' // <-- will crash );}try { console.log("Result:", isWebAuthnSupported());} catch (e) { console.log("CRASH:", e.message);}EOF
node /tmp/test.js

Repository: clerk/javascript

Length of output: 191


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check if clerk resources are instantiated in SSR initialization ==="
rg -n "initializeClerk|new SignIn|new Session" packages/clerk-js/src/core/clerk.ts | head -5
echoecho"=== Confirm window check in isValidBrowser matches inBrowser pattern ==="
cat -n packages/shared/src/browser.ts | sed -n '57,66p'

Repository: clerk/javascript

Length of output: 131


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Verify: is webauthn always guarded with typeof checks on import side? ==="
cat -n packages/clerk-js/src/core/resources/SignIn.ts | sed -n '70,80p'echoecho"=== Check full context of how isWebAuthnSupported is used in SignIn ==="
cat -n packages/clerk-js/src/core/resources/SignIn.ts | sed -n '514,525p'echoecho"=== Simplified: In SSR, would isWebAuthnSupported() be called? ==="echo"Check if SignIn methods are accessible in SSR or only browser-side"
grep -n "class SignIn" packages/clerk-js/src/core/resources/SignIn.ts | head -1
</script>

Repository: clerk/javascript

Length of output: 1639


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check: When are webauthn functions evaluated? At load time or at runtime? ==="
cat -n packages/shared/src/webauthn.ts
echoecho"=== Check: Are these webauthn functions called during SSR server init? ==="echo"They're exported from `@clerk/shared` and imported in clerk-js"echo"But are they called during non-interactive SSR, or only during client-side flows?"echoecho"=== The critical question: Does the guard actually protect? ==="echo"In webauthn.ts line 8:"echo" isValidBrowser() && typeof window.PublicKeyCredential === 'function'"echo""echo"With new isValidBrowser() = true in SSR:"echo" true && typeof window.PublicKeyCredential === 'function'"echo" ^ This evaluates to the right side"echo" ^ But we're in Node.js where window is undefined"echo" -> ReferenceError: window is not defined"

Repository: clerk/javascript

Length of output: 1880


isValidBrowser() and isBrowserOnline() returning true for all non-navigator cases breaks webauthn guard semantics

Lines 60 and 76 returning true for all non-browser runtimes (not just React Native) creates a safety issue. In packages/shared/src/webauthn.ts:8–10, the guard pattern isValidBrowser() && typeof window.PublicKeyCredential === 'function' relies on the first condition being false in non-browser environments. With this change, SSR/Node.js environments will pass the first condition (true), then evaluate typeof window.PublicKeyCredential, which throws ReferenceError: window is not defined since typeof doesn't protect property access.

The test comment correctly identifies the intent ("React Native"), but the implementation conflates React Native with generic non-browser runtimes. Narrow the true return to React Native detection only:

+function isReactNativeRuntime(): boolean {+ return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';+}+
export function isValidBrowser(): boolean {
const navigator = inBrowser() ? window?.navigator : null;
if (!navigator) {
- return true;+ return isReactNativeRuntime();
}
return !userAgentIsRobot(navigator?.userAgent) && !navigator?.webdriver;
}
export function isBrowserOnline(): boolean {
const navigator = inBrowser() ? window?.navigator : null;
if (!navigator) {
- return true;+ return isReactNativeRuntime();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/shared/src/browser.ts` around lines 60 - 63, The functions
isValidBrowser() and isBrowserOnline() should not return true for all
non-navigator runtimes; narrow the "true" case to only React Native. Update
isValidBrowser() and isBrowserOnline() to detect React Native explicitly (e.g.,
check navigator?.product === 'ReactNative' or another RN-specific signal) and
return true only in that case, otherwise return false for generic non-browser
runtimes so code like the guard in webauthn.ts ("isValidBrowser() && typeof
window.PublicKeyCredential === 'function'") does not attempt to access window in
SSR/Node environments; ensure the detection logic is used in the existing
functions named isValidBrowser and isBrowserOnline.

@chriscanin

Copy link
Copy Markdown
ContributorAuthor

This PR is being closed in favor of #8084.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@chriscanin
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(shared): correct isValidBrowser/isBrowserOnline for React Native by chriscanin · Pull Request #8086 · clerk/javascript · GitHub
Skip to content

fix(shared): correct isValidBrowser/isBrowserOnline for React Native - #8086

Closed
chriscanin wants to merge 1 commit into
mainfrom
chris/fix-gettoken-template-offline-rn
Closed

fix(shared): correct isValidBrowser/isBrowserOnline for React Native#8086
chriscanin wants to merge 1 commit into
mainfrom
chris/fix-gettoken-template-offline-rn

Conversation

@chriscanin

@chriscaninchriscanin commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • isValidBrowser() and isBrowserOnline() in @clerk/shared were returning false when window/navigator are undefined (React Native environments)
  • Core-3's token fetching checks these functions before making FAPI calls — returning false caused getToken({ template: '...' }) to silently fail on React Native while getToken() (which reads from local cache) continued to work
  • Fix: return true in non-browser environments since React Native has its own networking layer and should not be treated as a bot or offline

Reported by

Bledar via Discord — getToken({ template: 'one_hour_token' }) returns nothing on Expo after upgrading to latest Clerk

Test plan

  • Updated existing browser.spec.ts test to verify isValidBrowser() returns true when window is undefined
  • All 983 tests in @clerk/shared pass
  • Verify getToken({ template }) works on React Native after fix

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed token retrieval with templates in React Native and other non-browser environments. Non-browser runtimes are now correctly recognized as valid execution contexts, ensuring template-based token operations function properly across React Native, server-side rendering, and similar platforms. This resolves compatibility issues that previously prevented proper authentication token management in these environments.

…owser environments
In React Native, there is no `window` or `navigator`, so `isValidBrowser()` and
`isBrowserOnline()` were returning `false`. This caused core-3's token fetching
to skip FAPI calls for templated tokens (`getToken({ template })`) since it
believed the environment was invalid/offline.
Non-browser runtimes like React Native have their own networking layer and should
not be treated as bots or offline. Return `true` when browser APIs are absent.
@vercel

vercelBot commented Mar 16, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentMar 16, 2026 4:38pm

Request Review

@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c3e2e81

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

This PR includes changesets to release 21 packages
NameType
@clerk/sharedPatch
@clerk/agent-toolkitPatch
@clerk/astroPatch
@clerk/backendPatch
@clerk/chrome-extensionPatch
@clerk/clerk-jsPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/uiPatch
@clerk/vuePatch

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

@pkg-pr-new

Copy link
Copy Markdown

Open in StackBlitz

@clerk/agent-toolkit

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

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8086

@clerk/dev-cli

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

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8086

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8086

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8086

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: c3e2e81

@chriscaninchriscanin self-assigned this Mar 16, 2026
@coderabbitai

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This change modifies the behavior of browser detection functions in the shared package to support token retrieval in React Native environments. The isValidBrowser() and isBrowserOnline() functions in browser.ts are updated to return true when running in non-browser environments instead of false. Corresponding test expectations are updated to reflect this new behavior. A changeset file documents this as a patch release for "@clerk/shared".

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe pull request title clearly and specifically describes the main change: fixing isValidBrowser and isBrowserOnline behavior for React Native environments.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Tip

CodeRabbit can use Trivy to scan for security misconfigurations and secrets in Infrastructure as Code files.

Add a .trivyignore file to your project to customize which findings Trivy reports.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/browser.ts`:
- Around line 60-63: The functions isValidBrowser() and isBrowserOnline() should
not return true for all non-navigator runtimes; narrow the "true" case to only
React Native. Update isValidBrowser() and isBrowserOnline() to detect React
Native explicitly (e.g., check navigator?.product === 'ReactNative' or another
RN-specific signal) and return true only in that case, otherwise return false
for generic non-browser runtimes so code like the guard in webauthn.ts
("isValidBrowser() && typeof window.PublicKeyCredential === 'function'") does
not attempt to access window in SSR/Node environments; ensure the detection
logic is used in the existing functions named isValidBrowser and
isBrowserOnline.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6d624320-01f8-40cd-9142-3372eccc1f4f

📥 Commits

Reviewing files that changed from the base of the PR and between da2b239 and c3e2e81.

📒 Files selected for processing (3)
  • .changeset/fix-gettoken-template-rn.md
  • packages/shared/src/__tests__/browser.spec.ts
  • packages/shared/src/browser.ts

Comment on lines +60 to +63
// Not in a browser environment (e.g. React Native, SSR).
// Return true so non-browser runtimes are not incorrectly
// treated as bots or automated browsers.
return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"1) Find call sites that use isValidBrowser() as a browser/window guard:"
rg -nP --type=ts --type=tsx -C3 'isValidBrowser\(\)\s*&&\s*(typeof\s+window\.|window\.)'echoecho"2) Inspect key downstream paths impacted by non-browser=true behavior:"
rg -nP -C3 '\bisBrowserOnline\(\)|\bisValidBrowserOnline\(\)' \
packages/clerk-js/src/core/resources/Session.ts \
packages/clerk-js/src/core/fapiClient.ts \
packages/shared/src/webauthn.ts
echoecho"Expected:"echo"- If any server-side/SSR path relies on isValidBrowser() to guard window access,"echo" current change is unsafe and should be narrowed to RN detection."

Repository: clerk/javascript

Length of output: 176


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Step 1: Check actual code in browser.ts around lines 60-63 and 76-79 ==="
cat -n packages/shared/src/browser.ts | sed -n '50,85p'echoecho"=== Step 2: Find all imports and usages of isValidBrowser ==="
rg -n 'isValidBrowser' packages/ --max-count=20
echoecho"=== Step 3: Find all imports and usages of isBrowserOnline ==="
rg -n 'isBrowserOnline' packages/ --max-count=20
echoecho"=== Step 4: Check webauthn.ts guard pattern ==="
cat -n packages/shared/src/webauthn.ts | head -20

Repository: clerk/javascript

Length of output: 7354


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Test expectations for isValidBrowser with no navigator ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '40,75p'echoecho"=== Check if webauthn.ts has any build-time guards or SSR exclusion ==="
grep -r "webauthn" packages/ --include="*.ts" --include="*.tsx" --include="*.json"| grep -E "(exclude|server|ssr|node)"||echo"No SSR-specific guards found"echoecho"=== Check for React Native detection utility in codebase ==="
rg -n "ReactNative|react.native|react-native" packages/shared/src/ | head -20
echoecho"=== Check inBrowser() implementation ==="
cat -n packages/shared/src/browser.ts | sed -n '1,50p'

Repository: clerk/javascript

Length of output: 3163


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Where is isWebAuthnSupported() actually called? ==="
rg -rn "isWebAuthnSupported|webauthn" packages/ --type=ts --type=tsx --include="*.ts" --include="*.tsx"| grep -v "node_modules"| grep -v "test"| head -30
echoecho"=== Check if webauthn module is conditionally loaded ==="
rg -rn "from.*webauthn|import.*webauthn" packages/ --include="*.ts" --include="*.tsx"| grep -v testechoecho"=== Full webauthn.ts to understand usage pattern ==="
cat packages/shared/src/webauthn.ts

Repository: clerk/javascript

Length of output: 574


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Where is isWebAuthnSupported() or webauthn functions called? ==="
rg -n "isWebAuthnSupported|isWebAuthnAutofillSupported" packages/ echoecho"=== Check all webauthn imports ==="
rg -n "from.*webauthn|import.*webauthn" packages/
echoecho"=== Full webauthn.ts content ==="
cat packages/shared/src/webauthn.ts

Repository: clerk/javascript

Length of output: 8269


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check if webauthn.ts is server-side accessible or client-only ==="
rg -n "webauthn" packages/shared/package.json packages/shared/tsconfig.json 2>/dev/null ||echo"Not in explicit exports"echoecho"=== Check how webauthn.ts is exported from `@clerk/shared` ==="
cat packages/shared/package.json | grep -A 20 '"exports"'echoecho"=== Look at complete test for isValidBrowser with no navigator ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '41,47p'echoecho"=== Does the test actually expect this behavior? Look at comment ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '40,50p'echoecho"=== Check if there's conditional execution or build-time webauthn exclusion ==="
grep -r "webauthn" packages/clerk-js/src --include="*.json" --include="*.config.*"2>/dev/null ||echo"No special build config for webauthn"echoecho"=== Key: check if window.PublicKeyCredential will actually throw in SSR ==="echo"Testing: in Node.js SSR, accessing window.PublicKeyCredential"
node -e "try { console.log(typeof window.PublicKeyCredential); } catch(e) { console.log('Error:', e.message); }"

Repository: clerk/javascript

Length of output: 2066


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Final verification: Can isWebAuthnSupported ever run in SSR? ==="echo"Check if SignIn/Session resources are server-side initialized:"
rg -n "new SignIn\|new Session\|__internal_isWebAuthnSupported" packages/clerk-js/src/core/clerk.ts | head -15
echoecho"=== Confirm: in SSR with new isValidBrowser() change, this will crash ==="echo"Simulating: inBrowser()=false, navigator=null, then evaluating typeof window.PublicKeyCredential"
cat > /tmp/test.js << 'EOF'// Simulate isValidBrowser() with new changefunction isValidBrowser_NEW() { // In SSR: inBrowser() is false, so navigator becomes null const navigator = false ? null : null; // inBrowser() returns false if (!navigator) { return true; // NEW BEHAVIOR: return true for non-browser } return false;}// Now the guard in isWebAuthnSupportedfunction isWebAuthnSupported() { return ( isValidBrowser_NEW() && typeof window.PublicKeyCredential === 'function' // <-- will crash );}try { console.log("Result:", isWebAuthnSupported());} catch (e) { console.log("CRASH:", e.message);}EOF
node /tmp/test.js

Repository: clerk/javascript

Length of output: 191


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check if clerk resources are instantiated in SSR initialization ==="
rg -n "initializeClerk|new SignIn|new Session" packages/clerk-js/src/core/clerk.ts | head -5
echoecho"=== Confirm window check in isValidBrowser matches inBrowser pattern ==="
cat -n packages/shared/src/browser.ts | sed -n '57,66p'

Repository: clerk/javascript

Length of output: 131


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Verify: is webauthn always guarded with typeof checks on import side? ==="
cat -n packages/clerk-js/src/core/resources/SignIn.ts | sed -n '70,80p'echoecho"=== Check full context of how isWebAuthnSupported is used in SignIn ==="
cat -n packages/clerk-js/src/core/resources/SignIn.ts | sed -n '514,525p'echoecho"=== Simplified: In SSR, would isWebAuthnSupported() be called? ==="echo"Check if SignIn methods are accessible in SSR or only browser-side"
grep -n "class SignIn" packages/clerk-js/src/core/resources/SignIn.ts | head -1
</script>

Repository: clerk/javascript

Length of output: 1639


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check: When are webauthn functions evaluated? At load time or at runtime? ==="
cat -n packages/shared/src/webauthn.ts
echoecho"=== Check: Are these webauthn functions called during SSR server init? ==="echo"They're exported from `@clerk/shared` and imported in clerk-js"echo"But are they called during non-interactive SSR, or only during client-side flows?"echoecho"=== The critical question: Does the guard actually protect? ==="echo"In webauthn.ts line 8:"echo" isValidBrowser() && typeof window.PublicKeyCredential === 'function'"echo""echo"With new isValidBrowser() = true in SSR:"echo" true && typeof window.PublicKeyCredential === 'function'"echo" ^ This evaluates to the right side"echo" ^ But we're in Node.js where window is undefined"echo" -> ReferenceError: window is not defined"

Repository: clerk/javascript

Length of output: 1880


isValidBrowser() and isBrowserOnline() returning true for all non-navigator cases breaks webauthn guard semantics

Lines 60 and 76 returning true for all non-browser runtimes (not just React Native) creates a safety issue. In packages/shared/src/webauthn.ts:8–10, the guard pattern isValidBrowser() && typeof window.PublicKeyCredential === 'function' relies on the first condition being false in non-browser environments. With this change, SSR/Node.js environments will pass the first condition (true), then evaluate typeof window.PublicKeyCredential, which throws ReferenceError: window is not defined since typeof doesn't protect property access.

The test comment correctly identifies the intent ("React Native"), but the implementation conflates React Native with generic non-browser runtimes. Narrow the true return to React Native detection only:

+function isReactNativeRuntime(): boolean {+ return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';+}+
export function isValidBrowser(): boolean {
const navigator = inBrowser() ? window?.navigator : null;
if (!navigator) {
- return true;+ return isReactNativeRuntime();
}
return !userAgentIsRobot(navigator?.userAgent) && !navigator?.webdriver;
}
export function isBrowserOnline(): boolean {
const navigator = inBrowser() ? window?.navigator : null;
if (!navigator) {
- return true;+ return isReactNativeRuntime();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/shared/src/browser.ts` around lines 60 - 63, The functions
isValidBrowser() and isBrowserOnline() should not return true for all
non-navigator runtimes; narrow the "true" case to only React Native. Update
isValidBrowser() and isBrowserOnline() to detect React Native explicitly (e.g.,
check navigator?.product === 'ReactNative' or another RN-specific signal) and
return true only in that case, otherwise return false for generic non-browser
runtimes so code like the guard in webauthn.ts ("isValidBrowser() && typeof
window.PublicKeyCredential === 'function'") does not attempt to access window in
SSR/Node environments; ensure the detection logic is used in the existing
functions named isValidBrowser and isBrowserOnline.

@chriscanin

Copy link
Copy Markdown
ContributorAuthor

This PR is being closed in favor of #8084.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@chriscanin
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' fix(shared): correct isValidBrowser/isBrowserOnline for React Native by chriscanin · Pull Request #8086 · clerk/javascript · GitHub
Skip to content

fix(shared): correct isValidBrowser/isBrowserOnline for React Native - #8086

Closed
chriscanin wants to merge 1 commit into
mainfrom
chris/fix-gettoken-template-offline-rn
Closed

fix(shared): correct isValidBrowser/isBrowserOnline for React Native#8086
chriscanin wants to merge 1 commit into
mainfrom
chris/fix-gettoken-template-offline-rn

Conversation

@chriscanin

@chriscaninchriscanin commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • isValidBrowser() and isBrowserOnline() in @clerk/shared were returning false when window/navigator are undefined (React Native environments)
  • Core-3's token fetching checks these functions before making FAPI calls — returning false caused getToken({ template: '...' }) to silently fail on React Native while getToken() (which reads from local cache) continued to work
  • Fix: return true in non-browser environments since React Native has its own networking layer and should not be treated as a bot or offline

Reported by

Bledar via Discord — getToken({ template: 'one_hour_token' }) returns nothing on Expo after upgrading to latest Clerk

Test plan

  • Updated existing browser.spec.ts test to verify isValidBrowser() returns true when window is undefined
  • All 983 tests in @clerk/shared pass
  • Verify getToken({ template }) works on React Native after fix

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed token retrieval with templates in React Native and other non-browser environments. Non-browser runtimes are now correctly recognized as valid execution contexts, ensuring template-based token operations function properly across React Native, server-side rendering, and similar platforms. This resolves compatibility issues that previously prevented proper authentication token management in these environments.

…owser environments
In React Native, there is no `window` or `navigator`, so `isValidBrowser()` and
`isBrowserOnline()` were returning `false`. This caused core-3's token fetching
to skip FAPI calls for templated tokens (`getToken({ template })`) since it
believed the environment was invalid/offline.
Non-browser runtimes like React Native have their own networking layer and should
not be treated as bots or offline. Return `true` when browser APIs are absent.
@vercel

vercelBot commented Mar 16, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentMar 16, 2026 4:38pm

Request Review

@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c3e2e81

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

This PR includes changesets to release 21 packages
NameType
@clerk/sharedPatch
@clerk/agent-toolkitPatch
@clerk/astroPatch
@clerk/backendPatch
@clerk/chrome-extensionPatch
@clerk/clerk-jsPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/uiPatch
@clerk/vuePatch

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

@pkg-pr-new

Copy link
Copy Markdown

Open in StackBlitz

@clerk/agent-toolkit

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

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8086

@clerk/dev-cli

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

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8086

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8086

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8086

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: c3e2e81

@chriscaninchriscanin self-assigned this Mar 16, 2026
@coderabbitai

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This change modifies the behavior of browser detection functions in the shared package to support token retrieval in React Native environments. The isValidBrowser() and isBrowserOnline() functions in browser.ts are updated to return true when running in non-browser environments instead of false. Corresponding test expectations are updated to reflect this new behavior. A changeset file documents this as a patch release for "@clerk/shared".

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe pull request title clearly and specifically describes the main change: fixing isValidBrowser and isBrowserOnline behavior for React Native environments.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Tip

CodeRabbit can use Trivy to scan for security misconfigurations and secrets in Infrastructure as Code files.

Add a .trivyignore file to your project to customize which findings Trivy reports.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/browser.ts`:
- Around line 60-63: The functions isValidBrowser() and isBrowserOnline() should
not return true for all non-navigator runtimes; narrow the "true" case to only
React Native. Update isValidBrowser() and isBrowserOnline() to detect React
Native explicitly (e.g., check navigator?.product === 'ReactNative' or another
RN-specific signal) and return true only in that case, otherwise return false
for generic non-browser runtimes so code like the guard in webauthn.ts
("isValidBrowser() && typeof window.PublicKeyCredential === 'function'") does
not attempt to access window in SSR/Node environments; ensure the detection
logic is used in the existing functions named isValidBrowser and
isBrowserOnline.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6d624320-01f8-40cd-9142-3372eccc1f4f

📥 Commits

Reviewing files that changed from the base of the PR and between da2b239 and c3e2e81.

📒 Files selected for processing (3)
  • .changeset/fix-gettoken-template-rn.md
  • packages/shared/src/__tests__/browser.spec.ts
  • packages/shared/src/browser.ts

Comment on lines +60 to +63
// Not in a browser environment (e.g. React Native, SSR).
// Return true so non-browser runtimes are not incorrectly
// treated as bots or automated browsers.
return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"1) Find call sites that use isValidBrowser() as a browser/window guard:"
rg -nP --type=ts --type=tsx -C3 'isValidBrowser\(\)\s*&&\s*(typeof\s+window\.|window\.)'echoecho"2) Inspect key downstream paths impacted by non-browser=true behavior:"
rg -nP -C3 '\bisBrowserOnline\(\)|\bisValidBrowserOnline\(\)' \
packages/clerk-js/src/core/resources/Session.ts \
packages/clerk-js/src/core/fapiClient.ts \
packages/shared/src/webauthn.ts
echoecho"Expected:"echo"- If any server-side/SSR path relies on isValidBrowser() to guard window access,"echo" current change is unsafe and should be narrowed to RN detection."

Repository: clerk/javascript

Length of output: 176


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Step 1: Check actual code in browser.ts around lines 60-63 and 76-79 ==="
cat -n packages/shared/src/browser.ts | sed -n '50,85p'echoecho"=== Step 2: Find all imports and usages of isValidBrowser ==="
rg -n 'isValidBrowser' packages/ --max-count=20
echoecho"=== Step 3: Find all imports and usages of isBrowserOnline ==="
rg -n 'isBrowserOnline' packages/ --max-count=20
echoecho"=== Step 4: Check webauthn.ts guard pattern ==="
cat -n packages/shared/src/webauthn.ts | head -20

Repository: clerk/javascript

Length of output: 7354


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Test expectations for isValidBrowser with no navigator ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '40,75p'echoecho"=== Check if webauthn.ts has any build-time guards or SSR exclusion ==="
grep -r "webauthn" packages/ --include="*.ts" --include="*.tsx" --include="*.json"| grep -E "(exclude|server|ssr|node)"||echo"No SSR-specific guards found"echoecho"=== Check for React Native detection utility in codebase ==="
rg -n "ReactNative|react.native|react-native" packages/shared/src/ | head -20
echoecho"=== Check inBrowser() implementation ==="
cat -n packages/shared/src/browser.ts | sed -n '1,50p'

Repository: clerk/javascript

Length of output: 3163


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Where is isWebAuthnSupported() actually called? ==="
rg -rn "isWebAuthnSupported|webauthn" packages/ --type=ts --type=tsx --include="*.ts" --include="*.tsx"| grep -v "node_modules"| grep -v "test"| head -30
echoecho"=== Check if webauthn module is conditionally loaded ==="
rg -rn "from.*webauthn|import.*webauthn" packages/ --include="*.ts" --include="*.tsx"| grep -v testechoecho"=== Full webauthn.ts to understand usage pattern ==="
cat packages/shared/src/webauthn.ts

Repository: clerk/javascript

Length of output: 574


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Where is isWebAuthnSupported() or webauthn functions called? ==="
rg -n "isWebAuthnSupported|isWebAuthnAutofillSupported" packages/ echoecho"=== Check all webauthn imports ==="
rg -n "from.*webauthn|import.*webauthn" packages/
echoecho"=== Full webauthn.ts content ==="
cat packages/shared/src/webauthn.ts

Repository: clerk/javascript

Length of output: 8269


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check if webauthn.ts is server-side accessible or client-only ==="
rg -n "webauthn" packages/shared/package.json packages/shared/tsconfig.json 2>/dev/null ||echo"Not in explicit exports"echoecho"=== Check how webauthn.ts is exported from `@clerk/shared` ==="
cat packages/shared/package.json | grep -A 20 '"exports"'echoecho"=== Look at complete test for isValidBrowser with no navigator ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '41,47p'echoecho"=== Does the test actually expect this behavior? Look at comment ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '40,50p'echoecho"=== Check if there's conditional execution or build-time webauthn exclusion ==="
grep -r "webauthn" packages/clerk-js/src --include="*.json" --include="*.config.*"2>/dev/null ||echo"No special build config for webauthn"echoecho"=== Key: check if window.PublicKeyCredential will actually throw in SSR ==="echo"Testing: in Node.js SSR, accessing window.PublicKeyCredential"
node -e "try { console.log(typeof window.PublicKeyCredential); } catch(e) { console.log('Error:', e.message); }"

Repository: clerk/javascript

Length of output: 2066


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Final verification: Can isWebAuthnSupported ever run in SSR? ==="echo"Check if SignIn/Session resources are server-side initialized:"
rg -n "new SignIn\|new Session\|__internal_isWebAuthnSupported" packages/clerk-js/src/core/clerk.ts | head -15
echoecho"=== Confirm: in SSR with new isValidBrowser() change, this will crash ==="echo"Simulating: inBrowser()=false, navigator=null, then evaluating typeof window.PublicKeyCredential"
cat > /tmp/test.js << 'EOF'// Simulate isValidBrowser() with new changefunction isValidBrowser_NEW() { // In SSR: inBrowser() is false, so navigator becomes null const navigator = false ? null : null; // inBrowser() returns false if (!navigator) { return true; // NEW BEHAVIOR: return true for non-browser } return false;}// Now the guard in isWebAuthnSupportedfunction isWebAuthnSupported() { return ( isValidBrowser_NEW() && typeof window.PublicKeyCredential === 'function' // <-- will crash );}try { console.log("Result:", isWebAuthnSupported());} catch (e) { console.log("CRASH:", e.message);}EOF
node /tmp/test.js

Repository: clerk/javascript

Length of output: 191


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check if clerk resources are instantiated in SSR initialization ==="
rg -n "initializeClerk|new SignIn|new Session" packages/clerk-js/src/core/clerk.ts | head -5
echoecho"=== Confirm window check in isValidBrowser matches inBrowser pattern ==="
cat -n packages/shared/src/browser.ts | sed -n '57,66p'

Repository: clerk/javascript

Length of output: 131


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Verify: is webauthn always guarded with typeof checks on import side? ==="
cat -n packages/clerk-js/src/core/resources/SignIn.ts | sed -n '70,80p'echoecho"=== Check full context of how isWebAuthnSupported is used in SignIn ==="
cat -n packages/clerk-js/src/core/resources/SignIn.ts | sed -n '514,525p'echoecho"=== Simplified: In SSR, would isWebAuthnSupported() be called? ==="echo"Check if SignIn methods are accessible in SSR or only browser-side"
grep -n "class SignIn" packages/clerk-js/src/core/resources/SignIn.ts | head -1
</script>

Repository: clerk/javascript

Length of output: 1639


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check: When are webauthn functions evaluated? At load time or at runtime? ==="
cat -n packages/shared/src/webauthn.ts
echoecho"=== Check: Are these webauthn functions called during SSR server init? ==="echo"They're exported from `@clerk/shared` and imported in clerk-js"echo"But are they called during non-interactive SSR, or only during client-side flows?"echoecho"=== The critical question: Does the guard actually protect? ==="echo"In webauthn.ts line 8:"echo" isValidBrowser() && typeof window.PublicKeyCredential === 'function'"echo""echo"With new isValidBrowser() = true in SSR:"echo" true && typeof window.PublicKeyCredential === 'function'"echo" ^ This evaluates to the right side"echo" ^ But we're in Node.js where window is undefined"echo" -> ReferenceError: window is not defined"

Repository: clerk/javascript

Length of output: 1880


isValidBrowser() and isBrowserOnline() returning true for all non-navigator cases breaks webauthn guard semantics

Lines 60 and 76 returning true for all non-browser runtimes (not just React Native) creates a safety issue. In packages/shared/src/webauthn.ts:8–10, the guard pattern isValidBrowser() && typeof window.PublicKeyCredential === 'function' relies on the first condition being false in non-browser environments. With this change, SSR/Node.js environments will pass the first condition (true), then evaluate typeof window.PublicKeyCredential, which throws ReferenceError: window is not defined since typeof doesn't protect property access.

The test comment correctly identifies the intent ("React Native"), but the implementation conflates React Native with generic non-browser runtimes. Narrow the true return to React Native detection only:

+function isReactNativeRuntime(): boolean {+ return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';+}+
export function isValidBrowser(): boolean {
const navigator = inBrowser() ? window?.navigator : null;
if (!navigator) {
- return true;+ return isReactNativeRuntime();
}
return !userAgentIsRobot(navigator?.userAgent) && !navigator?.webdriver;
}
export function isBrowserOnline(): boolean {
const navigator = inBrowser() ? window?.navigator : null;
if (!navigator) {
- return true;+ return isReactNativeRuntime();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/shared/src/browser.ts` around lines 60 - 63, The functions
isValidBrowser() and isBrowserOnline() should not return true for all
non-navigator runtimes; narrow the "true" case to only React Native. Update
isValidBrowser() and isBrowserOnline() to detect React Native explicitly (e.g.,
check navigator?.product === 'ReactNative' or another RN-specific signal) and
return true only in that case, otherwise return false for generic non-browser
runtimes so code like the guard in webauthn.ts ("isValidBrowser() && typeof
window.PublicKeyCredential === 'function'") does not attempt to access window in
SSR/Node environments; ensure the detection logic is used in the existing
functions named isValidBrowser and isBrowserOnline.

@chriscanin

Copy link
Copy Markdown
ContributorAuthor

This PR is being closed in favor of #8084.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@chriscanin
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(shared): correct isValidBrowser/isBrowserOnline for React Native by chriscanin · Pull Request #8086 · clerk/javascript · GitHub
Skip to content

fix(shared): correct isValidBrowser/isBrowserOnline for React Native - #8086

Closed
chriscanin wants to merge 1 commit into
mainfrom
chris/fix-gettoken-template-offline-rn
Closed

fix(shared): correct isValidBrowser/isBrowserOnline for React Native#8086
chriscanin wants to merge 1 commit into
mainfrom
chris/fix-gettoken-template-offline-rn

Conversation

@chriscanin

@chriscaninchriscanin commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • isValidBrowser() and isBrowserOnline() in @clerk/shared were returning false when window/navigator are undefined (React Native environments)
  • Core-3's token fetching checks these functions before making FAPI calls — returning false caused getToken({ template: '...' }) to silently fail on React Native while getToken() (which reads from local cache) continued to work
  • Fix: return true in non-browser environments since React Native has its own networking layer and should not be treated as a bot or offline

Reported by

Bledar via Discord — getToken({ template: 'one_hour_token' }) returns nothing on Expo after upgrading to latest Clerk

Test plan

  • Updated existing browser.spec.ts test to verify isValidBrowser() returns true when window is undefined
  • All 983 tests in @clerk/shared pass
  • Verify getToken({ template }) works on React Native after fix

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed token retrieval with templates in React Native and other non-browser environments. Non-browser runtimes are now correctly recognized as valid execution contexts, ensuring template-based token operations function properly across React Native, server-side rendering, and similar platforms. This resolves compatibility issues that previously prevented proper authentication token management in these environments.

…owser environments
In React Native, there is no `window` or `navigator`, so `isValidBrowser()` and
`isBrowserOnline()` were returning `false`. This caused core-3's token fetching
to skip FAPI calls for templated tokens (`getToken({ template })`) since it
believed the environment was invalid/offline.
Non-browser runtimes like React Native have their own networking layer and should
not be treated as bots or offline. Return `true` when browser APIs are absent.
@vercel

vercelBot commented Mar 16, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentMar 16, 2026 4:38pm

Request Review

@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c3e2e81

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

This PR includes changesets to release 21 packages
NameType
@clerk/sharedPatch
@clerk/agent-toolkitPatch
@clerk/astroPatch
@clerk/backendPatch
@clerk/chrome-extensionPatch
@clerk/clerk-jsPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/uiPatch
@clerk/vuePatch

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

@pkg-pr-new

Copy link
Copy Markdown

Open in StackBlitz

@clerk/agent-toolkit

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

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8086

@clerk/dev-cli

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

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8086

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8086

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8086

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: c3e2e81

@chriscaninchriscanin self-assigned this Mar 16, 2026
@coderabbitai

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This change modifies the behavior of browser detection functions in the shared package to support token retrieval in React Native environments. The isValidBrowser() and isBrowserOnline() functions in browser.ts are updated to return true when running in non-browser environments instead of false. Corresponding test expectations are updated to reflect this new behavior. A changeset file documents this as a patch release for "@clerk/shared".

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe pull request title clearly and specifically describes the main change: fixing isValidBrowser and isBrowserOnline behavior for React Native environments.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Tip

CodeRabbit can use Trivy to scan for security misconfigurations and secrets in Infrastructure as Code files.

Add a .trivyignore file to your project to customize which findings Trivy reports.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/browser.ts`:
- Around line 60-63: The functions isValidBrowser() and isBrowserOnline() should
not return true for all non-navigator runtimes; narrow the "true" case to only
React Native. Update isValidBrowser() and isBrowserOnline() to detect React
Native explicitly (e.g., check navigator?.product === 'ReactNative' or another
RN-specific signal) and return true only in that case, otherwise return false
for generic non-browser runtimes so code like the guard in webauthn.ts
("isValidBrowser() && typeof window.PublicKeyCredential === 'function'") does
not attempt to access window in SSR/Node environments; ensure the detection
logic is used in the existing functions named isValidBrowser and
isBrowserOnline.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6d624320-01f8-40cd-9142-3372eccc1f4f

📥 Commits

Reviewing files that changed from the base of the PR and between da2b239 and c3e2e81.

📒 Files selected for processing (3)
  • .changeset/fix-gettoken-template-rn.md
  • packages/shared/src/__tests__/browser.spec.ts
  • packages/shared/src/browser.ts

Comment on lines +60 to +63
// Not in a browser environment (e.g. React Native, SSR).
// Return true so non-browser runtimes are not incorrectly
// treated as bots or automated browsers.
return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"1) Find call sites that use isValidBrowser() as a browser/window guard:"
rg -nP --type=ts --type=tsx -C3 'isValidBrowser\(\)\s*&&\s*(typeof\s+window\.|window\.)'echoecho"2) Inspect key downstream paths impacted by non-browser=true behavior:"
rg -nP -C3 '\bisBrowserOnline\(\)|\bisValidBrowserOnline\(\)' \
packages/clerk-js/src/core/resources/Session.ts \
packages/clerk-js/src/core/fapiClient.ts \
packages/shared/src/webauthn.ts
echoecho"Expected:"echo"- If any server-side/SSR path relies on isValidBrowser() to guard window access,"echo" current change is unsafe and should be narrowed to RN detection."

Repository: clerk/javascript

Length of output: 176


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Step 1: Check actual code in browser.ts around lines 60-63 and 76-79 ==="
cat -n packages/shared/src/browser.ts | sed -n '50,85p'echoecho"=== Step 2: Find all imports and usages of isValidBrowser ==="
rg -n 'isValidBrowser' packages/ --max-count=20
echoecho"=== Step 3: Find all imports and usages of isBrowserOnline ==="
rg -n 'isBrowserOnline' packages/ --max-count=20
echoecho"=== Step 4: Check webauthn.ts guard pattern ==="
cat -n packages/shared/src/webauthn.ts | head -20

Repository: clerk/javascript

Length of output: 7354


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Test expectations for isValidBrowser with no navigator ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '40,75p'echoecho"=== Check if webauthn.ts has any build-time guards or SSR exclusion ==="
grep -r "webauthn" packages/ --include="*.ts" --include="*.tsx" --include="*.json"| grep -E "(exclude|server|ssr|node)"||echo"No SSR-specific guards found"echoecho"=== Check for React Native detection utility in codebase ==="
rg -n "ReactNative|react.native|react-native" packages/shared/src/ | head -20
echoecho"=== Check inBrowser() implementation ==="
cat -n packages/shared/src/browser.ts | sed -n '1,50p'

Repository: clerk/javascript

Length of output: 3163


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Where is isWebAuthnSupported() actually called? ==="
rg -rn "isWebAuthnSupported|webauthn" packages/ --type=ts --type=tsx --include="*.ts" --include="*.tsx"| grep -v "node_modules"| grep -v "test"| head -30
echoecho"=== Check if webauthn module is conditionally loaded ==="
rg -rn "from.*webauthn|import.*webauthn" packages/ --include="*.ts" --include="*.tsx"| grep -v testechoecho"=== Full webauthn.ts to understand usage pattern ==="
cat packages/shared/src/webauthn.ts

Repository: clerk/javascript

Length of output: 574


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Where is isWebAuthnSupported() or webauthn functions called? ==="
rg -n "isWebAuthnSupported|isWebAuthnAutofillSupported" packages/ echoecho"=== Check all webauthn imports ==="
rg -n "from.*webauthn|import.*webauthn" packages/
echoecho"=== Full webauthn.ts content ==="
cat packages/shared/src/webauthn.ts

Repository: clerk/javascript

Length of output: 8269


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check if webauthn.ts is server-side accessible or client-only ==="
rg -n "webauthn" packages/shared/package.json packages/shared/tsconfig.json 2>/dev/null ||echo"Not in explicit exports"echoecho"=== Check how webauthn.ts is exported from `@clerk/shared` ==="
cat packages/shared/package.json | grep -A 20 '"exports"'echoecho"=== Look at complete test for isValidBrowser with no navigator ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '41,47p'echoecho"=== Does the test actually expect this behavior? Look at comment ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '40,50p'echoecho"=== Check if there's conditional execution or build-time webauthn exclusion ==="
grep -r "webauthn" packages/clerk-js/src --include="*.json" --include="*.config.*"2>/dev/null ||echo"No special build config for webauthn"echoecho"=== Key: check if window.PublicKeyCredential will actually throw in SSR ==="echo"Testing: in Node.js SSR, accessing window.PublicKeyCredential"
node -e "try { console.log(typeof window.PublicKeyCredential); } catch(e) { console.log('Error:', e.message); }"

Repository: clerk/javascript

Length of output: 2066


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Final verification: Can isWebAuthnSupported ever run in SSR? ==="echo"Check if SignIn/Session resources are server-side initialized:"
rg -n "new SignIn\|new Session\|__internal_isWebAuthnSupported" packages/clerk-js/src/core/clerk.ts | head -15
echoecho"=== Confirm: in SSR with new isValidBrowser() change, this will crash ==="echo"Simulating: inBrowser()=false, navigator=null, then evaluating typeof window.PublicKeyCredential"
cat > /tmp/test.js << 'EOF'// Simulate isValidBrowser() with new changefunction isValidBrowser_NEW() { // In SSR: inBrowser() is false, so navigator becomes null const navigator = false ? null : null; // inBrowser() returns false if (!navigator) { return true; // NEW BEHAVIOR: return true for non-browser } return false;}// Now the guard in isWebAuthnSupportedfunction isWebAuthnSupported() { return ( isValidBrowser_NEW() && typeof window.PublicKeyCredential === 'function' // <-- will crash );}try { console.log("Result:", isWebAuthnSupported());} catch (e) { console.log("CRASH:", e.message);}EOF
node /tmp/test.js

Repository: clerk/javascript

Length of output: 191


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check if clerk resources are instantiated in SSR initialization ==="
rg -n "initializeClerk|new SignIn|new Session" packages/clerk-js/src/core/clerk.ts | head -5
echoecho"=== Confirm window check in isValidBrowser matches inBrowser pattern ==="
cat -n packages/shared/src/browser.ts | sed -n '57,66p'

Repository: clerk/javascript

Length of output: 131


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Verify: is webauthn always guarded with typeof checks on import side? ==="
cat -n packages/clerk-js/src/core/resources/SignIn.ts | sed -n '70,80p'echoecho"=== Check full context of how isWebAuthnSupported is used in SignIn ==="
cat -n packages/clerk-js/src/core/resources/SignIn.ts | sed -n '514,525p'echoecho"=== Simplified: In SSR, would isWebAuthnSupported() be called? ==="echo"Check if SignIn methods are accessible in SSR or only browser-side"
grep -n "class SignIn" packages/clerk-js/src/core/resources/SignIn.ts | head -1
</script>

Repository: clerk/javascript

Length of output: 1639


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check: When are webauthn functions evaluated? At load time or at runtime? ==="
cat -n packages/shared/src/webauthn.ts
echoecho"=== Check: Are these webauthn functions called during SSR server init? ==="echo"They're exported from `@clerk/shared` and imported in clerk-js"echo"But are they called during non-interactive SSR, or only during client-side flows?"echoecho"=== The critical question: Does the guard actually protect? ==="echo"In webauthn.ts line 8:"echo" isValidBrowser() && typeof window.PublicKeyCredential === 'function'"echo""echo"With new isValidBrowser() = true in SSR:"echo" true && typeof window.PublicKeyCredential === 'function'"echo" ^ This evaluates to the right side"echo" ^ But we're in Node.js where window is undefined"echo" -> ReferenceError: window is not defined"

Repository: clerk/javascript

Length of output: 1880


isValidBrowser() and isBrowserOnline() returning true for all non-navigator cases breaks webauthn guard semantics

Lines 60 and 76 returning true for all non-browser runtimes (not just React Native) creates a safety issue. In packages/shared/src/webauthn.ts:8–10, the guard pattern isValidBrowser() && typeof window.PublicKeyCredential === 'function' relies on the first condition being false in non-browser environments. With this change, SSR/Node.js environments will pass the first condition (true), then evaluate typeof window.PublicKeyCredential, which throws ReferenceError: window is not defined since typeof doesn't protect property access.

The test comment correctly identifies the intent ("React Native"), but the implementation conflates React Native with generic non-browser runtimes. Narrow the true return to React Native detection only:

+function isReactNativeRuntime(): boolean {+ return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';+}+
export function isValidBrowser(): boolean {
const navigator = inBrowser() ? window?.navigator : null;
if (!navigator) {
- return true;+ return isReactNativeRuntime();
}
return !userAgentIsRobot(navigator?.userAgent) && !navigator?.webdriver;
}
export function isBrowserOnline(): boolean {
const navigator = inBrowser() ? window?.navigator : null;
if (!navigator) {
- return true;+ return isReactNativeRuntime();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/shared/src/browser.ts` around lines 60 - 63, The functions
isValidBrowser() and isBrowserOnline() should not return true for all
non-navigator runtimes; narrow the "true" case to only React Native. Update
isValidBrowser() and isBrowserOnline() to detect React Native explicitly (e.g.,
check navigator?.product === 'ReactNative' or another RN-specific signal) and
return true only in that case, otherwise return false for generic non-browser
runtimes so code like the guard in webauthn.ts ("isValidBrowser() && typeof
window.PublicKeyCredential === 'function'") does not attempt to access window in
SSR/Node environments; ensure the detection logic is used in the existing
functions named isValidBrowser and isBrowserOnline.

@chriscanin

Copy link
Copy Markdown
ContributorAuthor

This PR is being closed in favor of #8084.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@chriscanin
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(shared): correct isValidBrowser/isBrowserOnline for React Native by chriscanin · Pull Request #8086 · clerk/javascript · GitHub
Skip to content

fix(shared): correct isValidBrowser/isBrowserOnline for React Native - #8086

Closed
chriscanin wants to merge 1 commit into
mainfrom
chris/fix-gettoken-template-offline-rn
Closed

fix(shared): correct isValidBrowser/isBrowserOnline for React Native#8086
chriscanin wants to merge 1 commit into
mainfrom
chris/fix-gettoken-template-offline-rn

Conversation

@chriscanin

@chriscaninchriscanin commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • isValidBrowser() and isBrowserOnline() in @clerk/shared were returning false when window/navigator are undefined (React Native environments)
  • Core-3's token fetching checks these functions before making FAPI calls — returning false caused getToken({ template: '...' }) to silently fail on React Native while getToken() (which reads from local cache) continued to work
  • Fix: return true in non-browser environments since React Native has its own networking layer and should not be treated as a bot or offline

Reported by

Bledar via Discord — getToken({ template: 'one_hour_token' }) returns nothing on Expo after upgrading to latest Clerk

Test plan

  • Updated existing browser.spec.ts test to verify isValidBrowser() returns true when window is undefined
  • All 983 tests in @clerk/shared pass
  • Verify getToken({ template }) works on React Native after fix

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed token retrieval with templates in React Native and other non-browser environments. Non-browser runtimes are now correctly recognized as valid execution contexts, ensuring template-based token operations function properly across React Native, server-side rendering, and similar platforms. This resolves compatibility issues that previously prevented proper authentication token management in these environments.

…owser environments
In React Native, there is no `window` or `navigator`, so `isValidBrowser()` and
`isBrowserOnline()` were returning `false`. This caused core-3's token fetching
to skip FAPI calls for templated tokens (`getToken({ template })`) since it
believed the environment was invalid/offline.
Non-browser runtimes like React Native have their own networking layer and should
not be treated as bots or offline. Return `true` when browser APIs are absent.
@vercel

vercelBot commented Mar 16, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentMar 16, 2026 4:38pm

Request Review

@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c3e2e81

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

This PR includes changesets to release 21 packages
NameType
@clerk/sharedPatch
@clerk/agent-toolkitPatch
@clerk/astroPatch
@clerk/backendPatch
@clerk/chrome-extensionPatch
@clerk/clerk-jsPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/uiPatch
@clerk/vuePatch

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

@pkg-pr-new

Copy link
Copy Markdown

Open in StackBlitz

@clerk/agent-toolkit

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

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8086

@clerk/dev-cli

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

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8086

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8086

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8086

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: c3e2e81

@chriscaninchriscanin self-assigned this Mar 16, 2026
@coderabbitai

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This change modifies the behavior of browser detection functions in the shared package to support token retrieval in React Native environments. The isValidBrowser() and isBrowserOnline() functions in browser.ts are updated to return true when running in non-browser environments instead of false. Corresponding test expectations are updated to reflect this new behavior. A changeset file documents this as a patch release for "@clerk/shared".

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe pull request title clearly and specifically describes the main change: fixing isValidBrowser and isBrowserOnline behavior for React Native environments.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Tip

CodeRabbit can use Trivy to scan for security misconfigurations and secrets in Infrastructure as Code files.

Add a .trivyignore file to your project to customize which findings Trivy reports.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/browser.ts`:
- Around line 60-63: The functions isValidBrowser() and isBrowserOnline() should
not return true for all non-navigator runtimes; narrow the "true" case to only
React Native. Update isValidBrowser() and isBrowserOnline() to detect React
Native explicitly (e.g., check navigator?.product === 'ReactNative' or another
RN-specific signal) and return true only in that case, otherwise return false
for generic non-browser runtimes so code like the guard in webauthn.ts
("isValidBrowser() && typeof window.PublicKeyCredential === 'function'") does
not attempt to access window in SSR/Node environments; ensure the detection
logic is used in the existing functions named isValidBrowser and
isBrowserOnline.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6d624320-01f8-40cd-9142-3372eccc1f4f

📥 Commits

Reviewing files that changed from the base of the PR and between da2b239 and c3e2e81.

📒 Files selected for processing (3)
  • .changeset/fix-gettoken-template-rn.md
  • packages/shared/src/__tests__/browser.spec.ts
  • packages/shared/src/browser.ts

Comment on lines +60 to +63
// Not in a browser environment (e.g. React Native, SSR).
// Return true so non-browser runtimes are not incorrectly
// treated as bots or automated browsers.
return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"1) Find call sites that use isValidBrowser() as a browser/window guard:"
rg -nP --type=ts --type=tsx -C3 'isValidBrowser\(\)\s*&&\s*(typeof\s+window\.|window\.)'echoecho"2) Inspect key downstream paths impacted by non-browser=true behavior:"
rg -nP -C3 '\bisBrowserOnline\(\)|\bisValidBrowserOnline\(\)' \
packages/clerk-js/src/core/resources/Session.ts \
packages/clerk-js/src/core/fapiClient.ts \
packages/shared/src/webauthn.ts
echoecho"Expected:"echo"- If any server-side/SSR path relies on isValidBrowser() to guard window access,"echo" current change is unsafe and should be narrowed to RN detection."

Repository: clerk/javascript

Length of output: 176


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Step 1: Check actual code in browser.ts around lines 60-63 and 76-79 ==="
cat -n packages/shared/src/browser.ts | sed -n '50,85p'echoecho"=== Step 2: Find all imports and usages of isValidBrowser ==="
rg -n 'isValidBrowser' packages/ --max-count=20
echoecho"=== Step 3: Find all imports and usages of isBrowserOnline ==="
rg -n 'isBrowserOnline' packages/ --max-count=20
echoecho"=== Step 4: Check webauthn.ts guard pattern ==="
cat -n packages/shared/src/webauthn.ts | head -20

Repository: clerk/javascript

Length of output: 7354


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Test expectations for isValidBrowser with no navigator ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '40,75p'echoecho"=== Check if webauthn.ts has any build-time guards or SSR exclusion ==="
grep -r "webauthn" packages/ --include="*.ts" --include="*.tsx" --include="*.json"| grep -E "(exclude|server|ssr|node)"||echo"No SSR-specific guards found"echoecho"=== Check for React Native detection utility in codebase ==="
rg -n "ReactNative|react.native|react-native" packages/shared/src/ | head -20
echoecho"=== Check inBrowser() implementation ==="
cat -n packages/shared/src/browser.ts | sed -n '1,50p'

Repository: clerk/javascript

Length of output: 3163


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Where is isWebAuthnSupported() actually called? ==="
rg -rn "isWebAuthnSupported|webauthn" packages/ --type=ts --type=tsx --include="*.ts" --include="*.tsx"| grep -v "node_modules"| grep -v "test"| head -30
echoecho"=== Check if webauthn module is conditionally loaded ==="
rg -rn "from.*webauthn|import.*webauthn" packages/ --include="*.ts" --include="*.tsx"| grep -v testechoecho"=== Full webauthn.ts to understand usage pattern ==="
cat packages/shared/src/webauthn.ts

Repository: clerk/javascript

Length of output: 574


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Where is isWebAuthnSupported() or webauthn functions called? ==="
rg -n "isWebAuthnSupported|isWebAuthnAutofillSupported" packages/ echoecho"=== Check all webauthn imports ==="
rg -n "from.*webauthn|import.*webauthn" packages/
echoecho"=== Full webauthn.ts content ==="
cat packages/shared/src/webauthn.ts

Repository: clerk/javascript

Length of output: 8269


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check if webauthn.ts is server-side accessible or client-only ==="
rg -n "webauthn" packages/shared/package.json packages/shared/tsconfig.json 2>/dev/null ||echo"Not in explicit exports"echoecho"=== Check how webauthn.ts is exported from `@clerk/shared` ==="
cat packages/shared/package.json | grep -A 20 '"exports"'echoecho"=== Look at complete test for isValidBrowser with no navigator ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '41,47p'echoecho"=== Does the test actually expect this behavior? Look at comment ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '40,50p'echoecho"=== Check if there's conditional execution or build-time webauthn exclusion ==="
grep -r "webauthn" packages/clerk-js/src --include="*.json" --include="*.config.*"2>/dev/null ||echo"No special build config for webauthn"echoecho"=== Key: check if window.PublicKeyCredential will actually throw in SSR ==="echo"Testing: in Node.js SSR, accessing window.PublicKeyCredential"
node -e "try { console.log(typeof window.PublicKeyCredential); } catch(e) { console.log('Error:', e.message); }"

Repository: clerk/javascript

Length of output: 2066


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Final verification: Can isWebAuthnSupported ever run in SSR? ==="echo"Check if SignIn/Session resources are server-side initialized:"
rg -n "new SignIn\|new Session\|__internal_isWebAuthnSupported" packages/clerk-js/src/core/clerk.ts | head -15
echoecho"=== Confirm: in SSR with new isValidBrowser() change, this will crash ==="echo"Simulating: inBrowser()=false, navigator=null, then evaluating typeof window.PublicKeyCredential"
cat > /tmp/test.js << 'EOF'// Simulate isValidBrowser() with new changefunction isValidBrowser_NEW() { // In SSR: inBrowser() is false, so navigator becomes null const navigator = false ? null : null; // inBrowser() returns false if (!navigator) { return true; // NEW BEHAVIOR: return true for non-browser } return false;}// Now the guard in isWebAuthnSupportedfunction isWebAuthnSupported() { return ( isValidBrowser_NEW() && typeof window.PublicKeyCredential === 'function' // <-- will crash );}try { console.log("Result:", isWebAuthnSupported());} catch (e) { console.log("CRASH:", e.message);}EOF
node /tmp/test.js

Repository: clerk/javascript

Length of output: 191


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check if clerk resources are instantiated in SSR initialization ==="
rg -n "initializeClerk|new SignIn|new Session" packages/clerk-js/src/core/clerk.ts | head -5
echoecho"=== Confirm window check in isValidBrowser matches inBrowser pattern ==="
cat -n packages/shared/src/browser.ts | sed -n '57,66p'

Repository: clerk/javascript

Length of output: 131


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Verify: is webauthn always guarded with typeof checks on import side? ==="
cat -n packages/clerk-js/src/core/resources/SignIn.ts | sed -n '70,80p'echoecho"=== Check full context of how isWebAuthnSupported is used in SignIn ==="
cat -n packages/clerk-js/src/core/resources/SignIn.ts | sed -n '514,525p'echoecho"=== Simplified: In SSR, would isWebAuthnSupported() be called? ==="echo"Check if SignIn methods are accessible in SSR or only browser-side"
grep -n "class SignIn" packages/clerk-js/src/core/resources/SignIn.ts | head -1
</script>

Repository: clerk/javascript

Length of output: 1639


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check: When are webauthn functions evaluated? At load time or at runtime? ==="
cat -n packages/shared/src/webauthn.ts
echoecho"=== Check: Are these webauthn functions called during SSR server init? ==="echo"They're exported from `@clerk/shared` and imported in clerk-js"echo"But are they called during non-interactive SSR, or only during client-side flows?"echoecho"=== The critical question: Does the guard actually protect? ==="echo"In webauthn.ts line 8:"echo" isValidBrowser() && typeof window.PublicKeyCredential === 'function'"echo""echo"With new isValidBrowser() = true in SSR:"echo" true && typeof window.PublicKeyCredential === 'function'"echo" ^ This evaluates to the right side"echo" ^ But we're in Node.js where window is undefined"echo" -> ReferenceError: window is not defined"

Repository: clerk/javascript

Length of output: 1880


isValidBrowser() and isBrowserOnline() returning true for all non-navigator cases breaks webauthn guard semantics

Lines 60 and 76 returning true for all non-browser runtimes (not just React Native) creates a safety issue. In packages/shared/src/webauthn.ts:8–10, the guard pattern isValidBrowser() && typeof window.PublicKeyCredential === 'function' relies on the first condition being false in non-browser environments. With this change, SSR/Node.js environments will pass the first condition (true), then evaluate typeof window.PublicKeyCredential, which throws ReferenceError: window is not defined since typeof doesn't protect property access.

The test comment correctly identifies the intent ("React Native"), but the implementation conflates React Native with generic non-browser runtimes. Narrow the true return to React Native detection only:

+function isReactNativeRuntime(): boolean {+ return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';+}+
export function isValidBrowser(): boolean {
const navigator = inBrowser() ? window?.navigator : null;
if (!navigator) {
- return true;+ return isReactNativeRuntime();
}
return !userAgentIsRobot(navigator?.userAgent) && !navigator?.webdriver;
}
export function isBrowserOnline(): boolean {
const navigator = inBrowser() ? window?.navigator : null;
if (!navigator) {
- return true;+ return isReactNativeRuntime();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/shared/src/browser.ts` around lines 60 - 63, The functions
isValidBrowser() and isBrowserOnline() should not return true for all
non-navigator runtimes; narrow the "true" case to only React Native. Update
isValidBrowser() and isBrowserOnline() to detect React Native explicitly (e.g.,
check navigator?.product === 'ReactNative' or another RN-specific signal) and
return true only in that case, otherwise return false for generic non-browser
runtimes so code like the guard in webauthn.ts ("isValidBrowser() && typeof
window.PublicKeyCredential === 'function'") does not attempt to access window in
SSR/Node environments; ensure the detection logic is used in the existing
functions named isValidBrowser and isBrowserOnline.

@chriscanin

Copy link
Copy Markdown
ContributorAuthor

This PR is being closed in favor of #8084.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@chriscanin
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); fix(shared): correct isValidBrowser/isBrowserOnline for React Native by chriscanin · Pull Request #8086 · clerk/javascript · GitHub
Skip to content

fix(shared): correct isValidBrowser/isBrowserOnline for React Native - #8086

Closed
chriscanin wants to merge 1 commit into
mainfrom
chris/fix-gettoken-template-offline-rn
Closed

fix(shared): correct isValidBrowser/isBrowserOnline for React Native#8086
chriscanin wants to merge 1 commit into
mainfrom
chris/fix-gettoken-template-offline-rn

Conversation

@chriscanin

@chriscaninchriscanin commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • isValidBrowser() and isBrowserOnline() in @clerk/shared were returning false when window/navigator are undefined (React Native environments)
  • Core-3's token fetching checks these functions before making FAPI calls — returning false caused getToken({ template: '...' }) to silently fail on React Native while getToken() (which reads from local cache) continued to work
  • Fix: return true in non-browser environments since React Native has its own networking layer and should not be treated as a bot or offline

Reported by

Bledar via Discord — getToken({ template: 'one_hour_token' }) returns nothing on Expo after upgrading to latest Clerk

Test plan

  • Updated existing browser.spec.ts test to verify isValidBrowser() returns true when window is undefined
  • All 983 tests in @clerk/shared pass
  • Verify getToken({ template }) works on React Native after fix

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed token retrieval with templates in React Native and other non-browser environments. Non-browser runtimes are now correctly recognized as valid execution contexts, ensuring template-based token operations function properly across React Native, server-side rendering, and similar platforms. This resolves compatibility issues that previously prevented proper authentication token management in these environments.

…owser environments
In React Native, there is no `window` or `navigator`, so `isValidBrowser()` and
`isBrowserOnline()` were returning `false`. This caused core-3's token fetching
to skip FAPI calls for templated tokens (`getToken({ template })`) since it
believed the environment was invalid/offline.
Non-browser runtimes like React Native have their own networking layer and should
not be treated as bots or offline. Return `true` when browser APIs are absent.
@vercel

vercelBot commented Mar 16, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentMar 16, 2026 4:38pm

Request Review

@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c3e2e81

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

This PR includes changesets to release 21 packages
NameType
@clerk/sharedPatch
@clerk/agent-toolkitPatch
@clerk/astroPatch
@clerk/backendPatch
@clerk/chrome-extensionPatch
@clerk/clerk-jsPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/uiPatch
@clerk/vuePatch

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

@pkg-pr-new

Copy link
Copy Markdown

Open in StackBlitz

@clerk/agent-toolkit

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

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8086

@clerk/dev-cli

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

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8086

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8086

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8086

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: c3e2e81

@chriscaninchriscanin self-assigned this Mar 16, 2026
@coderabbitai

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This change modifies the behavior of browser detection functions in the shared package to support token retrieval in React Native environments. The isValidBrowser() and isBrowserOnline() functions in browser.ts are updated to return true when running in non-browser environments instead of false. Corresponding test expectations are updated to reflect this new behavior. A changeset file documents this as a patch release for "@clerk/shared".

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe pull request title clearly and specifically describes the main change: fixing isValidBrowser and isBrowserOnline behavior for React Native environments.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Tip

CodeRabbit can use Trivy to scan for security misconfigurations and secrets in Infrastructure as Code files.

Add a .trivyignore file to your project to customize which findings Trivy reports.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/browser.ts`:
- Around line 60-63: The functions isValidBrowser() and isBrowserOnline() should
not return true for all non-navigator runtimes; narrow the "true" case to only
React Native. Update isValidBrowser() and isBrowserOnline() to detect React
Native explicitly (e.g., check navigator?.product === 'ReactNative' or another
RN-specific signal) and return true only in that case, otherwise return false
for generic non-browser runtimes so code like the guard in webauthn.ts
("isValidBrowser() && typeof window.PublicKeyCredential === 'function'") does
not attempt to access window in SSR/Node environments; ensure the detection
logic is used in the existing functions named isValidBrowser and
isBrowserOnline.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6d624320-01f8-40cd-9142-3372eccc1f4f

📥 Commits

Reviewing files that changed from the base of the PR and between da2b239 and c3e2e81.

📒 Files selected for processing (3)
  • .changeset/fix-gettoken-template-rn.md
  • packages/shared/src/__tests__/browser.spec.ts
  • packages/shared/src/browser.ts

Comment on lines +60 to +63
// Not in a browser environment (e.g. React Native, SSR).
// Return true so non-browser runtimes are not incorrectly
// treated as bots or automated browsers.
return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"1) Find call sites that use isValidBrowser() as a browser/window guard:"
rg -nP --type=ts --type=tsx -C3 'isValidBrowser\(\)\s*&&\s*(typeof\s+window\.|window\.)'echoecho"2) Inspect key downstream paths impacted by non-browser=true behavior:"
rg -nP -C3 '\bisBrowserOnline\(\)|\bisValidBrowserOnline\(\)' \
packages/clerk-js/src/core/resources/Session.ts \
packages/clerk-js/src/core/fapiClient.ts \
packages/shared/src/webauthn.ts
echoecho"Expected:"echo"- If any server-side/SSR path relies on isValidBrowser() to guard window access,"echo" current change is unsafe and should be narrowed to RN detection."

Repository: clerk/javascript

Length of output: 176


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Step 1: Check actual code in browser.ts around lines 60-63 and 76-79 ==="
cat -n packages/shared/src/browser.ts | sed -n '50,85p'echoecho"=== Step 2: Find all imports and usages of isValidBrowser ==="
rg -n 'isValidBrowser' packages/ --max-count=20
echoecho"=== Step 3: Find all imports and usages of isBrowserOnline ==="
rg -n 'isBrowserOnline' packages/ --max-count=20
echoecho"=== Step 4: Check webauthn.ts guard pattern ==="
cat -n packages/shared/src/webauthn.ts | head -20

Repository: clerk/javascript

Length of output: 7354


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Test expectations for isValidBrowser with no navigator ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '40,75p'echoecho"=== Check if webauthn.ts has any build-time guards or SSR exclusion ==="
grep -r "webauthn" packages/ --include="*.ts" --include="*.tsx" --include="*.json"| grep -E "(exclude|server|ssr|node)"||echo"No SSR-specific guards found"echoecho"=== Check for React Native detection utility in codebase ==="
rg -n "ReactNative|react.native|react-native" packages/shared/src/ | head -20
echoecho"=== Check inBrowser() implementation ==="
cat -n packages/shared/src/browser.ts | sed -n '1,50p'

Repository: clerk/javascript

Length of output: 3163


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Where is isWebAuthnSupported() actually called? ==="
rg -rn "isWebAuthnSupported|webauthn" packages/ --type=ts --type=tsx --include="*.ts" --include="*.tsx"| grep -v "node_modules"| grep -v "test"| head -30
echoecho"=== Check if webauthn module is conditionally loaded ==="
rg -rn "from.*webauthn|import.*webauthn" packages/ --include="*.ts" --include="*.tsx"| grep -v testechoecho"=== Full webauthn.ts to understand usage pattern ==="
cat packages/shared/src/webauthn.ts

Repository: clerk/javascript

Length of output: 574


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Where is isWebAuthnSupported() or webauthn functions called? ==="
rg -n "isWebAuthnSupported|isWebAuthnAutofillSupported" packages/ echoecho"=== Check all webauthn imports ==="
rg -n "from.*webauthn|import.*webauthn" packages/
echoecho"=== Full webauthn.ts content ==="
cat packages/shared/src/webauthn.ts

Repository: clerk/javascript

Length of output: 8269


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check if webauthn.ts is server-side accessible or client-only ==="
rg -n "webauthn" packages/shared/package.json packages/shared/tsconfig.json 2>/dev/null ||echo"Not in explicit exports"echoecho"=== Check how webauthn.ts is exported from `@clerk/shared` ==="
cat packages/shared/package.json | grep -A 20 '"exports"'echoecho"=== Look at complete test for isValidBrowser with no navigator ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '41,47p'echoecho"=== Does the test actually expect this behavior? Look at comment ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '40,50p'echoecho"=== Check if there's conditional execution or build-time webauthn exclusion ==="
grep -r "webauthn" packages/clerk-js/src --include="*.json" --include="*.config.*"2>/dev/null ||echo"No special build config for webauthn"echoecho"=== Key: check if window.PublicKeyCredential will actually throw in SSR ==="echo"Testing: in Node.js SSR, accessing window.PublicKeyCredential"
node -e "try { console.log(typeof window.PublicKeyCredential); } catch(e) { console.log('Error:', e.message); }"

Repository: clerk/javascript

Length of output: 2066


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Final verification: Can isWebAuthnSupported ever run in SSR? ==="echo"Check if SignIn/Session resources are server-side initialized:"
rg -n "new SignIn\|new Session\|__internal_isWebAuthnSupported" packages/clerk-js/src/core/clerk.ts | head -15
echoecho"=== Confirm: in SSR with new isValidBrowser() change, this will crash ==="echo"Simulating: inBrowser()=false, navigator=null, then evaluating typeof window.PublicKeyCredential"
cat > /tmp/test.js << 'EOF'// Simulate isValidBrowser() with new changefunction isValidBrowser_NEW() { // In SSR: inBrowser() is false, so navigator becomes null const navigator = false ? null : null; // inBrowser() returns false if (!navigator) { return true; // NEW BEHAVIOR: return true for non-browser } return false;}// Now the guard in isWebAuthnSupportedfunction isWebAuthnSupported() { return ( isValidBrowser_NEW() && typeof window.PublicKeyCredential === 'function' // <-- will crash );}try { console.log("Result:", isWebAuthnSupported());} catch (e) { console.log("CRASH:", e.message);}EOF
node /tmp/test.js

Repository: clerk/javascript

Length of output: 191


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check if clerk resources are instantiated in SSR initialization ==="
rg -n "initializeClerk|new SignIn|new Session" packages/clerk-js/src/core/clerk.ts | head -5
echoecho"=== Confirm window check in isValidBrowser matches inBrowser pattern ==="
cat -n packages/shared/src/browser.ts | sed -n '57,66p'

Repository: clerk/javascript

Length of output: 131


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Verify: is webauthn always guarded with typeof checks on import side? ==="
cat -n packages/clerk-js/src/core/resources/SignIn.ts | sed -n '70,80p'echoecho"=== Check full context of how isWebAuthnSupported is used in SignIn ==="
cat -n packages/clerk-js/src/core/resources/SignIn.ts | sed -n '514,525p'echoecho"=== Simplified: In SSR, would isWebAuthnSupported() be called? ==="echo"Check if SignIn methods are accessible in SSR or only browser-side"
grep -n "class SignIn" packages/clerk-js/src/core/resources/SignIn.ts | head -1
</script>

Repository: clerk/javascript

Length of output: 1639


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"=== Check: When are webauthn functions evaluated? At load time or at runtime? ==="
cat -n packages/shared/src/webauthn.ts
echoecho"=== Check: Are these webauthn functions called during SSR server init? ==="echo"They're exported from `@clerk/shared` and imported in clerk-js"echo"But are they called during non-interactive SSR, or only during client-side flows?"echoecho"=== The critical question: Does the guard actually protect? ==="echo"In webauthn.ts line 8:"echo" isValidBrowser() && typeof window.PublicKeyCredential === 'function'"echo""echo"With new isValidBrowser() = true in SSR:"echo" true && typeof window.PublicKeyCredential === 'function'"echo" ^ This evaluates to the right side"echo" ^ But we're in Node.js where window is undefined"echo" -> ReferenceError: window is not defined"

Repository: clerk/javascript

Length of output: 1880


isValidBrowser() and isBrowserOnline() returning true for all non-navigator cases breaks webauthn guard semantics

Lines 60 and 76 returning true for all non-browser runtimes (not just React Native) creates a safety issue. In packages/shared/src/webauthn.ts:8–10, the guard pattern isValidBrowser() && typeof window.PublicKeyCredential === 'function' relies on the first condition being false in non-browser environments. With this change, SSR/Node.js environments will pass the first condition (true), then evaluate typeof window.PublicKeyCredential, which throws ReferenceError: window is not defined since typeof doesn't protect property access.

The test comment correctly identifies the intent ("React Native"), but the implementation conflates React Native with generic non-browser runtimes. Narrow the true return to React Native detection only:

+function isReactNativeRuntime(): boolean {+ return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';+}+
export function isValidBrowser(): boolean {
const navigator = inBrowser() ? window?.navigator : null;
if (!navigator) {
- return true;+ return isReactNativeRuntime();
}
return !userAgentIsRobot(navigator?.userAgent) && !navigator?.webdriver;
}
export function isBrowserOnline(): boolean {
const navigator = inBrowser() ? window?.navigator : null;
if (!navigator) {
- return true;+ return isReactNativeRuntime();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/shared/src/browser.ts` around lines 60 - 63, The functions
isValidBrowser() and isBrowserOnline() should not return true for all
non-navigator runtimes; narrow the "true" case to only React Native. Update
isValidBrowser() and isBrowserOnline() to detect React Native explicitly (e.g.,
check navigator?.product === 'ReactNative' or another RN-specific signal) and
return true only in that case, otherwise return false for generic non-browser
runtimes so code like the guard in webauthn.ts ("isValidBrowser() && typeof
window.PublicKeyCredential === 'function'") does not attempt to access window in
SSR/Node environments; ensure the detection logic is used in the existing
functions named isValidBrowser and isBrowserOnline.

@chriscanin

Copy link
Copy Markdown
ContributorAuthor

This PR is being closed in favor of #8084.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@chriscanin