From 27198b53ea5a64639e2ee740b2e4bbf91443403c Mon Sep 17 00:00:00 2001 From: dev Date: Tue, 5 May 2026 21:06:34 +0200 Subject: [PATCH] =?UTF-8?q?Nexo=20P1=20fix=20=E2=80=94=20match=20API=20hos?= =?UTF-8?q?tname=20to=20the=20page=20so=20the=20session=20cookie=20travels?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The session cookie is set with `SameSite=Lax` (see `servers/dating/http.mjs:sessionCookie`). Browsers treat `http://localhost` and `http://127.0.0.1` as DIFFERENT sites, so when SvelteKit dev runs on `localhost:5173` and the API client hard-codes `127.0.0.1:8787`, the cookie set by `/api/auth/login` is dropped on every subsequent fetch. End result the user reported: login appears to "do nothing" — the request really did succeed, but the next `/api/session` probe arrives without the cookie and the client thinks the user is anonymous again. Fix: derive the API base from `window.location.hostname` at runtime (falling back to `127.0.0.1` on SSR / vitest where `window` doesn't exist). Now the page on `localhost:5173` talks to `localhost:8787` and the page on `127.0.0.1:5173` talks to `127.0.0.1:8787` — both same-site pairs, so `SameSite=Lax` keeps the cookie attached on fetch. `DATING_API_DEFAULT_BASE` stays exported as a deprecated literal so external callers that imported it don't break; new code should use `resolveDatingApiBase()` or pass `options.base` explicitly. The server's CORS allowlist already covers both `localhost:5173` and `127.0.0.1:5173`, so no server change is needed. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/web/routes/dating/_lib/api.ts | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/web/routes/dating/_lib/api.ts b/src/web/routes/dating/_lib/api.ts index 6049cd4..efafaa2 100644 --- a/src/web/routes/dating/_lib/api.ts +++ b/src/web/routes/dating/_lib/api.ts @@ -45,7 +45,34 @@ import { type DatingUser } from './types.ts'; -export const DATING_API_DEFAULT_BASE = 'http://127.0.0.1:8787'; +/** Default port the standalone dating server listens on. */ +export const DATING_API_DEFAULT_PORT = 8787; + +/** + * Resolve the API base URL. + * + * Browsers treat `localhost` and `127.0.0.1` as different sites, so + * the session cookie's `SameSite=Lax` policy drops it on cross-origin + * fetches between them — the user logs in, the cookie is set on the + * 127.0.0.1 origin, and the next `/api/session` probe from + * localhost:5173 lands without it. Result: login looks like it + * silently does nothing. + * + * To stay same-site without touching the server, reuse whatever + * hostname the page is already on (`localhost` ↔ `localhost`, + * `127.0.0.1` ↔ `127.0.0.1`). On non-browser runtimes (SSR, vitest) + * fall back to the explicit `127.0.0.1` since `window` doesn't exist. + */ +export function resolveDatingApiBase(port: number = DATING_API_DEFAULT_PORT): string { + if (typeof window !== 'undefined' && window.location?.hostname) { + const protocol = window.location.protocol === 'https:' ? 'https:' : 'http:'; + return `${protocol}//${window.location.hostname}:${port}`; + } + return `http://127.0.0.1:${port}`; +} + +/** @deprecated Use `resolveDatingApiBase()`. Kept for callers that import the literal. */ +export const DATING_API_DEFAULT_BASE = `http://127.0.0.1:${DATING_API_DEFAULT_PORT}`; export interface DatingApiClientOptions { /** Base URL of the standalone dating server. Defaults to `DATING_API_DEFAULT_BASE`. */ @@ -160,7 +187,7 @@ export interface DatingApiClient { export function createDatingApiClient( options: DatingApiClientOptions = {} ): DatingApiClient { - const base = options.base ?? DATING_API_DEFAULT_BASE; + const base = options.base ?? resolveDatingApiBase(); const doFetch = options.fetch ?? globalThis.fetch.bind(globalThis); function safeJson(text: string): unknown {