Skip to content

Commit c471627

Browse files
authored
fix(posthog): replace proxy rewrite with route handler for reliable body streaming (#3187)
* fix(posthog): replace proxy rewrite with route handler for reliable body streaming * fix posthog
1 parent f5dc180 commit c471627

File tree

2 files changed

+74
-20
lines changed

2 files changed

+74
-20
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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 and strips cookies/connection headers
25+
* 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+
headers.delete('cookie')
31+
headers.delete('connection')
32+
33+
return headers
34+
}
35+
36+
async function handler(request: NextRequest) {
37+
const { url, hostname } = buildTargetUrl(request.nextUrl.pathname, request.nextUrl.search)
38+
const headers = buildHeaders(request, hostname)
39+
40+
const hasBody = !['GET', 'HEAD'].includes(request.method)
41+
42+
try {
43+
const response = await fetch(url, {
44+
method: request.method,
45+
headers,
46+
...(hasBody ? { body: request.body, duplex: 'half' } : {}),
47+
} as RequestInit)
48+
49+
const responseHeaders = new Headers(response.headers)
50+
responseHeaders.delete('content-encoding')
51+
responseHeaders.delete('content-length')
52+
responseHeaders.delete('transfer-encoding')
53+
54+
return new NextResponse(response.body, {
55+
status: response.status,
56+
headers: responseHeaders,
57+
})
58+
} catch (error) {
59+
logger.error('PostHog proxy error', {
60+
url,
61+
method: request.method,
62+
error: error instanceof Error ? error.message : String(error),
63+
})
64+
return new NextResponse(null, { status: 502 })
65+
}
66+
}
67+
68+
export const GET = handler
69+
export const POST = handler
70+
export const PUT = handler
71+
export const PATCH = handler
72+
export const DELETE = handler
73+
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)