mirror of
https://github.com/hanzoai/insights-js.git
synced 2026-11-03 00:53:53 +00:00
* feat(react-native): support __add_tracing_headers for linking requests to sessions The browser SDK already supports injecting X-POSTHOG-DISTINCT-ID and X-POSTHOG-SESSION-ID headers on outgoing requests (used, e.g., for linking LLM traces to session replays). The React Native SDK silently ignored __add_tracing_headers because no equivalent patching existed. Patch the global fetch on init when the option is set, and add the tracing headers to requests whose hostname matches the configured list. Fixes #3196 Generated-By: PostHog Code Task-Id: 50cf1519-462f-4dc4-a8af-6d9ac94466e6 * refactor(react-native): make tracing-headers patch idempotent Detect an existing PostHog-installed fetch patch via a non-enumerable marker and unwrap it before re-wrapping, so re-initialisation (HMR, tests, or creating a second PostHog instance) replaces the previous patch instead of stacking on top. The latest instance's hostname list and distinct/session ids take effect. Also fold the symmetric matching/non-matching hostname tests into a single `it.each` and add a regression test for the idempotency fix. Generated-By: PostHog Code Task-Id: 50cf1519-462f-4dc4-a8af-6d9ac94466e6 * refactor: rename __add_tracing_headers to addTracingHeaders The `__` prefix signalled an internal/experimental option, but the config is now a public API surface (documented for linking LLM traces to session replays). Per review consensus, drop the prefix on both browser and RN SDKs. `__add_tracing_headers` is kept as a deprecated alias on both so existing users keep working. Also address review feedback on the RN tracing-headers module: - drop the dead `isArray` guard (type is already `string[] | undefined`) - narrow `input` with `instanceof Request` instead of a cast - bind a new `initWithHeaders` rather than reassigning the `init` param - document the unwrap-chain limitation on the PATCH_MARKER - replace `globalThis as any` with a typed narrow in tests - cover more branches: URL input, Request input, Headers-instance init.headers, and fetch rejection propagation Generated-By: PostHog Code Task-Id: 50cf1519-462f-4dc4-a8af-6d9ac94466e6 * refactor: move tracing-headers to @posthog/core and drop RN deprecated alias - Move tracing-headers.ts (and its tests) from packages/react-native to packages/core so other SDKs (posthog-web, future clients) can reuse the implementation. Decouple from the RN PostHog type via a minimal TracingHeadersClient interface ({ getDistinctId, getSessionId }). - Drop the `__add_tracing_headers` deprecated alias from the RN PostHogOptions. RN never shipped this option, so there's no user to be backwards-compatible with — keeping the alias would be dead code on day one. - Split the changeset into two: one for the cross-package rename (posthog-js + types + core), one for the new RN feature. Generated-By: PostHog Code Task-Id: 50cf1519-462f-4dc4-a8af-6d9ac94466e6 * refactor: move addTracingHeaders option to PostHogCoreOptions Any SDK extending PostHogCoreOptions (posthog-react-native today, posthog-web in the future) automatically picks up the config field, matching the patch function now living in @posthog/core. Generated-By: PostHog Code Task-Id: 50cf1519-462f-4dc4-a8af-6d9ac94466e6 * chore(examples): add tracing-headers screen to example-expo-53 Lets anyone running the example visually confirm that the `addTracingHeaders` fetch patch actually injects `X-POSTHOG-DISTINCT-ID` and `X-POSTHOG-SESSION-ID` on-device. The screen fires two requests — one to a listed hostname (httpbin.org, which echoes headers back) and one to an unlisted host — and renders the echoed headers, highlighting PostHog ones. Generated-By: PostHog Code Task-Id: 50cf1519-462f-4dc4-a8af-6d9ac94466e6 * chore(examples): use postman-echo for unlisted hostname case httpbin.org.com wasn't a real domain and failed DNS on-device, which looked like a bug in the example. postman-echo.com is a real working header-echo service, so the unlisted case now exercises an actual network round-trip and the demo is just about the header filter rather than a DNS failure. Generated-By: PostHog Code Task-Id: 50cf1519-462f-4dc4-a8af-6d9ac94466e6 * chore(browser): add _getConfiguredHostnames to terser mangled names New private method added when renaming __add_tracing_headers to addTracingHeaders in the TracingHeaders extension. Generated-By: PostHog Code Task-Id: 50cf1519-462f-4dc4-a8af-6d9ac94466e6
149 lines
5.4 KiB
TypeScript
149 lines
5.4 KiB
TypeScript
import { useState } from 'react'
|
|
import { Button, StyleSheet, Text, View } from 'react-native'
|
|
|
|
import ParallaxScrollView from '@/components/ParallaxScrollView'
|
|
import { ThemedText } from '@/components/ThemedText'
|
|
import { ThemedView } from '@/components/ThemedView'
|
|
import { IconSymbol } from '@/components/ui/IconSymbol'
|
|
|
|
// httpbin.org/headers echoes the request headers back in the JSON response — perfect
|
|
// for eyeballing that `addTracingHeaders` actually reaches the network layer on device.
|
|
const LISTED_URL = 'https://httpbin.org/headers'
|
|
|
|
// postman-echo.com/headers also echoes headers but is NOT in the `addTracingHeaders`
|
|
// list in app/posthog.tsx, so the PostHog headers should NOT appear on this response.
|
|
const UNLISTED_URL = 'https://postman-echo.com/headers'
|
|
|
|
type Result = {
|
|
url: string
|
|
status?: number
|
|
echoedHeaders?: Record<string, string>
|
|
error?: string
|
|
}
|
|
|
|
export default function TracingHeadersScreen() {
|
|
const [result, setResult] = useState<Result | null>(null)
|
|
const [loading, setLoading] = useState(false)
|
|
|
|
const runRequest = async (url: string): Promise<void> => {
|
|
setLoading(true)
|
|
setResult(null)
|
|
try {
|
|
const response = await fetch(url)
|
|
const body = (await response.json()) as { headers?: Record<string, string> }
|
|
setResult({ url, status: response.status, echoedHeaders: body.headers ?? {} })
|
|
} catch (error) {
|
|
setResult({ url, error: error instanceof Error ? error.message : String(error) })
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<ParallaxScrollView
|
|
headerBackgroundColor={{ light: '#D0D0D0', dark: '#353636' }}
|
|
headerImage={<IconSymbol size={310} color="#808080" name="link" style={styles.headerImage} />}
|
|
>
|
|
<ThemedView style={styles.titleContainer}>
|
|
<ThemedText type="title">Tracing Headers</ThemedText>
|
|
</ThemedView>
|
|
<ThemedText>
|
|
When `addTracingHeaders` is set on PostHog init, the SDK patches the global `fetch` to inject
|
|
`X-POSTHOG-DISTINCT-ID` and `X-POSTHOG-SESSION-ID` on requests to the configured hostnames. This is
|
|
used to link LLM traces and backend spans to the current PostHog session replay.
|
|
</ThemedText>
|
|
|
|
<ThemedView style={styles.sectionContainer}>
|
|
<ThemedText type="subtitle">Listed hostname (httpbin.org)</ThemedText>
|
|
<ThemedText>
|
|
Echoes the request headers back. You should see `X-Posthog-Distinct-Id` and
|
|
`X-Posthog-Session-Id` in the response.
|
|
</ThemedText>
|
|
<Button onPress={() => void runRequest(LISTED_URL)} title="Fetch listed hostname" />
|
|
</ThemedView>
|
|
|
|
<ThemedView style={styles.sectionContainer}>
|
|
<ThemedText type="subtitle">Unlisted hostname</ThemedText>
|
|
<ThemedText>Same endpoint on a different host. The PostHog headers should NOT be added.</ThemedText>
|
|
<Button onPress={() => void runRequest(UNLISTED_URL)} title="Fetch unlisted hostname" />
|
|
</ThemedView>
|
|
|
|
{loading && (
|
|
<View style={styles.resultBox}>
|
|
<Text>Loading…</Text>
|
|
</View>
|
|
)}
|
|
{result && !loading && (
|
|
<View style={styles.resultBox}>
|
|
<Text style={styles.resultTitle}>Request: {result.url}</Text>
|
|
{result.error ? (
|
|
<Text style={styles.resultError}>Error: {result.error}</Text>
|
|
) : (
|
|
<>
|
|
<Text style={styles.resultStatus}>Status: {result.status}</Text>
|
|
<Text style={styles.resultSubtitle}>Echoed headers:</Text>
|
|
{Object.entries(result.echoedHeaders ?? {}).map(([key, value]) => {
|
|
const isTracing = key.toLowerCase().startsWith('x-posthog-')
|
|
return (
|
|
<Text
|
|
key={key}
|
|
style={[styles.resultHeader, isTracing && styles.resultHeaderTracing]}
|
|
>
|
|
{key}: {value}
|
|
</Text>
|
|
)
|
|
})}
|
|
</>
|
|
)}
|
|
</View>
|
|
)}
|
|
</ParallaxScrollView>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
headerImage: {
|
|
color: '#808080',
|
|
bottom: -90,
|
|
left: -35,
|
|
position: 'absolute',
|
|
},
|
|
titleContainer: {
|
|
flexDirection: 'row',
|
|
gap: 8,
|
|
},
|
|
sectionContainer: {
|
|
gap: 8,
|
|
marginTop: 16,
|
|
},
|
|
resultBox: {
|
|
marginTop: 16,
|
|
padding: 12,
|
|
backgroundColor: '#f5f5f5',
|
|
borderRadius: 8,
|
|
gap: 4,
|
|
},
|
|
resultTitle: {
|
|
fontWeight: '700',
|
|
},
|
|
resultStatus: {
|
|
color: '#2e7d32',
|
|
fontWeight: '600',
|
|
},
|
|
resultSubtitle: {
|
|
marginTop: 6,
|
|
fontWeight: '600',
|
|
},
|
|
resultHeader: {
|
|
fontSize: 12,
|
|
fontFamily: 'Courier',
|
|
},
|
|
resultHeaderTracing: {
|
|
color: '#1565c0',
|
|
fontWeight: '700',
|
|
},
|
|
resultError: {
|
|
color: '#c62828',
|
|
},
|
|
})
|