39cf3f2a74
Captured a real iOS Feeld token-refresh request — our outbound headers were unmistakably "not the iOS app." Aligning so requests fingerprint identically. - APP_VERSION 8.11.0 → 9.4.3 in constants.ts, server/index.js, vite.config.ts - Bundle id corrected to com.3nder.threender (was com.3nder.ios) - REQUEST_HEADERS User-Agent now the realistic Alamofire iOS UA, not 'feeld-mobile' - server/index.js refreshAccessToken now sends the full Firebase iOS header set (FirebaseAuth.iOS UA, X-Client-Version, X-Firebase-AppCheck fallback, X-Firebase-GMPID, X-Ios-Bundle-Identifier) and uses camelCase body keys. Response parsing accepts both camelCase and snake_case for resilience. - vite proxy /api/firebase now applies the same iOS headers in dev mode - vite proxy /api/graphql strips browser sec-* fingerprint headers and sets the realistic Alamofire UA unconditionally (was a conditional 'feeld-mobile') Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
170 lines
6.6 KiB
TypeScript
Executable File
170 lines
6.6 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');
|
|
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,
|
|
},
|
|
},
|
|
},
|
|
})
|