Files
zeekay ec6f6b50c1 chore: kill ALL posthog — zero refs, zero filenames (1452 -> 0)
We own the ingestion server (hanzoai/insights: Django + rust/capture + nodejs
plugin) AND this SDK, so there is no external wire contract to preserve. The
server is already 100% posthog-free; this SDK was the last laggard.

- $last_posthog_reset -> $last_insights_reset. This FIXES A LIVE MISMATCH: the
  server taxonomy (insights/taxonomy/taxonomy.py, insights/insightsql/ai.py)
  already expected the insights form while the SDK emitted the posthog form.
- __PosthogExtensions__ -> __InsightsExtensions__; all PostHog/posthog/POSTHOG
  identifiers -> Insights/insights/INSIGHTS (src, tests, playground, playwright,
  scripts, rrweb subpackages, package.json keywords).
- Hosts: DEFAULT_PLUGIN_HOST + DEFAULT_NUXT_HOST + the core host default pointed
  at PostHog's cloud (us.i.posthog.com) — a real bug. All -> insights.hanzo.ai.
- Removed the us-assets/eu-assets CDN split in getRemoteConfig (PostHog-specific;
  Hanzo serves remote config from the same host) — it had degenerated to a no-op.
- Fixed generate-docs writing to a stray references/@hanzo/ subdir.
- Renamed the 3 posthog-named test files; dropped 58MB of stale reference
  snapshots named for upstream posthog versions we never shipped (they are
  generated output — references/ is now gitignored).

Verified: typecheck errors 42 (HEAD) -> 18 (all pre-existing test-mock
looseness); types package 0; every package.json valid with @hanzo/* names.
Guard: grep -rioE 'posthog' packages ... | wc -l  => 0
2026-07-16 09:09:18 -07:00

168 lines
5.9 KiB
TypeScript

import { BrowserContext, Page, Route } from '@playwright/test'
import { expect, test } from './utils/insights-playwright-test-base'
import { start, waitForSessionRecordingToStart } from './utils/setup'
const POST_URL = '/__insights_fetch_post_body_test'
const POST_BODY = 'some string'
const ORIGINAL_HEADER = 'original-header-value'
async function mockPostEndpoint(context: BrowserContext) {
let capturedRequest: { headers: Record<string, string>; body: string | null } | undefined
await context.route(`**${POST_URL}`, async (route: Route) => {
capturedRequest = {
headers: route.request().headers(),
body: route.request().postData(),
}
await route.fulfill({ status: 200, contentType: 'text/plain', body: 'ok' })
})
return () => capturedRequest
}
async function postStringBody(page: Page) {
return page.evaluate(
async ({
postUrl,
postBody,
originalHeader,
}: {
postUrl: string
postBody: string
originalHeader: string
}) => {
try {
const response = await fetch(postUrl, {
method: 'POST',
body: postBody,
headers: { 'x-original-header': originalHeader },
})
return {
ok: true,
status: response.status,
text: await response.text(),
shape: (window as any).__fetchShape,
}
} catch (e) {
return {
ok: false,
name: (e as Error).name,
message: (e as Error).message,
shape: (window as any).__fetchShape,
}
}
},
{ postUrl: POST_URL, postBody: POST_BODY, originalHeader: ORIGINAL_HEADER }
)
}
async function installRequestBodyForwardingFetchWrapper(page: Page) {
await page.addInitScript(() => {
const nativeFetch = window.fetch
window.fetch = (url, init) => {
;(window as any).__fetchShape = {
isRequest: url instanceof Request,
hasInit: init !== undefined,
bodyType: url instanceof Request ? Object.prototype.toString.call(url.body) : undefined,
}
// This mimics fetch wrappers/interceptors that rebuild init from a Request.
// If Insights passes a newly-created Request downstream, this forwards request.body
// as a ReadableStream and WebKit throws "ReadableStream uploading is not supported".
if (url instanceof Request) {
return nativeFetch(url, { ...init, body: url.body })
}
return nativeFetch(url, init)
}
})
}
// Older Safari/WebKit versions can throw `NotSupportedError: ReadableStream uploading is not supported`
// when a wrapper turns a string body POST into a Request/ReadableStream upload.
test.describe('fetch wrappers preserve POST string bodies', () => {
test('does not expose a POST string body as a Request ReadableStream when tracing headers are enabled', async ({
page,
context,
}) => {
const getCapturedRequest = await mockPostEndpoint(context)
await installRequestBodyForwardingFetchWrapper(page)
await start(
{
options: { __add_tracing_headers: ['localhost'] },
url: '/playground/cypress/index.html',
},
page,
context
)
const result = await postStringBody(page)
expect(result).toEqual({
ok: true,
status: 200,
text: 'ok',
shape: expect.objectContaining({ isRequest: false, hasInit: true }),
})
expect(getCapturedRequest()).toMatchObject({
body: POST_BODY,
headers: expect.objectContaining({
'x-original-header': ORIGINAL_HEADER,
'x-insights-distinct-id': expect.any(String),
'x-insights-session-id': expect.any(String),
'x-insights-window-id': expect.any(String),
}),
})
})
test('does not expose a POST string body as a Request ReadableStream when recording request bodies', async ({
page,
context,
}) => {
const getCapturedRequest = await mockPostEndpoint(context)
await installRequestBodyForwardingFetchWrapper(page)
await page.waitingForNetworkCausedBy({
urlPatternsToWaitFor: ['**/*recorder.js*'],
action: async () => {
await start(
{
options: {
session_recording: {
compress_events: true,
recordBody: true,
},
},
flagsResponseOverrides: {
sessionRecording: { endpoint: '/ses/' },
capturePerformance: true,
autocapture_opt_out: true,
},
url: '/playground/cypress/index.html',
},
page,
context
)
},
})
await waitForSessionRecordingToStart(page)
await expect(page.evaluate(() => (window.fetch as any).__insights_wrapped__)).resolves.toBe(true)
const result = await postStringBody(page)
expect(result).toEqual({
ok: true,
status: 200,
text: 'ok',
shape: expect.objectContaining({ isRequest: false, hasInit: true }),
})
expect(getCapturedRequest()).toMatchObject({
body: POST_BODY,
headers: expect.objectContaining({ 'x-original-header': ORIGINAL_HEADER }),
})
})
})