From 8656f237d4f3c4f5abd423af6ca15120f1806957 Mon Sep 17 00:00:00 2001 From: Shinwoo PARK Date: Fri, 1 May 2026 08:35:06 +0900 Subject: [PATCH] feat: default web API calls to same origin --- .env.example | 5 +++-- README.md | 10 ++++++---- apps/web/package.json | 3 ++- apps/web/src/lib/api-base.ts | 22 ++++++++++++++++++++++ apps/web/src/lib/api.ts | 6 +++++- apps/web/test/api-base.test.js | 20 ++++++++++++++++++++ package.json | 2 +- 7 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 apps/web/src/lib/api-base.ts create mode 100644 apps/web/test/api-base.test.js 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" } }