mirror of
https://github.com/supabase/supabase.git
synced 2026-10-08 02:45:07 +03:00
refactor(telemetry): extract embedded agent posthog config builder
This commit is contained in:
1 parent
1c5bc7b29f
commit
2e61b6d87f
3 files changed
+71
-20
No files matched your search
@@ -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()
|
||||
})
|
||||
})
|
||||
@@ -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,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
|
||||
|
||||
Reference in new issue
Block a user