Files
Feeld/web/vite.config.ts
T
Trey T da2bab21e5 Block banned-country locations and align GraphQL ops
Defense-in-depth banned-country gate covering every entry point that could
set a location Feeld's policy disallows (~60 countries from their support
article):

- New src/config/bannedCountries.ts — single source of truth (ISO codes + aliases)
- New src/utils/reverseGeocode.ts — Nominatim reverse lookup w/ localStorage cache
- New src/api/links/bannedCountryLink.ts — Apollo link chokepoint; intercepts
  every DeviceLocationUpdate mutation and refuses to forward if reverse-geocode
  resolves to a banned country. Catches Settings, Discover, Likes scanner, and
  ApiExplorer raw GraphQL alike.
- useLocation.tsx — setLocation throws BannedCountryError; saveLocation gate;
  sanitize banned entries on localStorage and server hydration
- Settings.tsx — block at search, saved-location pick, and save-current
- Likes.tsx — skip banned saved locations in scanForLikes and "Fuck It" scan
- server/index.js — PUT /api/saved-locations filters; readSavedLocations
  filters legacy banned entries so rotation cron is safe too
- nginx.conf — route additions for new backend endpoints

Plus the broader rc/realign-graphql-ops session work: GraphQL query/mutation
realignment after Feeld API changes, ApiExplorer updates, Profile/Discover/Likes
refinements, useFavorites hook, dataSync extensions, vite proxy adjustments.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 18:30:37 -05:00

144 lines
5.0 KiB
TypeScript
Executable File

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');
// Ensure mobile app headers are preserved
if (!proxyReq.getHeader('user-agent')?.includes('feeld')) {
proxyReq.setHeader('User-Agent', 'feeld-mobile');
}
});
},
},
'/api/firebase': {
target: 'https://securetoken.googleapis.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/firebase/, ''),
secure: false,
},
'/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/8.11.0 (com.3nder.ios; 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', '8.11.0');
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,
},
},
},
})