Skip to content

Conversation

bratsos
Copy link
Contributor

@bratsos bratsos commented Sep 9, 2025

Description

Users reported a [TypeError: Cannot read property 'origin' of undefined] warning/error when calling signOut() from the useAuth hook in Expo applications. While the signOut functionality worked correctly, this error was logged to the console.

This issue started when we introduced the debugLogger in PR #6452, which attempts to strip the origin from the redirect URL before reporting it in debug logs.

Steps to reproduce

  • Clone https://github.com/beerose/clerk-expo-bare/ or use your own Expo + Clerk project
  • Make sure you're not using expo-router - it imports @expo/metro-runtime which sets up polyfills for window.location.origin so you won't be able to see the error
  • Login with a user and call useAuth().signOut()
  • You should see the following error in the logs [TypeError: Cannot read property 'origin' of undefined]
  • Checkout this branch
  • On packages/clerk-js run turbo build && yalc publish (requires yalc installed globally)
  • On your expo app run yalc add @clerk/clerk-js
  • Rerun the flow
  • You should not see the error anymore 🎉

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • New Features
    • Added a utility to strip the site origin from same-origin absolute URLs while preserving relative and external URLs.
  • Bug Fixes
    • Avoids errors when URL handling runs in non-browser environments (no window/location).
  • Tests
    • Added tests covering browser and non-browser URL scenarios.
  • Chores
    • Updated bundle size threshold and added a patch release changeset.

Copy link

changeset-bot bot commented Sep 9, 2025

🦋 Changeset detected

Latest commit: a78ffd7

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

This PR includes changesets to release 3 packages
Name Type
@clerk/clerk-js Patch
@clerk/chrome-extension Patch
@clerk/clerk-expo Patch

Not sure what this means? Click here to learn what changesets are.

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

Copy link
Contributor

coderabbitai bot commented Sep 9, 2025

Walkthrough

Adds stripOrigin to URL utilities and exports it. Implements a guard for non‑browser environments to return the input unchanged when window.location is unavailable. Introduces tests covering behavior with and without window.location, for absolute and relative URLs. Adds a changeset and increases a bundle size limit.

Changes

Cohort / File(s) Summary of Edits
URL utility implementation
packages/clerk-js/src/utils/url.ts
Implemented stripOrigin(url: string) with a guard for undefined window.location; returns input unchanged when not in a browser, otherwise strips matching origin from absolute URLs. Added stripOrigin to exports.
URL utility tests
packages/clerk-js/src/utils/__tests__/url.spec.ts
Added test suite validating stripOrigin behavior in browser and non-browser environments, covering absolute vs. relative URLs and ensuring no throw when window.location is undefined.
Changeset
.changeset/good-loops-make.md
Added a changeset file declaring a patch release for @clerk/clerk-js with note: "Fixes errors in react-native without polyfill for window.location".
Bundlewatch config
packages/clerk-js/bundlewatch.config.json
Increased maxSize for ./dist/clerk.browser.js from 78KB to 79KB.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Caller
  participant U as stripOrigin(url)
  participant E as Environment

  C->>U: call with url string
  U->>E: check if window.location exists
  alt window.location defined
    U->>U: parse URL and compare origin to window.location.origin
    alt origins match
      U-->>C: return path+query+hash (origin stripped)
    else
      U-->>C: return original absolute URL
    end
  else window.location undefined
    U-->>C: return input unchanged
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Pre-merge checks (3 passed)

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title “fix(clerk-js): Handle removing origin in non-browser environments” directly reflects the primary change by indicating that the pull request patches the origin‐stripping utility to work safely outside browser contexts. It is concise, labels the type of change and the affected package, and avoids extraneous details. This makes it easy for team members to scan and understand the core purpose of the PR at a glance.
Description Check ✅ Passed The description clearly outlines the bug scenario, including the TypeError in Expo environments, links to the related debugLogger change, and provides concrete reproduction and verification steps. It is directly tied to the changes in stripOrigin and explains both the issue and the resolution, making it fully relevant to the changeset. All necessary context for reviewers is present without veering off topic.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

Poem

I nibble at URLs, neat and precise,
Strip the origin clean—slice, slice, slice.
If windows go missing, I’ll keep it whole,
No panic, no crash, just a tidy stroll.
Hop-hop, tests green—now fetch me a roll! 🥕

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 49f8a60 and a78ffd7.

📒 Files selected for processing (1)
  • packages/clerk-js/src/utils/url.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/clerk-js/src/utils/url.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (25)
  • GitHub Check: Integration Tests (elements, chrome)
  • GitHub Check: Integration Tests (nextjs, chrome, 15)
  • GitHub Check: Integration Tests (sessions, chrome)
  • GitHub Check: Integration Tests (machine, chrome)
  • GitHub Check: Integration Tests (localhost, chrome)
  • GitHub Check: Integration Tests (nextjs, chrome, 14)
  • GitHub Check: Integration Tests (expo-web, chrome)
  • GitHub Check: Integration Tests (vue, chrome)
  • GitHub Check: Integration Tests (tanstack-react-start, chrome)
  • GitHub Check: Integration Tests (nuxt, chrome)
  • GitHub Check: Integration Tests (custom, chrome)
  • GitHub Check: Integration Tests (react-router, chrome)
  • GitHub Check: Integration Tests (astro, chrome)
  • GitHub Check: Integration Tests (tanstack-react-router, chrome)
  • GitHub Check: Integration Tests (billing, chrome)
  • GitHub Check: Integration Tests (quickstart, chrome)
  • GitHub Check: Integration Tests (ap-flows, chrome)
  • GitHub Check: Integration Tests (generic, chrome)
  • GitHub Check: Integration Tests (express, chrome)
  • GitHub Check: Unit Tests (18, --filter=@clerk/astro --filter=@clerk/backend --filter=@clerk/express --filter=@c...
  • GitHub Check: Publish with pkg-pr-new
  • GitHub Check: Unit Tests (22, **)
  • GitHub Check: Static analysis
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch alexbratsos/user-3415-clerkclerk-expo-cannot-read-property-origin-of-undefined

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

Copy link

vercel bot commented Sep 9, 2025

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

Project Deployment Preview Comments Updated (UTC)
clerk-js-sandbox Ready Ready Preview Comment Sep 9, 2025 5:14pm

Copy link

pkg-pr-new bot commented Sep 9, 2025

Open in StackBlitz

@clerk/agent-toolkit

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

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/dev-cli

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

@clerk/elements

npm i https://pkg.pr.new/@clerk/elements@6737

@clerk/clerk-expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/clerk-react

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

@clerk/react-router

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

@clerk/remix

npm i https://pkg.pr.new/@clerk/remix@6737

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/themes

npm i https://pkg.pr.new/@clerk/themes@6737

@clerk/types

npm i https://pkg.pr.new/@clerk/types@6737

@clerk/upgrade

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

@clerk/vue

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

commit: a78ffd7

Comment on lines 177 to 181
// In non-browser environments `window.location.origin` might not be available
// if not polyfilled, so we can't construct a URL object with the `url` string
if (typeof window.location === 'undefined' && typeof url === 'string') {
return url;
}
Copy link
Member

Choose a reason for hiding this comment

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

❓ should we instead handle this in toURL() where the direct window.location.origin usage is?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That was my initial thought, but I think stripOrigin is better suited for making sure origin is accessible, and as it accepts both URL and string, essentially the code looks like this:

  • if you somehow managed to create a URL object, I'll pass it down to toURL
  • if you passed me a string, and I cannot safely detect origin to remove it, I'll just return the string

Happy to change this if you think moving it to toURL is easier to understand though.

Copy link
Member

Choose a reason for hiding this comment

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

@bratsos I don't think this covers the case of passing a fully qualified URL as a string, e.g.

stripOrigin("https://clerk.com/blog") // -> /blog

In this case, I would still expect it to strip the origin. toURL() only uses window.location.origin as the fallback.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this case, if location is available (essentially in all browser environments) this will still work. It won't work if we detect that location is no available, so we're in an exotic runtime that isn't polyfilled.

The thing is that toURL uses URL directly, and even though it's polyfilled in react native (source), the implementation is non-standard - and for example .origin which is accessed by stripOrigin is not even implemented. So we cannot really assume that if URL exists, new URL('https://clerk.com/blog', undefined) would work as expected. I think it's too late if we reach to toURL when we detect that something is wrong in the runtime APIs.

An even better solution would probably be to try and detect if the required APIs are available, and work as expected, or detect the runtime and decide what to do based on that, but I think that's an overkill as in most cases you're either in a browser/polyfilled* environment (so window.location and URL exist and we can safely use them), or not, so in that case we cannot do much on a plain string, unless we want to implement a pathname parser in-house.

*: If you polyfilled the environment in a weird way this can also throw us off.

WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

Makes sense! Maybe we can adjust the JSDoc comment here to make it explicit that origin won't be stripped in certain non-browser environments.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure: ff7748e

Copy link
Member

@nikosdouvlis nikosdouvlis left a comment

Choose a reason for hiding this comment

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

Needs a changeset (and I had a minor question) but this looks good to go. Approving to unblock

export function stripOrigin(url: URL | string): string {
// In non-browser environments `window.location.origin` might not be available
// if not polyfilled, so we can't construct a URL object with the `url` string
if (typeof window.location === 'undefined' && typeof url === 'string') {
Copy link
Member

Choose a reason for hiding this comment

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

Sorry if Im missing something obvious, but would this be safer?

Suggested change
if (typeof window.location === 'undefined' && typeof url === 'string') {
if ((typeof window === 'undefined' || !window.location) && typeof url === 'string') {

Copy link
Contributor Author

@bratsos bratsos Sep 9, 2025

Choose a reason for hiding this comment

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

This is intentional as we're expecting that at least a window global object will be somehow declared. If it's not I prefer to get an error here, plus we'll have many other places that would break as well. WDYT?

Edit: Happy to make this more defensive though if we think it's valuable

Copy link
Member

Choose a reason for hiding this comment

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

window is defined in react-native 😬

See my comment here https://github.com/clerk/javascript/pull/4095/files#r1742755342

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/clerk-js/src/utils/url.ts (1)

160-162: Optional: make toURL safe in non-browser environments or document it as web-only.

toURL unconditionally reads window.location.origin. Either guard like above or annotate as browser-only and keep it out of RN/SSR paths. This reduces future regressions similar to the one you’re fixing.

If you prefer to harden it now:

 export function toURL(url: string | URL): URL {
-  return new URL(url.toString(), window.location.origin);
+  const base =
+    typeof window !== 'undefined' && !!window.location && typeof window.location.origin === 'string'
+      ? window.location.origin
+      : DUMMY_URL_BASE;
+  return new URL(url.toString(), base);
 }
packages/clerk-js/src/utils/__tests__/url.spec.ts (1)

646-673: Stubbing window.location via defineProperty is brittle; prefer stubbing the global or add a case for URL inputs.

Directly redefining window.location can fail depending on the test environment/property descriptors. Use vi.stubGlobal or guard with try/catch. Also add a test for stripOrigin(new URL(...)) with window/location unavailable to lock the fix.

Suggested test adjustments:

 describe('stripOrigin(url)', () => {
   it('should strip origin when window.location is available', () => {
-    const originalLocation = window.location;
-    Object.defineProperty(window, 'location', {
-      value: { origin: 'https://example.com' },
-      writable: true,
-    });
+    const originalLocation = window.location;
+    // Prefer stubbing the global to avoid non-configurable descriptor issues
+    vi.stubGlobal('window', { ...window, location: { origin: 'https://example.com' } } as any);

     expect(stripOrigin('https://example.com/test?param=1')).toBe('/test?param=1');
     expect(stripOrigin('/test')).toBe('/test');

-    Object.defineProperty(window, 'location', { value: originalLocation });
+    // Restore
+    vi.stubGlobal('window', { ...window, location: originalLocation } as any);
   });

   it('should handle undefined window.location gracefully', () => {
-    const originalLocation = window.location;
-    Object.defineProperty(window, 'location', {
-      value: undefined,
-      writable: true,
-    });
+    const originalLocation = window.location;
+    vi.stubGlobal('window', { ...window, location: undefined } as any);

     expect(() => stripOrigin('/test')).not.toThrow();
     expect(stripOrigin('/test')).toBe('/test');
     expect(stripOrigin('https://example.com/test')).toBe('https://example.com/test');

-    Object.defineProperty(window, 'location', { value: originalLocation });
+    vi.stubGlobal('window', { ...window, location: originalLocation } as any);
   });
+
+  it('should return href unchanged for URL inputs when window/location is unavailable', () => {
+    const originalWindow = globalThis.window;
+    vi.stubGlobal('window', undefined as any);
+    expect(stripOrigin(new URL('https://example.com/test'))).toBe('https://example.com/test');
+    vi.stubGlobal('window', originalWindow as any);
+  });
 });
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f0fe516 and 686f2f0.

📒 Files selected for processing (2)
  • packages/clerk-js/src/utils/__tests__/url.spec.ts (2 hunks)
  • packages/clerk-js/src/utils/url.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.{js,jsx,ts,tsx}: All code must pass ESLint checks with the project's configuration
Follow established naming conventions (PascalCase for components, camelCase for variables)
Maintain comprehensive JSDoc comments for public APIs
Use dynamic imports for optional features
All public APIs must be documented with JSDoc
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Profile and optimize critical paths
Validate all inputs and sanitize outputs
Implement proper logging with different levels

Files:

  • packages/clerk-js/src/utils/url.ts
  • packages/clerk-js/src/utils/__tests__/url.spec.ts
**/*.{js,jsx,ts,tsx,json,css,scss,md,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/clerk-js/src/utils/url.ts
  • packages/clerk-js/src/utils/__tests__/url.spec.ts
packages/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/clerk-js/src/utils/url.ts
  • packages/clerk-js/src/utils/__tests__/url.spec.ts
packages/**/*.{ts,tsx,d.ts}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Packages should export TypeScript types alongside runtime code

Files:

  • packages/clerk-js/src/utils/url.ts
  • packages/clerk-js/src/utils/__tests__/url.spec.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Prefer readonly for properties that shouldn't change after construction
Prefer composition and interfaces over deep inheritance chains
Use mixins for shared behavior across unrelated classes
Implement dependency injection for loose coupling
Let TypeScript infer when types are obvious
Use const assertions for literal types: as const
Use satisfies operator for type checking without widening
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Use type-only imports: import type { ... } from ...
No any types without justification
Proper error handling with typed errors
Consistent use of readonly for immutable data
Proper generic constraints
No unused type parameters
Proper use of utility types instead of manual type construction
Type-only imports where possible
Proper tree-shaking friendly exports
No circular dependencies
Efficient type computations (avoid deep recursion)

Files:

  • packages/clerk-js/src/utils/url.ts
  • packages/clerk-js/src/utils/__tests__/url.spec.ts
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Support multiple Clerk environment variables (CLERK_, NEXT_PUBLIC_CLERK_, etc.) for configuration.

Files:

  • packages/clerk-js/src/utils/url.ts
  • packages/clerk-js/src/utils/__tests__/url.spec.ts
packages/**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Unit tests should use Jest or Vitest as the test runner.

Files:

  • packages/clerk-js/src/utils/__tests__/url.spec.ts
packages/{clerk-js,elements,themes}/**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Visual regression testing should be performed for UI components.

Files:

  • packages/clerk-js/src/utils/__tests__/url.spec.ts
**/__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)

**/__tests__/**/*.{ts,tsx}: Create type-safe test builders/factories
Use branded types for test isolation
Implement proper mock types that match interfaces

Files:

  • packages/clerk-js/src/utils/__tests__/url.spec.ts
🧬 Code graph analysis (1)
packages/clerk-js/src/utils/__tests__/url.spec.ts (1)
packages/clerk-js/src/utils/url.ts (1)
  • stripOrigin (176-185)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Build Packages
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: semgrep/ci
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
packages/clerk-js/src/utils/__tests__/url.spec.ts (1)

23-23: Import looks good.

Including stripOrigin in the test surface is appropriate.

Comment on lines 176 to 185
export function stripOrigin(url: URL | string): string {
// In non-browser environments `window.location.origin` might not be available
// if not polyfilled, so we can't construct a URL object with the `url` string
if (typeof window.location === 'undefined' && typeof url === 'string') {
return url;
}

url = toURL(url);
return url.href.replace(url.origin, '');
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Fix: guard must not access window.location when window is undefined; also avoid toURL() (it dereferences window.location.origin).

Current check typeof window.location === 'undefined' will throw in SSR/Node where window is not defined. Additionally, the subsequent toURL(url) uses window.location.origin and can still throw in non-browser contexts or when url is a URL instance. Harden the function and remove the dependency on toURL() here.

Apply:

 export function stripOrigin(url: URL | string): string {
-  // In non-browser environments `window.location.origin` might not be available
-  // if not polyfilled, so we can't construct a URL object with the `url` string
-  if (typeof window.location === 'undefined' && typeof url === 'string') {
-    return url;
-  }
-
-  url = toURL(url);
-  return url.href.replace(url.origin, '');
+  // In non-browser environments `window` or `window.location` might not exist.
+  const hasWindowOrigin =
+    typeof window !== 'undefined' && !!window.location && typeof window.location.origin === 'string';
+  if (!hasWindowOrigin) {
+    // Without a reliable current origin, return input unchanged.
+    return typeof url === 'string' ? url : url.href;
+  }
+  const u = typeof url === 'string' ? new URL(url, window.location.origin) : url;
+  return u.href.replace(u.origin, '');
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function stripOrigin(url: URL | string): string {
// In non-browser environments `window.location.origin` might not be available
// if not polyfilled, so we can't construct a URL object with the `url` string
if (typeof window.location === 'undefined' && typeof url === 'string') {
return url;
}
url = toURL(url);
return url.href.replace(url.origin, '');
}
export function stripOrigin(url: URL | string): string {
// In non-browser environments `window` or `window.location` might not exist.
const hasWindowOrigin =
typeof window !== 'undefined' && !!window.location && typeof window.location.origin === 'string';
if (!hasWindowOrigin) {
// Without a reliable current origin, return input unchanged.
return typeof url === 'string' ? url : url.href;
}
const u = typeof url === 'string' ? new URL(url, window.location.origin) : url;
return u.href.replace(u.origin, '');
}
🤖 Prompt for AI Agents
In packages/clerk-js/src/utils/url.ts around lines 176-185, the current guard
uses typeof window.location which throws when window is undefined and then calls
toURL() which itself accesses window.location.origin; change the logic to first
check typeof window !== 'undefined' before touching window at all, avoid calling
toURL(), and handle both URL and string inputs explicitly: if the input is a URL
instance use it directly; if it's a string and window is undefined simply return
the string unchanged; otherwise (in a browser or when window exists) construct a
URL safely using window.location.origin as the base (or a fallback base like
'http://localhost' if needed) and return href with origin stripped; ensure no
access to window.location happens unless typeof window !== 'undefined'.

@bratsos bratsos merged commit b291c98 into main Sep 9, 2025
89 of 92 checks passed
@bratsos bratsos deleted the alexbratsos/user-3415-clerkclerk-expo-cannot-read-property-origin-of-undefined branch September 9, 2025 17:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants