♻️(frontend) encapsulate PostHog capture calls in the telemetry module

Move the remaining direct `posthog.capture` calls behind the
telemetry module, so PostHog is only referenced from a single place.

Call sites now use the telemetry API instead of touching PostHog
directly, making it easier to swap the backend later without
changing every call site.
This commit is contained in:
lebaudantoine
2026-08-07 16:10:05 +02:00
committed by aleb_the_flash
parent 48c0cb320e
commit fb3ee56702
10 changed files with 45 additions and 127 deletions
+1
View File
@@ -11,6 +11,7 @@ and this project adheres to
### Changed
- ♻️(frontend) encapsulate error tracking behind a telemetry module
- ♻️(frontend) encapsulate PostHog capture calls in the telemetry module
## [1.25.2] - 2026-08-06
@@ -1,5 +1,21 @@
import { getPosthog } from './utils'
export const captureEvent = (
event: string,
props?: Record<string, unknown>
) => {
void getPosthog()
.then((ph) => {
ph.capture(event, props)
})
.catch(() => {
/* telemetry must never break the app */
})
if (import.meta.env.DEV) {
console.warn(`[telemetry] ${event}`, props)
}
}
export type LogCode =
// media
| 'join_preview_failure'
@@ -16,7 +16,6 @@ import {
notifyRecordingSaveInProgress,
useNotifyParticipants,
} from '@/features/notifications'
import posthog from 'posthog-js'
import { useConfig } from '@/api/useConfig'
import { NoAccessView } from './NoAccessView'
import { ControlsButton } from './ControlsButton'
@@ -29,7 +28,7 @@ import { useSidePanel } from '@/features/rooms/livekit/hooks/useSidePanel'
import { useIsAdminOrOwner } from '@/features/rooms/livekit/hooks/useIsAdminOrOwner'
import { FeatureFlags } from '@/features/analytics/enums'
import { LimitDescription } from './LimitDescription'
import { reportError } from '@/features/analytics/telemetry'
import { captureEvent, reportError } from '@/features/analytics/telemetry'
export const ScreenRecordingSidePanel = () => {
const { data } = useConfig()
@@ -64,7 +63,7 @@ export const ScreenRecordingSidePanel = () => {
await notifyParticipants({
type: NotificationType.ScreenRecordingRequested,
})
posthog.capture('screen-recording-requested', {})
captureEvent('screen-recording-requested', {})
}
const handleScreenRecording = async () => {
@@ -101,7 +100,7 @@ export const ScreenRecordingSidePanel = () => {
await notifyParticipants({
type: NotificationType.ScreenRecordingStarted,
})
posthog.capture('screen-recording-started', {
captureEvent('screen-recording-started', {
includeTranscript: includeTranscript,
language: selectedLanguageKey,
})
@@ -17,7 +17,6 @@ import {
useNotifyParticipants,
notifyRecordingSaveInProgress,
} from '@/features/notifications'
import posthog from 'posthog-js'
import { useConfig } from '@/api/useConfig'
import { VStack } from '@/styled-system/jsx'
import { Checkbox } from '@/primitives/Checkbox.tsx'
@@ -35,7 +34,7 @@ import { useSidePanel } from '@/features/rooms/livekit/hooks/useSidePanel'
import { useIsAdminOrOwner } from '@/features/rooms/livekit/hooks/useIsAdminOrOwner'
import { LimitDescription } from './LimitDescription'
import { openSettingsDialog } from '@/stores/settings'
import { reportError } from '@/features/analytics/telemetry'
import { captureEvent, reportError } from '@/features/analytics/telemetry'
export const TranscriptSidePanel = () => {
const { data } = useConfig()
@@ -77,7 +76,7 @@ export const TranscriptSidePanel = () => {
await notifyParticipants({
type: NotificationType.TranscriptionRequested,
})
posthog.capture('transcript-requested', {})
captureEvent('transcript-requested', {})
}
const handleTranscript = async () => {
@@ -122,7 +121,7 @@ export const TranscriptSidePanel = () => {
await notifyParticipants({
type: NotificationType.TranscriptionStarted,
})
posthog.capture('transcript-started', {
captureEvent('transcript-started', {
includeScreenRecording: includeScreenRecording,
language: selectedLanguageKey,
})
@@ -26,8 +26,7 @@ import { css } from '@/styled-system/css'
import { BackgroundProcessorFactory } from '../livekit/components/blur'
import { LocalUserChoices } from '@/stores/userChoices'
import { MediaDeviceErrorAlert } from './MediaDeviceErrorAlert'
import { reportError } from '@/features/analytics/telemetry'
import { usePostHog } from 'posthog-js/react'
import { captureEvent, reportError } from '@/features/analytics/telemetry'
import { useConfig } from '@/api/useConfig'
import { isFireFox } from '@/utils/livekit'
import { useIsMobile } from '@/utils/useIsMobile'
@@ -48,7 +47,6 @@ export const Conference = ({
mode?: 'join' | 'create'
initialRoomData?: ApiRoom
}) => {
const posthog = usePostHog()
const { data: apiConfig } = useConfig()
const { userChoices: userConfig } = usePersistentUserChoices() as {
@@ -58,8 +56,8 @@ export const Conference = ({
const { username } = useSnapshot(userStore)
useEffect(() => {
posthog.capture('visit-room', { slug: roomId })
}, [roomId, posthog])
captureEvent('visit-room', { slug: roomId })
}, [roomId])
const fetchKey = [keys.room, roomId]
const [isConnectionWarmedUp, setIsConnectionWarmedUp] = useState(false)
@@ -1,13 +1,12 @@
import { Button, H, Input, Text, TextArea } from '@/primitives'
import { Button, H, Text, TextArea } from '@/primitives'
import { useEffect, useMemo, useState } from 'react'
import { cva } from '@/styled-system/css'
import { useTranslation } from 'react-i18next'
import { styled, VStack } from '@/styled-system/jsx'
import { usePostHog } from 'posthog-js/react'
import type { PostHog } from 'posthog-js'
import { Button as RACButton } from 'react-aria-components'
import { useIsAnalyticsEnabled } from '@/features/analytics/hooks/useIsAnalyticsEnabled'
import type { CandidateInfo } from '@/stores/connectionObserver'
import { captureEvent } from '@/features/analytics/telemetry'
const Card = styled('div', {
base: {
@@ -72,11 +71,9 @@ const labelRecipe = cva({
})
const OpenFeedback = ({
posthog,
onNext,
metadata,
}: {
posthog: PostHog
onNext: () => void
metadata?: Record<string, unknown>
}) => {
@@ -90,7 +87,7 @@ const OpenFeedback = ({
const onSubmit = () => {
try {
posthog.capture('open-feedback', {
captureEvent('open-feedback', {
feedback,
...metadata,
})
@@ -141,12 +138,10 @@ const OpenFeedback = ({
}
const RateQuality = ({
posthog,
onNext,
metadata,
maxRating = 5,
}: {
posthog: PostHog
onNext: () => void
metadata?: Record<string, unknown>
maxRating?: number
@@ -160,7 +155,7 @@ const RateQuality = ({
const onSubmit = () => {
try {
posthog.capture('quality-rating', {
captureEvent('quality-rating', {
rating: selectedRating,
...metadata,
})
@@ -243,67 +238,6 @@ const ConfirmationMessage = ({ onNext }: { onNext: () => void }) => {
)
}
const AuthenticationMessage = ({
onNext,
posthog,
}: {
onNext: () => void
posthog: PostHog
}) => {
const { t } = useTranslation('rooms', { keyPrefix: 'authenticationMessage' })
const [email, setEmail] = useState('')
const onSubmit = () => {
posthog.people.set({ unsafe_email: email })
onNext()
}
return (
<Card
style={{
maxWidth: '380px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
}}
>
<H lvl={3}>{t('heading')}</H>
<Input
id="emailInput"
name="email"
placeholder={t('placeholder')}
required
value={email}
onChange={(e) => setEmail(e.target.value)}
style={{
marginBottom: '1rem',
}}
/>
<VStack gap="0.5">
<Button
variant="primary"
size="sm"
fullWidth
isDisabled={!email}
onPress={onSubmit}
>
{t('submit')}
</Button>
<Button
invisible
variant="secondary"
size="sm"
fullWidth
onPress={onNext}
>
{t('ignore')}
</Button>
</VStack>
</Card>
)
}
type RatingMetadata = {
room_id?: string
pc_publisher?: CandidateInfo
@@ -318,12 +252,6 @@ export const Rating = ({
metadata: RatingMetadata
}) => {
const isAnalyticsEnabled = useIsAnalyticsEnabled()
const posthog = usePostHog()
const isUserAnonymous = useMemo(() => {
return posthog.get_property('$user_state') == 'anonymous'
}, [posthog])
const [step, setStep] = useState(0)
const sessionId = useMemo(() => crypto.randomUUID(), [])
@@ -339,37 +267,14 @@ export const Rating = ({
if (!isAnalyticsEnabled) return
if (step == 0) {
return (
<RateQuality
posthog={posthog}
onNext={() => setStep(step + 1)}
metadata={metadata}
/>
)
return <RateQuality onNext={() => setStep(step + 1)} metadata={metadata} />
}
if (step == 1) {
return (
<OpenFeedback
posthog={posthog}
onNext={() => setStep(step + 1)}
metadata={metadata}
/>
)
return <OpenFeedback onNext={() => setStep(step + 1)} metadata={metadata} />
}
if (step == 2) {
return isUserAnonymous ? (
<AuthenticationMessage
posthog={posthog}
onNext={() => setStep(step + 1)}
/>
) : (
<ConfirmationMessage onNext={() => setStep(0)} />
)
}
if (step == 3) {
return <ConfirmationMessage onNext={() => setStep(0)} />
}
}
@@ -11,10 +11,10 @@ import { DisconnectReason, RoomEvent } from 'livekit-client'
import { userPreferencesStore } from '@/stores/userPreferences'
import { connectionObserverStore } from '@/stores/connectionObserver'
import posthog from 'posthog-js'
import { useFeatureFlagEnabled } from 'posthog-js/react'
import { isMobileBrowser } from '@livekit/components-core'
import { FeatureFlags } from '@/features/analytics/enums'
import { captureEvent } from '@/features/analytics/telemetry'
const CANDIDATE_POLL_INTERVAL_MS = 5000
@@ -182,23 +182,23 @@ export const ConnectionObserver = () => {
// total session duration from first connect to final disconnect.
if (connectionStartTimeRef.current != null) return
connectionStartTimeRef.current = Date.now()
posthog.capture('connection-event')
captureEvent('connection-event')
}
const handleReconnect = () => {
posthog.capture('reconnect-event')
captureEvent('reconnect-event')
}
const handleReconnected = () => {
posthog.capture('reconnected-event')
captureEvent('reconnected-event')
}
const handleSignalingConnect = () => {
posthog.capture('signaling-connect-event')
captureEvent('signaling-connect-event')
}
const handleSignalingReconnect = () => {
posthog.capture('signaling-reconnect-event')
captureEvent('signaling-reconnect-event')
}
const handleDisconnect = (
@@ -206,7 +206,7 @@ export const ConnectionObserver = () => {
) => {
const connectionEndTime = Date.now()
posthog.capture('disconnect-event', {
captureEvent('disconnect-event', {
// Calculate total session duration from first connection to final disconnect
// This duration is sensitive to refreshing the page.
sessionDuration: connectionStartTimeRef.current
@@ -1,5 +1,4 @@
import type { ProcessorOptions, Track } from 'livekit-client'
import posthog from 'posthog-js'
import {
FilesetResolver,
ImageSegmenter,
@@ -18,6 +17,7 @@ import {
type ProcessorType,
MEDIAPIPE_PATH_WASM,
} from '.'
import { captureEvent } from '@/features/analytics/telemetry.ts'
const PROCESSING_WIDTH = 256
const PROCESSING_HEIGHT = 144
@@ -100,7 +100,7 @@ export class BackgroundCustomProcessor implements BackgroundProcessorInterface {
await this.initSegmenter()
this._initWorker()
posthog.capture('firefox-blurring-init')
captureEvent('firefox-blurring-init', {})
}
_initVirtualBackgroundImage() {
@@ -1,5 +1,4 @@
import type { ProcessorOptions, Track, TrackProcessor } from 'livekit-client'
import posthog from 'posthog-js'
import {
FilesetResolver,
FaceLandmarker,
@@ -16,6 +15,7 @@ import {
ProcessorType,
MEDIAPIPE_PATH_WASM,
} from '.'
import { captureEvent } from '@/features/analytics/telemetry'
const PROCESSING_WIDTH = 256 * 3
const PROCESSING_HEIGHT = 144 * 3
@@ -101,7 +101,7 @@ export class FaceLandmarksProcessor implements TrackProcessor<Track.Kind> {
await this.initFaceLandmarker()
this._initWorker()
posthog.capture('face-landmarks-init')
captureEvent('face-landmarks-init', {})
}
_initWorker() {
@@ -11,7 +11,6 @@ import { useTranslation } from 'react-i18next'
import { SoundTester } from '@/components/SoundTester'
import { ActiveSpeaker } from '@/features/rooms/components/ActiveSpeaker'
import { useNoiseReductionAvailable } from '@/features/rooms/livekit/hooks/useNoiseReductionAvailable'
import posthog from 'posthog-js'
import { RowWrapper } from './layout/RowWrapper'
import { useSnapshot } from 'valtio'
import {
@@ -20,6 +19,7 @@ import {
saveNoiseReductionEnabled,
userChoicesStore,
} from '@/stores/userChoices'
import { captureEvent } from '@/features/analytics/telemetry'
export type AudioTabProps = Pick<DialogProps, 'onOpenChange'> &
Pick<TabPanelProps, 'id'>
@@ -128,7 +128,7 @@ export const AudioTab = ({ id }: AudioTabProps) => {
isSelected={noiseReductionEnabled}
onChange={(v) => {
saveNoiseReductionEnabled(v)
if (v) posthog.capture('noise-reduction-init')
if (v) captureEvent('noise-reduction-init')
}}
>
{t('audio.noiseReduction.label')}