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
40 changes: 39 additions & 1 deletion web/src/lib/backend-config.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
BackendStorageError,
buildTransportConfig,
Expand Down Expand Up @@ -109,3 +109,41 @@ describe('buildTransportConfig', () => {
expect(buildTransportConfig()?.wsURL).toBe('ws://localhost:9880/ws');
});
});

describe('VITE_BACKEND_URL build-time fallback', () => {
afterEach(() => {
vi.unstubAllEnvs();
vi.resetModules();
localStorage.clear();
});

it('ignores a non-http(s) VITE_BACKEND_URL (ftp://) and returns undefined', async () => {
vi.stubEnv('VITE_BACKEND_URL', 'ftp://api.example.com');
vi.resetModules();
const { buildTransportConfig: btc } = await import('./backend-config');
// No localStorage override, no valid build-time URL → same-origin default.
expect(btc()).toBeUndefined();
});

it('uses a valid https VITE_BACKEND_URL as the fallback', async () => {
vi.stubEnv('VITE_BACKEND_URL', 'https://api.example.com');
vi.resetModules();
const { buildTransportConfig: btc } = await import('./backend-config');
expect(btc()).toEqual({
baseURL: 'https://api.example.com/api',
adminBaseURL: 'https://api.example.com/api/admin',
wsURL: 'wss://api.example.com/ws',
});
});

it('normalizes VITE_BACKEND_URL by dropping query/hash and trailing slashes', async () => {
vi.stubEnv('VITE_BACKEND_URL', 'https://api.example.com/maxx/?x=1#frag');
vi.resetModules();
const { buildTransportConfig: btc } = await import('./backend-config');
expect(btc()).toEqual({
baseURL: 'https://api.example.com/maxx/api',
adminBaseURL: 'https://api.example.com/maxx/api/admin',
wsURL: 'wss://api.example.com/maxx/ws',
});
});
});
29 changes: 25 additions & 4 deletions web/src/lib/backend-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,34 @@ export class BackendStorageError extends Error {
}
}

/** Build-time fallback (empty string when unset). */
const BUILD_TIME_BACKEND_URL: string =
(import.meta.env.VITE_BACKEND_URL as string | undefined)?.trim() ?? '';
/**
* Normalizes a raw backend URL string the same way setBackendUrl() does:
* validates the protocol is http(s), drops query/hash, strips trailing slashes.
* Returns an empty string for invalid, empty, or non-http(s) inputs so callers
* can treat the result as "no override" (same-origin default).
*/
function normalizeBackendUrl(raw: string): string {
const trimmed = raw.trim();
if (!trimmed) return '';
try {
const parsed = new URL(trimmed);
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
return '';
}
return (parsed.origin + parsed.pathname).replace(/\/+$/, '');
} catch {
return '';
}
}

/** Build-time fallback (empty string when unset or invalid). */
const BUILD_TIME_BACKEND_URL: string = normalizeBackendUrl(
(import.meta.env.VITE_BACKEND_URL as string | undefined) ?? '',
);

/**
* Returns the configured backend base origin (no trailing slash), or an empty
* string when the UI should use its own origin (same-origin default).

*/
export function getBackendUrl(): string {
let stored = '';
Expand Down