Mimic iOS Feeld app on auth + version bump to 9.4.3

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>
This commit is contained in:
Trey T
2026-06-01 18:40:21 -05:00
parent da2bab21e5
commit 39cf3f2a74
3 changed files with 79 additions and 18 deletions
+32 -6
View File
@@ -37,10 +37,14 @@ export default defineConfig({
// 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');
}
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');
});
},
},
@@ -49,6 +53,28 @@ export default defineConfig({
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',
@@ -84,10 +110,10 @@ export default defineConfig({
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('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', '8.11.0');
proxyReq.setHeader('x-app-version', '9.4.3');
proxyReq.setHeader('x-device-os', 'ios');
proxyReq.setHeader('x-os-version', '26.2.1');
});