import * as Sentry from '@sentry/nextjs' import { LOCAL_STORAGE_KEYS, PageTelemetry, posthogClient, useUser } from 'common' import { useEffect, useRef } from 'react' import { useConsentToast } from 'ui-patterns/consent' import { useOrganizationsQuery } from '@/data/organizations/organizations-query' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { API_URL, IS_PLATFORM } from '@/lib/constants' const getAnonId = async (id: string) => { const encoder = new TextEncoder() const data = encoder.encode(id) const hashBuffer = await crypto.subtle.digest('SHA-256', data) const hashArray = Array.from(new Uint8Array(hashBuffer)) const base64String = btoa(hashArray.map((byte) => String.fromCharCode(byte)).join('')) return base64String } export function Telemetry() { // Although this is "technically" breaking the rules of hooks // IS_PLATFORM never changes within a session, so this won't cause any issues // eslint-disable-next-line react-hooks/rules-of-hooks const { hasAcceptedConsent } = IS_PLATFORM ? useConsentToast() : { hasAcceptedConsent: true } // Get org from selected organization query because it's not // always available in the URL params const { data: organization } = useSelectedOrganizationQuery() const user = useUser() // Mirror the user's org-list length into a PostHog person property so feature // flags and analytics can segment by current org membership. signup_timestamp // is set on the same identify so flag audiences requiring both properties see // them together on /decide. Only fires when the value changes. const { data: organizations } = useOrganizationsQuery() const lastSentRef = useRef<{ userId: string orgCount: number signupTimestamp?: string } | null>(null) useEffect(() => { if (!user?.id || !organizations) return const orgCount = organizations.length const signupTimestamp = user.created_at ?? undefined const last = lastSentRef.current if ( last?.userId === user.id && last.orgCount === orgCount && last.signupTimestamp === signupTimestamp ) { return } lastSentRef.current = { userId: user.id, orgCount, signupTimestamp } posthogClient.identify(user.id, { org_count: orgCount, ...(signupTimestamp && { signup_timestamp: signupTimestamp }), }) }, [user?.id, user?.created_at, organizations]) useEffect(() => { // don't set the sentry user id if the user hasn't logged in (so that Sentry errors show null user id instead of anonymous id) if (!user?.id) { return } const setSentryId = async () => { let sentryUserId = localStorage.getItem(LOCAL_STORAGE_KEYS.SENTRY_USER_ID) if (!sentryUserId) { sentryUserId = await getAnonId(user?.id) localStorage.setItem(LOCAL_STORAGE_KEYS.SENTRY_USER_ID, sentryUserId) } Sentry.setUser({ id: sentryUserId }) } // if an error happens, continue without setting a sentry id setSentryId().catch((e) => console.error(e)) }, [user?.id]) return ( ) }