diff --git a/.env.example b/.env.example index 3f577a0..17a61b2 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,7 @@ -JWT_SECRET=*** -ENCRYPTION_SECRET=change-this-to-at-least-32-characters +JWT_SECRET=replace-me +ENCRYPTION_SECRET=replace-with-at-least-32-characters DATABASE_URL=file:./dev.db CODEXDASH_FRONTEND_ORIGIN=http://localhost:5173 CODEX_OAUTH_REDIRECT_URI=http://localhost:1455/auth/callback +# Optional: set this in local dev when the Vite app runs on a different origin than the API. VITE_API_BASE_URL=http://localhost:3001 diff --git a/README.md b/README.md index 81580de..bf02ded 100644 --- a/README.md +++ b/README.md @@ -73,8 +73,9 @@ docker run --rm \ Notes: - The container serves the built React app from the same process on port `3001`. -- The bundled frontend defaults to `http://localhost:3001` for API calls. If you need a different origin, rebuild the image with `VITE_API_BASE_URL` set at build time. -- `CODEX_OAUTH_CALLBACK_BIND_HOST=0.0.0.0` is baked into the image so the callback bridge remains reachable through Docker port publishing while the public redirect URL can still stay on `localhost:1455`. +- The bundled frontend now defaults to the browser's current origin for API calls, so the production image can be deployed behind any host name without rebuilding the web bundle. +- `VITE_API_BASE_URL` is now optional and mainly useful for local development when Vite runs on a different origin than the API. +- `CODEX_OAUTH_CALLBACK_BIND_HOST=0.0.0.0` keeps the callback bridge reachable through Docker port publishing while the public redirect URL can still stay on `localhost:1455`. - If the callback bridge is still unreachable in your setup, the manual callback URL paste fallback remains available. ## Environment variables @@ -82,11 +83,12 @@ Notes: ### Root `.env` ```env -JWT_SECRET=*** -ENCRYPTION_SECRET=*** +JWT_SECRET=replace-me +ENCRYPTION_SECRET=replace-with-at-least-32-characters DATABASE_URL=file:./dev.db CODEXDASH_FRONTEND_ORIGIN=http://localhost:5173 CODEX_OAUTH_REDIRECT_URI=http://localhost:1455/auth/callback +# Optional in local dev when the web app does not share the API origin. VITE_API_BASE_URL=http://localhost:3001 ``` diff --git a/apps/web/package.json b/apps/web/package.json index 54c526d..338f22b 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -7,7 +7,8 @@ "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "test": "bun test ./test" }, "dependencies": { "@codexdash/shared-types": "workspace:*", diff --git a/apps/web/src/lib/api-base.ts b/apps/web/src/lib/api-base.ts new file mode 100644 index 0000000..6c75490 --- /dev/null +++ b/apps/web/src/lib/api-base.ts @@ -0,0 +1,22 @@ +const LOCALHOST_API_ORIGIN = 'http://localhost:3001'; + +function trimTrailingSlashes(value: string): string { + return value.replace(/\/+$/, ''); +} + +export function getApiBaseUrl( + configuredApiBaseUrl?: string, + windowOrigin?: string, +): string { + const configured = configuredApiBaseUrl?.trim(); + if (configured) { + return trimTrailingSlashes(configured); + } + + const origin = windowOrigin?.trim(); + if (origin) { + return trimTrailingSlashes(origin); + } + + return LOCALHOST_API_ORIGIN; +} diff --git a/apps/web/src/lib/api.ts b/apps/web/src/lib/api.ts index 75b3f96..7d8d6bf 100644 --- a/apps/web/src/lib/api.ts +++ b/apps/web/src/lib/api.ts @@ -10,9 +10,13 @@ import type { UsageSummary, UserProfile, } from '@codexdash/shared-types'; +import { getApiBaseUrl } from './api-base'; import { clearToken, getToken } from './storage'; -const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:3001'; +const API_BASE_URL = getApiBaseUrl( + import.meta.env.VITE_API_BASE_URL, + typeof window === 'undefined' ? undefined : window.location.origin, +); async function request(path: string, init?: RequestInit): Promise { const token = getToken(); diff --git a/apps/web/test/api-base.test.js b/apps/web/test/api-base.test.js new file mode 100644 index 0000000..b86de4f --- /dev/null +++ b/apps/web/test/api-base.test.js @@ -0,0 +1,20 @@ +import { describe, expect, test } from 'bun:test'; +import { getApiBaseUrl } from '../src/lib/api-base.ts'; + +describe('getApiBaseUrl', () => { + test('uses the configured API base when provided', () => { + expect(getApiBaseUrl('https://api.example.com/', 'https://app.example.com')).toBe( + 'https://api.example.com', + ); + }); + + test('defaults to the current window origin when no API base is configured', () => { + expect(getApiBaseUrl(undefined, 'https://app.example.com/')).toBe( + 'https://app.example.com', + ); + }); + + test('falls back to localhost in non-browser contexts without configuration', () => { + expect(getApiBaseUrl(undefined, undefined)).toBe('http://localhost:3001'); + }); +}); diff --git a/package.json b/package.json index b0c0ea5..800ad95 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "dev:web": "bun run --filter @codexdash/web dev", "build": "bun run --filter @codexdash/api --filter @codexdash/web build", "lint": "bun run --filter @codexdash/api --filter @codexdash/web lint", - "test": "bun run --filter @codexdash/api --if-present test", + "test": "bun run --filter @codexdash/api --filter @codexdash/web --if-present test", "postinstall": "bun run --filter @codexdash/api prisma:generate" } }