Files
supabase/packages/common/telemetry.tsx
Charis 30b6af4ff3 fix (docs): non-platform integrations page (#35285)
There were two bugs when trying to run the integrations page locally
with NEXT_PUBLIC_IS_PLATFORM=false:

1. The IS_PLATFORM check imported from common was not evaluating
correctly to a boolean. This is because I slapped a 'use client' on the
entire common package last year -_-""" which caused all its imports to
be evaluated to functions when used in server components. I have now
moved the 'use client's down to the submodules that actually need it.

2. When the integrations submenu is empty, the navigation menu errors
out because it expects all navigation items to either have children or
have links. Have updated this to gracefully hide empty headers.
2025-04-25 15:22:06 -04:00

221 lines
5.8 KiB
TypeScript

'use client'
import { components } from 'api-types'
import { useRouter } from 'next/compat/router'
import { usePathname } from 'next/navigation'
import { useCallback, useEffect, useRef } from 'react'
import { LOCAL_STORAGE_KEYS } from './constants'
import { useFeatureFlags } from './feature-flags'
import { post } from './fetchWrappers'
import { ensurePlatformSuffix, isBrowser } from './helpers'
import { useTelemetryCookie } from './hooks'
import { TelemetryEvent } from './telemetry-constants'
import { useUser } from './auth'
import { getSharedTelemetryData } from './telemetry-utils'
//---
// PAGE TELEMETRY
//---
export function handlePageTelemetry(
API_URL: string,
pathname?: string,
featureFlags?: {
[key: string]: unknown
},
telemetryDataOverride?: components['schemas']['TelemetryPageBodyV2Dto']
) {
return post(
`${ensurePlatformSuffix(API_URL)}/telemetry/page`,
telemetryDataOverride !== undefined
? { feature_flags: featureFlags, ...telemetryDataOverride }
: {
...getSharedTelemetryData(pathname),
feature_flags: featureFlags,
},
{ headers: { Version: '2' } }
)
}
export function handlePageLeaveTelemetry(
API_URL: string,
pathname: string,
featureFlags?: {
[key: string]: unknown
}
) {
return post(`${ensurePlatformSuffix(API_URL)}/telemetry/page-leave`, {
body: {
pathname,
page_url: isBrowser ? window.location.href : '',
page_title: isBrowser ? document?.title : '',
feature_flags: featureFlags,
},
})
}
export const PageTelemetry = ({
API_URL,
hasAcceptedConsent,
enabled = true,
}: {
API_URL: string
hasAcceptedConsent: boolean
enabled?: boolean
}) => {
const router = useRouter()
const pagesPathname = router?.pathname
const appPathname = usePathname()
const featureFlags = useFeatureFlags()
const title = typeof document !== 'undefined' ? document?.title : ''
const referrer = typeof document !== 'undefined' ? document?.referrer : ''
useTelemetryCookie({ hasAcceptedConsent, title, referrer })
const sendPageTelemetry = useCallback(() => {
if (!(enabled && hasAcceptedConsent)) return Promise.resolve()
return handlePageTelemetry(
API_URL,
pagesPathname ?? appPathname ?? undefined,
featureFlags.posthog
).catch((e) => {
console.error('Problem sending telemetry page:', e)
})
}, [API_URL, pagesPathname, appPathname, hasAcceptedConsent, featureFlags.posthog])
const sendPageLeaveTelemetry = useCallback(() => {
if (!(enabled && hasAcceptedConsent)) return Promise.resolve()
return handlePageTelemetry(
API_URL,
pagesPathname ?? appPathname ?? undefined,
featureFlags.posthog
).catch((e) => {
console.error('Problem sending telemetry page-leave:', e)
})
}, [pagesPathname, appPathname, hasAcceptedConsent, featureFlags.posthog])
const hasSentInitialPageTelemetryRef = useRef(false)
useEffect(() => {
// Send page telemetry on first page load
// Waiting for router ready before sending page_view
// if not the path will be dynamic route instead of the browser url
if (
(router?.isReady ?? true) &&
featureFlags.hasLoaded &&
!hasSentInitialPageTelemetryRef.current
) {
sendPageTelemetry()
hasSentInitialPageTelemetryRef.current = true
}
}, [router?.isReady, featureFlags.hasLoaded])
useEffect(() => {
// For pages router
if (router === null) return
function handleRouteChange() {
sendPageTelemetry()
}
// Listen for page changes after a navigation or when the query changes
router.events.on('routeChangeComplete', handleRouteChange)
return () => {
router.events.off('routeChangeComplete', handleRouteChange)
}
}, [router])
useEffect(() => {
// For app router
if (router !== null) return
if (appPathname) {
sendPageTelemetry()
}
}, [appPathname, router, sendPageTelemetry])
useEffect(() => {
if (!enabled) return
const handleBeforeUnload = () => sendPageLeaveTelemetry()
window.addEventListener('beforeunload', handleBeforeUnload)
return () => window.removeEventListener('beforeunload', handleBeforeUnload)
}, [enabled, sendPageLeaveTelemetry])
// Identify the user
useTelemetryIdentify(API_URL)
return null
}
// ---
// EVENT TELEMETRY
// ---
type EventBody = components['schemas']['TelemetryEventBodyV2Dto']
export function sendTelemetryEvent(API_URL: string, event: TelemetryEvent, pathname?: string) {
const consent =
(typeof window !== 'undefined'
? localStorage.getItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT)
: null) === 'true'
if (!consent) return
const body: EventBody = {
...getSharedTelemetryData(pathname),
action: event.action,
custom_properties: 'properties' in event ? event.properties : {},
groups: 'groups' in event ? event.groups : {},
}
return post(`${ensurePlatformSuffix(API_URL)}/telemetry/event`, body, {
headers: { Version: '2' },
})
}
//---
// TELEMETRY IDENTIFY
//---
type IdentifyBody = components['schemas']['TelemetryIdentifyBodyV2']
export function sendTelemetryIdentify(API_URL: string, body: IdentifyBody) {
const consent =
(typeof window !== 'undefined'
? localStorage.getItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT)
: null) === 'true'
if (!consent) return Promise.resolve()
return post(`${ensurePlatformSuffix(API_URL)}/telemetry/identify`, body, {
headers: { Version: '2' },
})
}
export function useTelemetryIdentify(API_URL: string) {
const user = useUser()
useEffect(() => {
if (user?.id) {
sendTelemetryIdentify(API_URL, {
user_id: user.id,
})
}
}, [API_URL, user?.id])
}
//---
// TELEMETRY RESET
//---
export function handleResetTelemetry(API_URL: string) {
return post(`${API_URL}/telemetry/reset`, {})
}