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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
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.

}
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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n 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;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
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.

}
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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
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.

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

}
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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
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.

}
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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
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.

}
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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
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.

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

}
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