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
11 changes: 8 additions & 3 deletions apps/sim/lib/core/utils/urls.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,20 @@ describe('getBaseUrl', () => {
expect(getBaseUrl()).toBe('https://app.example.com')
})

it('falls back to the page origin instead of throwing when the injected env is missing', () => {
/**
* Never guesses from `window.location.origin`: an opaque origin (a sandboxed
* iframe) serializes to the truthy string `'null'`, which would silently
* produce `null/api/...` rather than surfacing the misconfiguration.
*/
it('throws in the browser rather than guessing from the page origin', () => {
setLocation('https://www.sim.ai/workspace/ws-1/w/wf-1')
expect(getBaseUrl()).toBe('https://www.sim.ai')
expect(() => getBaseUrl()).toThrow('NEXT_PUBLIC_APP_URL must be configured')
})

it('treats a whitespace-only NEXT_PUBLIC_APP_URL as unset', () => {
mockGetEnv.mockImplementation((key) => (key === 'NEXT_PUBLIC_APP_URL' ? ' ' : undefined))
setLocation('https://www.sim.ai/')
expect(getBaseUrl()).toBe('https://www.sim.ai')
expect(() => getBaseUrl()).toThrow('NEXT_PUBLIC_APP_URL must be configured')
})
})

Expand Down
29 changes: 12 additions & 17 deletions apps/sim/lib/core/utils/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,26 @@ function normalizeBaseUrl(url: string): string {
* Returns the base URL of the application from NEXT_PUBLIC_APP_URL
* This ensures webhooks, callbacks, and other integrations always use the correct public URL
*
* In the browser, falls back to the page's own origin when the injected env is
* unavailable. Client-side callers only ever want a URL back to the app they are
* already served from, so the origin is a correct answer — and a throw here
* during render tears down the whole page through the error boundary. Server-side
* callers (webhooks, callbacks, emails) have no origin to fall back to and must
* still fail loudly on a misconfigured deployment.
* Deliberately has no browser fallback to `window.location.origin`. The value is
* injected before hydration by `<PublicEnvScript>`, so an empty read means the
* deployment is misconfigured — and a same-origin guess would hide that. It also
* would not be safe to guess: an opaque origin (a sandboxed iframe, and `/chat/*`
* is embeddable) serializes to the string `'null'`, which is truthy and would
* silently produce `null/api/...` at every call site.
*
* @returns The base URL string (e.g., 'http://localhost:3000' or 'https://example.com')
* @throws Error if NEXT_PUBLIC_APP_URL is not configured and no browser origin exists
* @throws Error if NEXT_PUBLIC_APP_URL is not configured
*/
export function getBaseUrl(): string {
const baseUrl = getEnv('NEXT_PUBLIC_APP_URL')?.trim()

if (baseUrl) {
return normalizeBaseUrl(baseUrl)
}

const browserOrigin = getBrowserOrigin()
if (browserOrigin) {
return browserOrigin
if (!baseUrl) {
throw new Error(
'NEXT_PUBLIC_APP_URL must be configured for webhooks and callbacks to work correctly'
)
}

throw new Error(
'NEXT_PUBLIC_APP_URL must be configured for webhooks and callbacks to work correctly'
)
return normalizeBaseUrl(baseUrl)
}

/**
Expand Down
18 changes: 7 additions & 11 deletions packages/testing/src/mocks/urls.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,14 @@ function hasHttpProtocol(url: string): boolean {

function getBaseUrlImpl(): string {
const baseUrl = readEnv('NEXT_PUBLIC_APP_URL')?.trim()
if (baseUrl) {
// Mirrors the real module: protocol-less values get https:// under isProd.
const protocol = envFlagsMock.isProd ? 'https://' : 'http://'
return hasHttpProtocol(baseUrl) ? baseUrl : `${protocol}${baseUrl}`
if (!baseUrl) {
throw new Error(
'NEXT_PUBLIC_APP_URL must be configured for webhooks and callbacks to work correctly'
)
}
// Mirrors the real module: the browser falls back to its own origin, only
// server-side (no `window`) callers throw.
const browserOrigin = getBrowserOriginImpl()
if (browserOrigin) return browserOrigin
throw new Error(
'NEXT_PUBLIC_APP_URL must be configured for webhooks and callbacks to work correctly'
)
// Mirrors the real module: protocol-less values get https:// under isProd.
const protocol = envFlagsMock.isProd ? 'https://' : 'http://'
return hasHttpProtocol(baseUrl) ? baseUrl : `${protocol}${baseUrl}`
}

function getInternalApiBaseUrlImpl(): string {
Expand Down
Loading