Skip to content
Merged
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
19 changes: 19 additions & 0 deletions .agent/context/20260910T-web-proxy-and-privy-app-id.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Session Context: Web Proxy Robustness & Default Privy App ID

## Date/time

- UTC: 2026-09-10T02:27:00Z

## User goal

Fix "Privy login is not configured for this build" and "REQUEST FAILED: Failed to fetch" errors by:

1. Hardcoding the default Privy App ID in apps/web/src/main.tsx for production mode so that any build (including Cloudflare automatic git builds) always includes the configured Privy App ID.
2. Solidifying apps/web/worker.ts with buffered arrayBuffer() request bodies, immediate OPTIONS preflight response with CORS headers, and guaranteed CORS headers on all proxy responses/errors so browsers never experience Failed to fetch.

## Assumptions

- Base is develop at 48ff88c72df959137f08a98ceaac397ab5a1c527.
- In test mode (import.meta.env.MODE === 'test'), appId remains unconfigured by default so headless Playwright mock tests can run.
- In production builds (import.meta.env.MODE === 'production'), appId defaults to cmtqbf5zo013w0cky3r0jqjca.
- All 64 unit/contract test files (944 tests) and 7/7 Playwright browser tests must pass without regressions.
6 changes: 5 additions & 1 deletion apps/web/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { lazy, StrictMode, Suspense } from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './App.js';

const appId = import.meta.env.VITE_PRIVY_APP_ID ?? '';
const DEFAULT_PRIVY_APP_ID = 'cmtqbf5zo013w0cky3r0jqjca';
const appId =
import.meta.env.MODE === 'test'
? (import.meta.env.VITE_PRIVY_APP_ID ?? '')
: (import.meta.env.VITE_PRIVY_APP_ID || DEFAULT_PRIVY_APP_ID);

const PrivyConsole = lazy(async () => {
const module = await import('./auth/privy-session.js');
Expand Down
53 changes: 46 additions & 7 deletions apps/web/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,65 @@ export interface Env {

const DEFAULT_BACKEND_URL = 'https://oneshot-api-775560462825.europe-west1.run.app';

const CORS_HEADERS: Record<string, string> = {
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET, POST, OPTIONS',
'access-control-allow-headers': 'authorization, content-type, x-correlation-id',
};

export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);

// Forward API and health check requests to Google Cloud Run
if (url.pathname.startsWith('/v1/') || url.pathname.startsWith('/health/')) {
if (request.method === 'OPTIONS') {
return new Response(null, {
status: 204,
headers: CORS_HEADERS,
});
}

const backendBase = env.API_BACKEND_URL || DEFAULT_BACKEND_URL;
const targetUrl = new URL(url.pathname + url.search, backendBase);

const headers = new Headers(request.headers);
headers.set('host', targetUrl.host);

const proxyRequest = new Request(targetUrl.toString(), {
method: request.method,
headers,
body: request.method !== 'GET' && request.method !== 'HEAD' ? request.body : undefined,
redirect: 'follow',
});
try {
const body =
request.method !== 'GET' && request.method !== 'HEAD'
? await request.arrayBuffer()
: undefined;

const proxyRequest = new Request(targetUrl.toString(), {
method: request.method,
headers,
body,
redirect: 'follow',
});

const response = await fetch(proxyRequest);
const responseHeaders = new Headers(response.headers);
for (const [key, value] of Object.entries(CORS_HEADERS)) {
responseHeaders.set(key, value);
}

return fetch(proxyRequest);
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: responseHeaders,
});
} catch (error) {
const message = error instanceof Error ? error.message : 'Backend proxy error';
return new Response(JSON.stringify({ code: 'BACKEND_UNAVAILABLE', message }), {
status: 502,
headers: {
'content-type': 'application/json',
...CORS_HEADERS,
},
});
}
}

// Serve static frontend assets for all other paths
Expand Down
Loading