Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-gettoken-template-rn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clerk/shared": patch
---

Fix `getToken({ template })` not working in React Native by correcting `isValidBrowser()` and `isBrowserOnline()` to return `true` in non-browser environments instead of `false`
4 changes: 2 additions & 2 deletions packages/shared/src/__tests__/browser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ describe('isValidBrowser', () => {
vi.restoreAllMocks();
});

it('returns false if not in browser', () => {
it('returns true if not in browser (assumes valid in non-browser environments like React Native)', () => {
const windowSpy = vi.spyOn(global, 'window', 'get');
// @ts-ignore - Test
windowSpy.mockReturnValue(undefined);

expect(isValidBrowser()).toBe(false);
expect(isValidBrowser()).toBe(true);
});

it('returns true if in browser, navigator is not a bot, and webdriver is not enabled', () => {
Expand Down
10 changes: 8 additions & 2 deletions packages/shared/src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ export function userAgentIsRobot(userAgent: string): boolean {
export function isValidBrowser(): boolean {
const navigator = inBrowser() ? window?.navigator : null;
if (!navigator) {
return false;
// 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;
Comment on lines +60 to +63
Copy link
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/bash
set -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\.)'

echo
echo "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

echo
echo "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/bash
set -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'

echo
echo "=== Step 2: Find all imports and usages of isValidBrowser ==="
rg -n 'isValidBrowser' packages/ --max-count=20

echo
echo "=== Step 3: Find all imports and usages of isBrowserOnline ==="
rg -n 'isBrowserOnline' packages/ --max-count=20

echo
echo "=== 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/bash
set -euo pipefail

echo "=== Test expectations for isValidBrowser with no navigator ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '40,75p'

echo
echo "=== 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"

echo
echo "=== Check for React Native detection utility in codebase ==="
rg -n "ReactNative|react.native|react-native" packages/shared/src/ | head -20

echo
echo "=== Check inBrowser() implementation ==="
cat -n packages/shared/src/browser.ts | sed -n '1,50p'

Repository: clerk/javascript

Length of output: 3163


🏁 Script executed:

#!/bin/bash
set -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

echo
echo "=== Check if webauthn module is conditionally loaded ==="
rg -rn "from.*webauthn|import.*webauthn" packages/ --include="*.ts" --include="*.tsx" | grep -v test

echo
echo "=== Full webauthn.ts to understand usage pattern ==="
cat packages/shared/src/webauthn.ts

Repository: clerk/javascript

Length of output: 574


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Where is isWebAuthnSupported() or webauthn functions called? ==="
rg -n "isWebAuthnSupported|isWebAuthnAutofillSupported" packages/ 

echo
echo "=== Check all webauthn imports ==="
rg -n "from.*webauthn|import.*webauthn" packages/

echo
echo "=== Full webauthn.ts content ==="
cat packages/shared/src/webauthn.ts

Repository: clerk/javascript

Length of output: 8269


🏁 Script executed:

#!/bin/bash
set -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"

echo
echo "=== Check how webauthn.ts is exported from `@clerk/shared` ==="
cat packages/shared/package.json | grep -A 20 '"exports"'

echo
echo "=== Look at complete test for isValidBrowser with no navigator ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '41,47p'

echo
echo "=== Does the test actually expect this behavior? Look at comment ==="
cat -n packages/shared/src/__tests__/browser.spec.ts | sed -n '40,50p'

echo
echo "=== 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"

echo
echo "=== 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/bash
set -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

echo
echo "=== 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 change
function 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 isWebAuthnSupported
function 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/bash
set -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

echo
echo "=== 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/bash
set -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'

echo
echo "=== Check full context of how isWebAuthnSupported is used in SignIn ==="
cat -n packages/clerk-js/src/core/resources/SignIn.ts | sed -n '514,525p'

echo
echo "=== 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/bash
set -euo pipefail

echo "=== Check: When are webauthn functions evaluated? At load time or at runtime? ==="
cat -n packages/shared/src/webauthn.ts

echo
echo "=== 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?"

echo
echo "=== 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.

}
return !userAgentIsRobot(navigator?.userAgent) && !navigator?.webdriver;
}
Expand All @@ -70,7 +73,10 @@ export function isValidBrowser(): boolean {
export function isBrowserOnline(): boolean {
const navigator = inBrowser() ? window?.navigator : null;
if (!navigator) {
return false;
// Not in a browser environment (e.g. React Native, SSR).
// Assume online — RN has its own networking layer and the
// absence of browser APIs does not indicate offline status.
return true;
}

// navigator.onLine is the standard API and is reliable for detecting
Expand Down
Loading