Spaces:
Sleeping
Sleeping
Fix PostHog proxy: use Node.js stream piping instead of fetch
Browse filesPrevious fetch-based approach had body re-encoding issues causing 405.
Stream piping forwards request bytes verbatim — preserving PostHog's
gzip/base64 compression and all headers without transformation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ui/api/ph/[...path].js +27 -41
- ui/src/main.jsx +2 -3
ui/api/ph/[...path].js
CHANGED
|
@@ -1,56 +1,42 @@
|
|
| 1 |
/**
|
| 2 |
* Vercel Serverless Function — PostHog reverse proxy.
|
| 3 |
*
|
| 4 |
-
*
|
| 5 |
-
*
|
| 6 |
-
* requests look first-party, bypassing the block.
|
| 7 |
*
|
| 8 |
-
*
|
| 9 |
-
*
|
| 10 |
-
*
|
| 11 |
*/
|
| 12 |
-
|
| 13 |
|
| 14 |
-
export default
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
const query = req.url.includes('?') ? req.url.slice(req.url.indexOf('?')) : '';
|
| 19 |
-
const targetUrl = `${POSTHOG_HOST}/${pathStr}${query}`;
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
const bodyChunks = [];
|
| 24 |
-
for await (const chunk of req) {
|
| 25 |
-
bodyChunks.push(chunk);
|
| 26 |
-
}
|
| 27 |
-
const rawBody = bodyChunks.length > 0 ? Buffer.concat(bodyChunks) : undefined;
|
| 28 |
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
| 34 |
};
|
| 35 |
-
if (req.headers['content-encoding']) {
|
| 36 |
-
forwardedHeaders['content-encoding'] = req.headers['content-encoding'];
|
| 37 |
-
}
|
| 38 |
|
| 39 |
-
const
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
body: ['GET', 'HEAD'].includes(req.method) ? undefined : rawBody,
|
| 43 |
});
|
| 44 |
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
proxyResp.headers.forEach((value, key) => {
|
| 48 |
-
// Skip headers that Node's http module manages itself
|
| 49 |
-
if (!['transfer-encoding', 'connection'].includes(key)) {
|
| 50 |
-
res.setHeader(key, value);
|
| 51 |
-
}
|
| 52 |
});
|
| 53 |
|
| 54 |
-
|
| 55 |
-
|
| 56 |
}
|
|
|
|
| 1 |
/**
|
| 2 |
* Vercel Serverless Function — PostHog reverse proxy.
|
| 3 |
*
|
| 4 |
+
* Ad blockers match on "us.i.posthog.com" and drop requests. Routing through
|
| 5 |
+
* our own Vercel domain (/api/ph/*) makes them look first-party.
|
|
|
|
| 6 |
*
|
| 7 |
+
* Uses Node.js stream piping (req.pipe → proxyRes.pipe → res) so the body
|
| 8 |
+
* is forwarded byte-for-byte with no parsing — preserving PostHog's gzip/base64
|
| 9 |
+
* compression without any re-encoding issues.
|
| 10 |
*/
|
| 11 |
+
import https from 'https';
|
| 12 |
|
| 13 |
+
export default function handler(req, res) {
|
| 14 |
+
const segments = Array.isArray(req.query.path)
|
| 15 |
+
? req.query.path.join('/')
|
| 16 |
+
: (req.query.path || '');
|
|
|
|
|
|
|
| 17 |
|
| 18 |
+
const query = req.url.includes('?') ? req.url.slice(req.url.indexOf('?')) : '';
|
| 19 |
+
const path = `/${segments}${query}`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
+
const options = {
|
| 22 |
+
hostname: 'us.i.posthog.com',
|
| 23 |
+
path,
|
| 24 |
+
method: req.method,
|
| 25 |
+
headers: {
|
| 26 |
+
...req.headers,
|
| 27 |
+
host: 'us.i.posthog.com',
|
| 28 |
+
},
|
| 29 |
};
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
+
const proxyReq = https.request(options, (proxyRes) => {
|
| 32 |
+
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
| 33 |
+
proxyRes.pipe(res);
|
|
|
|
| 34 |
});
|
| 35 |
|
| 36 |
+
proxyReq.on('error', (err) => {
|
| 37 |
+
res.status(500).json({ error: err.message });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
});
|
| 39 |
|
| 40 |
+
// Pipe the incoming body directly — no buffering, no re-encoding
|
| 41 |
+
req.pipe(proxyReq);
|
| 42 |
}
|
ui/src/main.jsx
CHANGED
|
@@ -6,9 +6,8 @@ import './index.css'
|
|
| 6 |
import App from './App.jsx'
|
| 7 |
|
| 8 |
posthog.init('phc_B4VarKaWfNc3u7vMcsUPRDbNgSyVxaBqtYT3ZwP6FshM', {
|
| 9 |
-
// Route through Vercel
|
| 10 |
-
// to us.i.posthog.com
|
| 11 |
-
// Route through Vercel Edge Function (/api/ph/*) — bypasses ad blockers
|
| 12 |
api_host: '/api/ph',
|
| 13 |
ui_host: 'https://us.posthog.com',
|
| 14 |
capture_pageview: true,
|
|
|
|
| 6 |
import App from './App.jsx'
|
| 7 |
|
| 8 |
posthog.init('phc_B4VarKaWfNc3u7vMcsUPRDbNgSyVxaBqtYT3ZwP6FshM', {
|
| 9 |
+
// Route through /api/ph (Vercel serverless proxy) so ad blockers
|
| 10 |
+
// don't block requests going directly to us.i.posthog.com.
|
|
|
|
| 11 |
api_host: '/api/ph',
|
| 12 |
ui_host: 'https://us.posthog.com',
|
| 13 |
capture_pageview: true,
|