mirror of
https://github.com/hanzoai/insights-js.git
synced 2026-10-30 18:34:19 +00:00
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
168 lines
5.9 KiB
TypeScript
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 }),
|
|
})
|
|
})
|
|
})
|