From 274f5b3b53cbe208949a4905e9aa648f0f22b34b Mon Sep 17 00:00:00 2001 From: vikingowl Date: Tue, 4 Nov 2025 21:34:26 +0100 Subject: [PATCH] fix: Configure Vite proxy to eliminate CORS issues in development MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented a comprehensive CORS proxy solution that works with both local and remote backends during development. ## Changes ### Vite Configuration (vite.config.ts) - Use loadEnv() to properly read VITE_API_BASE_URL from .env - Configure proxy to forward /api/* requests to backend - Add detailed logging for proxy requests and responses - Support changeOrigin, rewrite, secure=false, and websockets ### API Client (src/lib/api/client.ts) - In development: Always use /api prefix (proxied) - In production: Use direct VITE_API_BASE_URL - Add console logging to show proxy configuration in dev mode - Automatic detection of environment (DEV vs PROD) ### Error Handling (route loaders) - Fix console.error() calls that caused TypeError with circular refs - Use error.message instead of logging full error objects - Affects: +page.ts, matches/+page.ts ### Documentation - docs/LOCAL_DEVELOPMENT.md: Complete rewrite with proxy explanation - Quick start guide for both production API and local backend - Detailed proxy flow diagrams - Comprehensive troubleshooting section - Clear examples and logs - docs/CORS_PROXY.md: Technical deep-dive on proxy implementation - How the proxy works internally - Configuration options explained - Testing procedures - Common issues and solutions - .env.example: Updated with proxy documentation ## How It Works Development Flow: 1. Frontend makes request: /api/matches 2. Vite proxy intercepts and forwards to: ${VITE_API_BASE_URL}/matches 3. Backend responds (no CORS headers needed) 4. Proxy returns response to frontend (same-origin) Production Flow: 1. Frontend makes request directly to: https://api.csgow.tf/matches 2. Backend responds with CORS headers 3. Browser allows request (CORS enabled on backend) ## Benefits ✅ No CORS errors in development ✅ Works with local backend (localhost:8000) ✅ Works with remote backend (api.csgow.tf) ✅ Simple configuration (just set VITE_API_BASE_URL) ✅ Detailed logging for debugging ✅ Production build unaffected (direct requests) ## Testing Verified with production API: - curl https://api.csgow.tf/matches ✓ - Dev server proxy logs show successful forwarding ✓ - Browser Network tab shows /api/* requests ✓ - No CORS errors in console ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .env.example | 5 +- docs/CORS_PROXY.md | 234 ++++++++++++++++++++++++++ docs/LOCAL_DEVELOPMENT.md | 318 ++++++++++++++++++++++++++++++++++++ src/lib/api/client.ts | 23 ++- src/routes/+page.ts | 2 +- src/routes/matches/+page.ts | 2 +- vite.config.ts | 87 +++++++--- 7 files changed, 636 insertions(+), 35 deletions(-) create mode 100644 docs/CORS_PROXY.md create mode 100644 docs/LOCAL_DEVELOPMENT.md diff --git a/.env.example b/.env.example index ff47f5c..5e070ac 100644 --- a/.env.example +++ b/.env.example @@ -7,8 +7,9 @@ # ============================================ # Backend API Base URL -# Default: http://localhost:8000 (local development) -# Production: https://api.csgow.tf or your backend URL +# Development: Vite proxy forwards /api to this URL (default: http://localhost:8000) +# Production: Set to your actual backend URL (e.g., https://api.csgow.tf) +# Note: In development, the frontend uses /api and Vite proxies to this URL VITE_API_BASE_URL=http://localhost:8000 # API request timeout in milliseconds diff --git a/docs/CORS_PROXY.md b/docs/CORS_PROXY.md new file mode 100644 index 0000000..949fe3a --- /dev/null +++ b/docs/CORS_PROXY.md @@ -0,0 +1,234 @@ +# CORS Proxy Configuration + +This document explains how the CORS proxy works in the CS2.WTF frontend. + +## Problem: CORS in Development + +When developing a frontend that talks to an API on a different origin, browsers enforce CORS (Cross-Origin Resource Sharing) policies. This causes errors like: + +``` +Access to fetch at 'https://api.csgow.tf/matches' from origin 'http://localhost:5173' +has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present +on the requested resource. +``` + +## Solution: Vite Development Proxy + +The Vite dev server includes a built-in proxy that solves this problem by making all API requests appear same-origin. + +### Configuration + +**File**: `vite.config.ts` + +```typescript +import { loadEnv } from 'vite'; + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd(), ''); + const apiBaseUrl = env.VITE_API_BASE_URL || 'http://localhost:8000'; + + return { + server: { + proxy: { + '/api': { + target: apiBaseUrl, + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, ''), + secure: false, + ws: true + } + } + } + }; +}); +``` + +### How It Works + +1. **API Client** (in development) makes requests to `/api/*`: + ```typescript + // src/lib/api/client.ts + const API_BASE_URL = import.meta.env.DEV ? '/api' : VITE_API_BASE_URL; + ``` + +2. **Vite Proxy** intercepts requests to `/api/*` and forwards them: + ``` + Browser Request: GET http://localhost:5173/api/matches?limit=6 + ↓ + Vite Proxy: Intercepts /api/* requests + ↓ + Backend Request: GET https://api.csgow.tf/matches?limit=6 + ↓ + Response: ← Returns data through proxy + ↓ + Browser: ← Receives response (appears same-origin) + ``` + +3. **Browser sees same-origin request** - no CORS error! + +### Configuration Options + +| Option | Value | Purpose | +|--------|-------|---------| +| `target` | `env.VITE_API_BASE_URL` | Where to forward requests | +| `changeOrigin` | `true` | Updates `Origin` header to match target | +| `rewrite` | Remove `/api` prefix | Maps `/api/matches` → `/matches` | +| `secure` | `false` | Allow self-signed certificates | +| `ws` | `true` | Enable WebSocket proxying | + +### Environment Variables + +**`.env`**: +```env +# Proxy will forward /api/* to this URL +VITE_API_BASE_URL=https://api.csgow.tf + +# Or use local backend +# VITE_API_BASE_URL=http://localhost:8000 +``` + +### Logging + +The proxy logs all requests for debugging: + +```bash +[Vite Config] API Proxy target: https://api.csgow.tf +[Proxy] GET /api/matches?limit=6 -> https://api.csgow.tf/matches?limit=6 +[Proxy ✓] GET /api/matches?limit=6 -> 200 +[Proxy] GET /api/match/123 -> https://api.csgow.tf/match/123 +[Proxy ✓] GET /api/match/123 -> 200 +``` + +Error logging: +```bash +[Proxy Error] ECONNREFUSED +[Proxy Error] Make sure backend is running at: http://localhost:8000 +``` + +## API Client Configuration + +**File**: `src/lib/api/client.ts` + +```typescript +const getAPIBaseURL = (): string => { + // In production builds, use the configured URL directly + if (import.meta.env.PROD) { + return import.meta.env?.VITE_API_BASE_URL || 'https://api.csgow.tf'; + } + + // In development mode, ALWAYS use the Vite proxy to avoid CORS issues + // The proxy will forward /api requests to VITE_API_BASE_URL + return '/api'; +}; +``` + +This ensures: +- ✅ **Development**: Always uses `/api` (proxy handles CORS) +- ✅ **Production**: Uses direct URL (backend has CORS enabled) + +## Testing the Proxy + +### 1. Check Vite Config Loads Environment + +Start dev server and look for: +```bash +npm run dev + +# Should show: +[Vite Config] API Proxy target: https://api.csgow.tf +``` + +### 2. Check API Client Configuration + +Open browser console, look for: +``` +[API Client] Development mode - using Vite proxy +[API Client] Frontend requests: /api/* +[API Client] Proxy target: https://api.csgow.tf +``` + +### 3. Check Network Requests + +Open DevTools → Network tab: +- ✅ Requests should go to `/api/*` (not full URL) +- ✅ Response should be `200 OK` +- ✅ No CORS errors in console + +### 4. Check Proxy Logs + +Terminal should show: +``` +[Proxy] GET /api/matches -> https://api.csgow.tf/matches +[Proxy ✓] GET /api/matches -> 200 +``` + +## Common Issues + +### Issue 1: Proxy Not Loading .env + +**Symptom**: Proxy uses default `http://localhost:8000` instead of `.env` value + +**Cause**: `vite.config.ts` not loading environment variables + +**Fix**: Use `loadEnv()` in config: +```typescript +import { loadEnv } from 'vite'; + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd(), ''); + const apiBaseUrl = env.VITE_API_BASE_URL || 'http://localhost:8000'; + // ... +}); +``` + +### Issue 2: Still Getting CORS Errors + +**Symptom**: Browser console shows CORS errors + +**Possible Causes**: +1. API client not using `/api` prefix in development +2. Request bypassing proxy somehow +3. Running production build instead of dev server + +**Fix**: +1. Check API client logs show: `Development mode - using Vite proxy` +2. Verify Network tab shows requests to `/api/*` +3. Run `npm run dev` (not `npm run preview`) + +### Issue 3: Connection Refused + +**Symptom**: `[Proxy Error] ECONNREFUSED` + +**Cause**: Backend is not running at the configured URL + +**Fix**: +- If using local backend: Start `csgowtfd` on port 8000 +- If using production API: Check `VITE_API_BASE_URL=https://api.csgow.tf` + +## Production Build + +In production, the proxy is **not used**. The frontend makes direct requests to the backend: + +```typescript +// Production build +const API_BASE_URL = 'https://api.csgow.tf'; + +// Direct request (no proxy) +fetch('https://api.csgow.tf/matches'); +``` + +The production API must have CORS enabled: +``` +Access-Control-Allow-Origin: https://cs2.wtf +Access-Control-Allow-Methods: GET, POST, OPTIONS +Access-Control-Allow-Headers: Content-Type, Authorization +``` + +## Summary + +| Environment | Frontend URL | API Requests | CORS | +|-------------|-------------|--------------|------| +| **Development** | `http://localhost:5173` | `/api/*` → Proxy → Backend | ✅ Proxy handles | +| **Production** | `https://cs2.wtf` | Direct to backend | ✅ Backend CORS | + +The proxy is a **development-only** feature that makes local development smooth and eliminates CORS headaches. diff --git a/docs/LOCAL_DEVELOPMENT.md b/docs/LOCAL_DEVELOPMENT.md new file mode 100644 index 0000000..a8eb71a --- /dev/null +++ b/docs/LOCAL_DEVELOPMENT.md @@ -0,0 +1,318 @@ +# Local Development Setup + +This guide will help you set up the CS2.WTF frontend for local development. + +## Prerequisites + +- **Node.js**: v18.x or v20.x (check with `node --version`) +- **npm**: v9.x or higher (comes with Node.js) +- **Backend API**: Either local csgowtfd service OR access to production API + +## Quick Start + +### 1. Install Dependencies + +```bash +npm install +``` + +### 2. Environment Configuration + +The `.env` file already exists in the project. You can use it as-is or modify it: + +**Option A: Use Production API** (Recommended for frontend development) +```env +# Use the live production API - no local backend needed +VITE_API_BASE_URL=https://api.csgow.tf +VITE_API_TIMEOUT=10000 +VITE_DEBUG_MODE=true +VITE_ENABLE_ANALYTICS=false +``` + +**Option B: Use Local Backend** (For full-stack development) +```env +# Use local backend (requires csgowtfd running on port 8000) +VITE_API_BASE_URL=http://localhost:8000 +VITE_API_TIMEOUT=10000 +VITE_DEBUG_MODE=true +VITE_ENABLE_ANALYTICS=false +``` + +### 3. Start the Development Server + +```bash +npm run dev +``` + +The frontend will be available at `http://localhost:5173` + +You should see output like: +``` +[Vite Config] API Proxy target: https://api.csgow.tf +[API Client] Development mode - using Vite proxy +[API Client] Frontend requests: /api/* +[API Client] Proxy target: https://api.csgow.tf + + ➜ Local: http://localhost:5173/ +``` + +### 4. (Optional) Start Local Backend + +Only needed if using `VITE_API_BASE_URL=http://localhost:8000`: + +```bash +# In the csgowtfd repository +cd ../csgowtfd +go run cmd/csgowtfd/main.go +``` + +Or use Docker: +```bash +docker-compose up csgowtfd +``` + +## How the CORS Proxy Works + +The Vite dev server includes a **built-in proxy** that eliminates CORS issues during development: + +### Development Mode Flow +``` +1. Browser makes request to: http://localhost:5173/api/matches +2. Vite intercepts and proxies to: ${VITE_API_BASE_URL}/matches +3. Backend responds +4. Vite forwards response to browser +``` + +### Benefits +- ✅ **No CORS errors** - All requests appear same-origin to the browser +- ✅ **Works with any backend** - Local or remote +- ✅ **No backend CORS config needed** - Proxy handles it +- ✅ **Simple configuration** - Just set `VITE_API_BASE_URL` + +### Proxy Logs + +You'll see detailed proxy activity in the terminal: + +```bash +[Proxy] GET /api/matches?limit=6 -> https://api.csgow.tf/matches?limit=6 +[Proxy ✓] GET /api/matches?limit=6 -> 200 +[Proxy] GET /api/match/123 -> https://api.csgow.tf/match/123 +[Proxy ✓] GET /api/match/123 -> 200 +``` + +### Production vs Development + +| Mode | API Base URL | CORS | +|------|-------------|------| +| **Development** (`npm run dev`) | `/api` (proxied to `VITE_API_BASE_URL`) | ✅ No issues | +| **Production** (`npm run build`) | `VITE_API_BASE_URL` (direct) | ✅ Backend has CORS enabled | + +## Troubleshooting + +### No Data Showing / Network Errors + +**Problem**: Frontend loads but shows no matches, players show "Failed to load" errors. + +**Solutions**: + +1. **Check what backend you're using**: + ```bash + # Look at your .env file + cat .env | grep VITE_API_BASE_URL + ``` + +2. **If using production API** (`https://api.csgow.tf`): + ```bash + # Test if production API is accessible + curl https://api.csgow.tf/matches?limit=1 + ``` + Should return JSON data. If not, production API may be down. + +3. **If using local backend** (`http://localhost:8000`): + ```bash + # Test if local backend is running + curl http://localhost:8000/matches?limit=1 + ``` + If you get "Connection refused", start the backend service. + +4. **Check proxy logs**: + - Look at the terminal running `npm run dev` + - You should see `[Proxy]` messages showing requests being forwarded + - If you see `[Proxy Error]`, check the error message + +5. **Check browser console**: + - Open DevTools → Console tab + - Look for `[API Client]` messages showing proxy configuration + - Network tab should show requests to `/api/*` (not external URLs) + +6. **Restart dev server**: + ```bash + # Stop dev server (Ctrl+C) + npm run dev + ``` + +### CORS Errors (Should Not Happen) + +If you see CORS errors in the browser console, the proxy isn't working: + +**Symptoms**: +- Browser console shows: `CORS policy: No 'Access-Control-Allow-Origin' header` +- Network tab shows requests going to `https://api.csgow.tf` directly (not `/api`) + +**Fix**: +1. Verify you're in development mode (not production build) +2. Check API client logs show: `Development mode - using Vite proxy` +3. Restart dev server with clean cache: + ```bash + rm -rf .svelte-kit + npm run dev + ``` + +### Port Already in Use + +If port 5173 is already in use: + +```bash +# Vite will automatically try the next available port +npm run dev + +# Or specify a custom port +npm run dev -- --port 3000 +``` + +### Backend Connection Issues + +If the backend is on a different host/port, update `.env`: + +```env +# Custom backend location +VITE_API_BASE_URL=http://192.168.1.100:8080 +``` + +Then restart the dev server. + +## Development Workflow + +### 1. Make Changes + +Edit files in `src/`. The dev server has hot module replacement (HMR): +- Component changes reload instantly +- Route changes reload the page +- Store changes reload affected components + +### 2. Type Checking + +Run TypeScript type checking: + +```bash +npm run check # Check once +npm run check:watch # Watch mode +``` + +### 3. Linting + +```bash +npm run lint # Check for issues +npm run lint:fix # Auto-fix issues +npm run format # Run Prettier +``` + +### 4. Testing + +```bash +# Unit tests +npm run test # Run once +npm run test:watch # Watch mode +npm run test:coverage # Generate coverage report + +# E2E tests +npm run test:e2e # Headless +npm run test:e2e:ui # Playwright UI +``` + +## API Endpoints + +The backend provides these endpoints (see `docs/API.md` for full details): + +- `GET /matches` - List all matches +- `GET /match/:id` - Get match details +- `GET /match/:id/rounds` - Get round economy data +- `GET /match/:id/weapons` - Get weapon statistics +- `GET /match/:id/chat` - Get chat messages +- `GET /player/:id` - Get player profile + +### How Requests Work + +**In Development** (`npm run dev`): +``` +Frontend code: api.matches.getMatches() + ↓ +API Client: GET /api/matches + ↓ +Vite Proxy: GET https://api.csgow.tf/matches + ↓ +Response: ← Data returned to frontend +``` + +**In Production** (deployed app): +``` +Frontend code: api.matches.getMatches() + ↓ +API Client: GET https://api.csgow.tf/matches (direct) + ↓ +Response: ← Data returned to frontend +``` + +The API client automatically uses the correct URL based on environment. + +## Mock Data (Alternative: No Backend) + +If you want to develop without any backend (local or production), enable MSW mocking: + +1. Update `.env`: + ```env + VITE_ENABLE_MSW_MOCKING=true + ``` + +2. Restart dev server + +The app will use mock data from `src/mocks/handlers/`. + +**Note**: Mock data is limited and may not reflect all features. **Production API is recommended** for most development work. + +## Building for Production + +```bash +# Build +npm run build + +# Preview production build locally +npm run preview +``` + +The preview server runs on `http://localhost:4173` and uses the production API configuration. + +## Environment Variables Reference + +| Variable | Default | Description | +|----------|---------|-------------| +| `VITE_API_BASE_URL` | `http://localhost:8000` | Backend API base URL | +| `VITE_API_TIMEOUT` | `10000` | Request timeout (ms) | +| `VITE_ENABLE_LIVE_MATCHES` | `false` | Enable live match polling | +| `VITE_ENABLE_ANALYTICS` | `false` | Enable analytics tracking | +| `VITE_DEBUG_MODE` | `false` | Enable debug logging | +| `VITE_ENABLE_MSW_MOCKING` | `false` | Use mock data instead of API | + +## Getting Help + +- **Frontend Issues**: Check browser console for errors +- **API Issues**: Check backend logs and proxy output in terminal +- **Type Errors**: Run `npm run check` for detailed messages +- **Build Issues**: Delete `.svelte-kit/` and `node_modules/`, then `npm install` + +## Next Steps + +- Read `TODO.md` for current development status +- Check `docs/DESIGN.md` for design system documentation +- Review `docs/API.md` for complete API reference +- See `README.md` for project overview diff --git a/src/lib/api/client.ts b/src/lib/api/client.ts index b735293..e01cb1f 100644 --- a/src/lib/api/client.ts +++ b/src/lib/api/client.ts @@ -5,13 +5,28 @@ import { APIException } from '$lib/types'; /** * API Client Configuration */ -const API_BASE_URL = - typeof window !== 'undefined' - ? import.meta.env?.VITE_API_BASE_URL || 'http://localhost:8000' - : 'http://localhost:8000'; +const getAPIBaseURL = (): string => { + // In production builds, use the configured URL directly + if (import.meta.env.PROD) { + return import.meta.env?.VITE_API_BASE_URL || 'https://api.csgow.tf'; + } + // In development mode, ALWAYS use the Vite proxy to avoid CORS issues + // The proxy will forward /api requests to VITE_API_BASE_URL + // This works regardless of whether the backend is local or remote + return '/api'; +}; + +const API_BASE_URL = getAPIBaseURL(); const API_TIMEOUT = Number(import.meta.env?.VITE_API_TIMEOUT) || 10000; +// Log the API configuration in development +if (import.meta.env.DEV) { + console.log('[API Client] Development mode - using Vite proxy'); + console.log('[API Client] Frontend requests: /api/*'); + console.log('[API Client] Proxy target:', import.meta.env?.VITE_API_BASE_URL || 'http://localhost:8000'); +} + /** * Base API Client * Provides centralized HTTP communication with error handling diff --git a/src/routes/+page.ts b/src/routes/+page.ts index 9f2282a..394caba 100644 --- a/src/routes/+page.ts +++ b/src/routes/+page.ts @@ -23,7 +23,7 @@ export const load: PageLoad = async ({ parent }) => { }; } catch (error) { // Log error but don't fail the page load - console.error('Failed to load featured matches:', error); + console.error('Failed to load featured matches:', error instanceof Error ? error.message : String(error)); // Return empty data - page will show without featured matches return { diff --git a/src/routes/matches/+page.ts b/src/routes/matches/+page.ts index 2523985..341c017 100644 --- a/src/routes/matches/+page.ts +++ b/src/routes/matches/+page.ts @@ -33,7 +33,7 @@ export const load: PageLoad = async ({ url }) => { } }; } catch (error) { - console.error('Failed to load matches:', error); + console.error('Failed to load matches:', error instanceof Error ? error.message : String(error)); // Return empty state on error return { diff --git a/vite.config.ts b/vite.config.ts index ae41044..69dac76 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,32 +1,65 @@ import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vitest/config'; +import { loadEnv } from 'vite'; -export default defineConfig({ - plugins: [sveltekit()], - test: { - include: ['src/**/*.{test,spec}.{js,ts}'], - globals: true, - environment: 'jsdom', - setupFiles: ['./src/tests/setup.ts'], - coverage: { - provider: 'v8', - reporter: ['text', 'json', 'html'], - exclude: [ - 'node_modules/', - 'src/tests/', - '**/*.d.ts', - '**/*.config.*', - '**/mockData', - '**/.svelte-kit' - ] +export default defineConfig(({ mode }) => { + // Load env file based on `mode` in the current working directory. + // Set the third parameter to '' to load all env regardless of the `VITE_` prefix. + const env = loadEnv(mode, process.cwd(), ''); + const apiBaseUrl = env.VITE_API_BASE_URL || 'http://localhost:8000'; + + console.log('[Vite Config] API Proxy target:', apiBaseUrl); + + return { + plugins: [sveltekit()], + test: { + include: ['src/**/*.{test,spec}.{js,ts}'], + globals: true, + environment: 'jsdom', + setupFiles: ['./src/tests/setup.ts'], + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + exclude: [ + 'node_modules/', + 'src/tests/', + '**/*.d.ts', + '**/*.config.*', + '**/mockData', + '**/.svelte-kit' + ] + } + }, + server: { + port: 5173, + strictPort: false, + proxy: { + // Proxy API requests to backend to avoid CORS issues in development + // All requests to /api/* are forwarded to the backend + '/api': { + target: apiBaseUrl, + changeOrigin: true, // Updates the Origin header to match the target + rewrite: (path) => path.replace(/^\/api/, ''), // Remove /api prefix + secure: false, // Allow self-signed certificates + ws: true, // Proxy websockets + configure: (proxy, options) => { + proxy.on('error', (err, _req, _res) => { + console.error('[Proxy Error]', err.message); + console.error('[Proxy Error] Make sure backend is running at:', apiBaseUrl); + }); + proxy.on('proxyReq', (proxyReq, req, _res) => { + console.log(`[Proxy] ${req.method} ${req.url} -> ${apiBaseUrl}${proxyReq.path}`); + }); + proxy.on('proxyRes', (proxyRes, req, _res) => { + console.log(`[Proxy ✓] ${req.method} ${req.url} -> ${proxyRes.statusCode}`); + }); + } + } + } + }, + preview: { + port: 4173, + strictPort: false } - }, - server: { - port: 5173, - strictPort: false - }, - preview: { - port: 4173, - strictPort: false - } + }; });