Skip to content

Commit edfddeb

Browse files
committed
fix(posthog): replace proxy rewrite with route handler for reliable body streaming
1 parent 78fef22 commit edfddeb

File tree

2 files changed

+79
-20
lines changed

2 files changed

+79
-20
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { createLogger } from '@sim/logger'
2+
import { type NextRequest, NextResponse } from 'next/server'
3+
4+
const logger = createLogger('PostHogProxy')
5+
6+
const API_HOST = 'us.i.posthog.com'
7+
const ASSET_HOST = 'us-assets.i.posthog.com'
8+
9+
/**
10+
* Builds the target PostHog URL from the incoming request path.
11+
* Routes /ingest/static/* to the asset host, everything else to the API host.
12+
*/
13+
function buildTargetUrl(pathname: string, search: string): { url: string; hostname: string } {
14+
const strippedPath = pathname.replace(/^\/ingest/, '')
15+
const hostname = strippedPath.startsWith('/static/') ? ASSET_HOST : API_HOST
16+
return {
17+
url: `https://${hostname}${strippedPath}${search}`,
18+
hostname,
19+
}
20+
}
21+
22+
/**
23+
* Builds forwarding headers for the PostHog request.
24+
* Sets the Host header, forwards client IP for geolocation,
25+
* and strips cookies/connection headers that shouldn't be forwarded.
26+
*/
27+
function buildHeaders(request: NextRequest, hostname: string): Headers {
28+
const headers = new Headers(request.headers)
29+
headers.set('host', hostname)
30+
31+
const forwardedFor = request.headers.get('x-forwarded-for')
32+
if (forwardedFor) {
33+
headers.set('x-forwarded-for', forwardedFor)
34+
}
35+
36+
headers.delete('cookie')
37+
headers.delete('connection')
38+
39+
return headers
40+
}
41+
42+
async function handler(request: NextRequest) {
43+
const { url, hostname } = buildTargetUrl(request.nextUrl.pathname, request.nextUrl.search)
44+
const headers = buildHeaders(request, hostname)
45+
46+
const hasBody = !['GET', 'HEAD'].includes(request.method)
47+
48+
try {
49+
const response = await fetch(url, {
50+
method: request.method,
51+
headers,
52+
...(hasBody ? { body: request.body, duplex: 'half' } : {}),
53+
} as RequestInit)
54+
55+
const responseHeaders = new Headers(response.headers)
56+
responseHeaders.delete('content-encoding')
57+
responseHeaders.delete('transfer-encoding')
58+
59+
return new NextResponse(response.body, {
60+
status: response.status,
61+
headers: responseHeaders,
62+
})
63+
} catch (error) {
64+
logger.error('PostHog proxy error', {
65+
url,
66+
method: request.method,
67+
error: error instanceof Error ? error.message : String(error),
68+
})
69+
return new NextResponse(null, { status: 502 })
70+
}
71+
}
72+
73+
export const GET = handler
74+
export const POST = handler
75+
export const PUT = handler
76+
export const PATCH = handler
77+
export const DELETE = handler
78+
export const OPTIONS = handler

apps/sim/proxy.ts

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -140,24 +140,6 @@ function handleSecurityFiltering(request: NextRequest): NextResponse | null {
140140
export async function proxy(request: NextRequest) {
141141
const url = request.nextUrl
142142

143-
if (url.pathname.startsWith('/ingest/')) {
144-
const hostname = url.pathname.startsWith('/ingest/static/')
145-
? 'us-assets.i.posthog.com'
146-
: 'us.i.posthog.com'
147-
148-
const targetPath = url.pathname.replace(/^\/ingest/, '')
149-
const targetUrl = `https://${hostname}${targetPath}${url.search}`
150-
151-
return NextResponse.rewrite(new URL(targetUrl), {
152-
request: {
153-
headers: new Headers({
154-
...Object.fromEntries(request.headers),
155-
host: hostname,
156-
}),
157-
},
158-
})
159-
}
160-
161143
const sessionCookie = getSessionCookie(request)
162144
const hasActiveSession = isAuthDisabled || !!sessionCookie
163145

@@ -219,7 +201,6 @@ export async function proxy(request: NextRequest) {
219201

220202
export const config = {
221203
matcher: [
222-
'/ingest/:path*', // PostHog proxy for session recording
223204
'/', // Root path for self-hosted redirect logic
224205
'/terms', // Whitelabel terms redirect
225206
'/privacy', // Whitelabel privacy redirect
@@ -230,6 +211,6 @@ export const config = {
230211
'/signup',
231212
'/invite/:path*', // Match invitation routes
232213
// Catch-all for other pages, excluding static assets and public directories
233-
'/((?!_next/static|_next/image|favicon.ico|logo/|static/|footer/|social/|enterprise/|favicon/|twitter/|robots.txt|sitemap.xml).*)',
214+
'/((?!_next/static|_next/image|ingest|favicon.ico|logo/|static/|footer/|social/|enterprise/|favicon/|twitter/|robots.txt|sitemap.xml).*)',
234215
],
235216
}

0 commit comments

Comments
 (0)