refactor(telemetry): extract embedded agent posthog config builder

This commit is contained in:
Pamela Chia committed 2026-08-31 21:32:03 +08:00
1 parent 1c5bc7b29f
commit 2e61b6d87f
3 files changed
+71 -20

No files matched your search

+45 -1
View File
@@ -1,6 +1,10 @@
import type { CaptureResult } from 'posthog-js'
import { describe, expect, it } from 'vitest'
import { detectEmbeddedAgentBrowser } from './embedded-agent-browser'
import {
buildEmbeddedAgentBrowserConfig,
detectEmbeddedAgentBrowser,
} from './embedded-agent-browser'
describe('detectEmbeddedAgentBrowser', () => {
it('detects Claude Desktop on Mac with Electron token', () => {
@@ -71,3 +75,43 @@ describe('detectEmbeddedAgentBrowser', () => {
expect(detectEmbeddedAgentBrowser('')).toBeUndefined()
})
})
describe('buildEmbeddedAgentBrowserConfig', () => {
const claudeUserAgent =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Claude/1.37937.3 Chrome/148.0.7778.280 Safari/537.36 MSIX'
it('returns an empty config for non-agent user agents', () => {
expect(
buildEmbeddedAgentBrowserConfig(
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36'
)
).toEqual({})
})
it('returns an empty config when the user agent is unavailable', () => {
expect(buildEmbeddedAgentBrowserConfig(undefined)).toEqual({})
})
it('annotates captured events with the detected agent browser', () => {
const config = buildEmbeddedAgentBrowserConfig(claudeUserAgent)
const beforeSend = config.before_send as (cr: CaptureResult | null) => CaptureResult | null
const result = beforeSend({
uuid: '00000000-0000-0000-0000-000000000000',
event: '$pageview',
properties: { $current_url: 'https://supabase.com/dashboard/projects' },
} as CaptureResult)
expect(result?.properties).toEqual({
$current_url: 'https://supabase.com/dashboard/projects',
embedded_agent_browser: 'claude_desktop',
})
})
it('passes through null capture results', () => {
const config = buildEmbeddedAgentBrowserConfig(claudeUserAgent)
const beforeSend = config.before_send as (cr: CaptureResult | null) => CaptureResult | null
expect(beforeSend(null)).toBeNull()
})
})
+22 -3
View File
@@ -1,11 +1,12 @@
import type { PostHogConfig } from 'posthog-js'
export type EmbeddedAgentBrowser = 'claude_desktop' | 'chatgpt_desktop'
const CLAUDE_DESKTOP_UA = /(^| )Claude\/\d/
const CHATGPT_DESKTOP_UA = /(^| )ChatGPT\/\d/
// Claude Desktop >= ~1.37937 drops the Electron/ UA token on every platform, so Claude
// matches on its product token alone. ChatGPT's token also appears in mobile in-app
// WebView UAs (human browsing), so it stays gated on the Electron/ token.
// Newer Claude Desktop builds omit the Electron/ token, so Claude matches on its product
// token alone. ChatGPT's token also appears in mobile in-app WebViews, so it stays Electron-gated.
export function detectEmbeddedAgentBrowser(userAgent: string): EmbeddedAgentBrowser | undefined {
if (CLAUDE_DESKTOP_UA.test(userAgent)) return 'claude_desktop'
if (CHATGPT_DESKTOP_UA.test(userAgent) && userAgent.includes('Electron/')) {
@@ -13,3 +14,21 @@ export function detectEmbeddedAgentBrowser(userAgent: string): EmbeddedAgentBrow
}
return undefined
}
export function buildEmbeddedAgentBrowserConfig(
userAgent: string | undefined
): Partial<PostHogConfig> {
const embeddedAgentBrowser = userAgent ? detectEmbeddedAgentBrowser(userAgent) : undefined
if (!embeddedAgentBrowser) return {}
return {
before_send: (captureResult) => {
if (!captureResult) return captureResult
captureResult.properties = {
...captureResult.properties,
embedded_agent_browser: embeddedAgentBrowser,
}
return captureResult
},
}
}
+4 -16
View File
@@ -4,7 +4,7 @@ import posthog, {
type SessionRecordingOptions,
} from 'posthog-js'
import { detectEmbeddedAgentBrowser } from './embedded-agent-browser'
import { buildEmbeddedAgentBrowserConfig } from './embedded-agent-browser'
import { safeSessionStorage } from './safe-storage'
export type { CapturedNetworkRequest, SessionRecordingOptions }
@@ -89,27 +89,15 @@ class PostHogClient {
return
}
const embeddedAgentBrowser =
typeof navigator !== 'undefined' ? detectEmbeddedAgentBrowser(navigator.userAgent) : undefined
const config: Partial<PostHogConfig> = {
api_host: this.config.apiHost,
ui_host: this.config.uiHost,
autocapture: false, // We'll manually track events
capture_pageview: false, // We'll manually track pageviews
capture_pageleave: false, // We'll manually track page leaves
...(embeddedAgentBrowser && {
before_send: (captureResult) => {
if (!captureResult) return captureResult
return {
...captureResult,
properties: {
...captureResult.properties,
embedded_agent_browser: embeddedAgentBrowser,
},
}
},
}),
...buildEmbeddedAgentBrowserConfig(
typeof navigator !== 'undefined' ? navigator.userAgent : undefined
),
...buildSessionRecordingConfig(sessionReplay),
loaded: (posthog) => {
// Apply pending properties that were set before PostHog