import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' export default defineConfig({ plugins: [react(), tailwindcss()], server: { port: 3000, open: true, allowedHosts: true, // HMR disabled - reverse proxy (feeld.treytartt.com) doesn't forward WebSockets // Manual refresh required after code changes when using custom domain // Access http://localhost:3000 directly for HMR during development hmr: false, proxy: { '/api/okcupid': { target: 'https://e2p-okapi.api.okcupid.com', changeOrigin: true, rewrite: (path) => path.replace(/^\/api\/okcupid/, ''), secure: false, configure: (proxy) => { proxy.on('proxyReq', (proxyReq) => { proxyReq.removeHeader('origin'); proxyReq.removeHeader('referer'); proxyReq.setHeader('User-Agent', 'OkCupid/111.1.0 iOS/26.2.1'); proxyReq.setHeader('x-okcupid-locale', 'en'); }); }, }, '/api/graphql': { target: 'https://core.api.fldcore.com', changeOrigin: true, rewrite: (path) => path.replace(/^\/api\/graphql/, '/graphql'), secure: false, configure: (proxy) => { proxy.on('proxyReq', (proxyReq) => { // Remove browser headers that reveal this is a web client proxyReq.removeHeader('origin'); proxyReq.removeHeader('referer'); proxyReq.removeHeader('sec-fetch-dest'); proxyReq.removeHeader('sec-fetch-mode'); proxyReq.removeHeader('sec-fetch-site'); proxyReq.removeHeader('sec-ch-ua'); proxyReq.removeHeader('sec-ch-ua-mobile'); proxyReq.removeHeader('sec-ch-ua-platform'); // Match the real iOS app's Alamofire UA. Keep APP_VERSION in sync with constants.ts. proxyReq.setHeader('User-Agent', 'Feeld/9.4.3 (com.3nder.threender; build:1; iOS 26.2.1) Alamofire/5.9.1'); }); }, }, '/api/firebase': { target: 'https://securetoken.googleapis.com', changeOrigin: true, rewrite: (path) => path.replace(/^\/api\/firebase/, ''), secure: false, configure: (proxy) => { proxy.on('proxyReq', (proxyReq) => { // Strip browser fingerprint headers proxyReq.removeHeader('origin'); proxyReq.removeHeader('referer'); proxyReq.removeHeader('sec-fetch-dest'); proxyReq.removeHeader('sec-fetch-mode'); proxyReq.removeHeader('sec-fetch-site'); proxyReq.removeHeader('sec-ch-ua'); proxyReq.removeHeader('sec-ch-ua-mobile'); proxyReq.removeHeader('sec-ch-ua-platform'); // Mirror the headers a real iOS Feeld app sends on token refresh. proxyReq.setHeader('User-Agent', 'FirebaseAuth.iOS/11.5.0 com.3nder.threender/9.4.3 iPhone/26.2.1 hw/iPhone14_4'); proxyReq.setHeader('Accept', '*/*'); proxyReq.setHeader('Accept-Language', 'en'); proxyReq.setHeader('Accept-Encoding', 'gzip, deflate, br'); proxyReq.setHeader('X-Client-Version', 'iOS/FirebaseSDK/11.5.0/FirebaseCore-iOS'); proxyReq.setHeader('X-Firebase-AppCheck', 'eyJlcnJvciI6IlVOS05PV05fRVJST1IifQ=='); proxyReq.setHeader('X-Firebase-GMPID', '1:594152761603:ios:f52cf15efff827861b2136'); proxyReq.setHeader('X-Ios-Bundle-Identifier', 'com.3nder.threender'); }); }, }, '/api/images': { target: 'https://res.cloudinary.com', changeOrigin: true, rewrite: (path) => path.replace(/^\/api\/images/, ''), secure: false, configure: (proxy) => { proxy.on('proxyReq', (proxyReq) => { // Remove browser headers that trigger hotlink protection proxyReq.removeHeader('origin'); proxyReq.removeHeader('referer'); // Set mobile app headers proxyReq.setHeader('User-Agent', 'feeld-mobile'); proxyReq.setHeader('Accept', '*/*'); }); }, }, '/api/fldcdn': { target: 'https://prod.fldcdn.com', changeOrigin: true, rewrite: (path) => path.replace(/^\/api\/fldcdn/, ''), secure: false, configure: (proxy) => { proxy.on('proxyReq', (proxyReq) => { // Remove browser headers that trigger hotlink protection proxyReq.removeHeader('origin'); proxyReq.removeHeader('referer'); proxyReq.removeHeader('sec-fetch-dest'); proxyReq.removeHeader('sec-fetch-mode'); proxyReq.removeHeader('sec-fetch-site'); proxyReq.removeHeader('sec-ch-ua'); proxyReq.removeHeader('sec-ch-ua-mobile'); proxyReq.removeHeader('sec-ch-ua-platform'); // Set mobile app headers to match iOS app // APP_VERSION: keep in sync with src/config/constants.ts APP_VERSION proxyReq.setHeader('User-Agent', 'Feeld/9.4.3 (com.3nder.threender; build:1; iOS 26.2.1) Alamofire/5.9.1'); proxyReq.setHeader('Accept', '*/*'); proxyReq.setHeader('Accept-Language', 'en-US,en;q=0.9'); proxyReq.setHeader('x-app-version', '9.4.3'); proxyReq.setHeader('x-device-os', 'ios'); proxyReq.setHeader('x-os-version', '26.2.1'); }); }, }, // Local backend endpoints (must be last to not override specific proxies above) '/api/matches': { target: 'http://localhost:3001', changeOrigin: true, }, '/api/who-liked-you': { target: 'http://localhost:3001', changeOrigin: true, }, '/api/sent-pings': { target: 'http://localhost:3001', changeOrigin: true, }, '/api/favorites': { target: 'http://localhost:3001', changeOrigin: true, }, '/api/disliked-profiles': { target: 'http://localhost:3001', changeOrigin: true, }, '/api/discovered-profiles': { target: 'http://localhost:3001', changeOrigin: true, }, '/api/location-rotation': { target: 'http://localhost:3001', changeOrigin: true, }, '/api/saved-locations': { target: 'http://localhost:3001', changeOrigin: true, }, '/api/data': { target: 'http://localhost:3001', changeOrigin: true, }, '/api/auth': { target: 'http://localhost:3001', changeOrigin: true, }, '/api/health': { target: 'http://localhost:3001', changeOrigin: true, }, }, }, })