Files
supabase/apps/studio/components/ui/GroupsTelemetry.tsx
Ivan VasilovandAlaister Young 37e44bec9c feat: Add GTM (#35567)
* Add third-parties dependency for GTM. Reexport the GTM from the common package.

* Add the TelemetryTagManager to four of the production apps.

* Add the GOOGLE_TAG_MANAGER_ID env var as a turbo dependency to the 4 apps.

* Skip rendering the tag manager if the env var is not set or not running on the platform.

* Fix the prop type to be extracted from the component.

* Add default values for consent to GTM.

* Another try to mimic gtag function.

* Fix a link in www.

* Try another approach.

* try.

* Remove the data-redaction flag.

* Remove extra code.

* Send a sign-up event if GTM is enabled.

* Send only the email to GTM.

* Minor fixes.

* Remove third-parties from pnpm lockfile.

* Lets try to make studio work again.

* Add CSP rules for img loading for GTM.

* Add event for testing.

* Add www.googletagmanager.com to the CSP rules.

* Add Stape to CSP rules.

* Fix stape CSP.

* Clean up the code.

* Remove extra console.log.

* Fix the stape urls for CORS.

* Fix the wrong category for Stape URL.

* Add google ads urls.

* Bump the timeout.

* Add google.com to the img-src for google ads.

* update csp

* remove comment

* update to use google analytics without signals

* add stape to default-src in csp

* move csp to middleware

* add google ads support and fix middleware base path

* remove google tag manager / google ads references from csp. load via stape proxy instead

* add double click url to image src

* add Google Tag Manager URL to CSP configuration

* add ga4 urls to csp

* remove google urls from CSP

---------

Co-authored-by: Alaister Young <a@alaisteryoung.com>
2025-06-30 10:35:47 +00:00

96 lines
3.8 KiB
TypeScript

/* eslint-disable react-hooks/rules-of-hooks */
import * as Sentry from '@sentry/nextjs'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { LOCAL_STORAGE_KEYS, useParams, useTelemetryCookie, useUser } from 'common'
import { useSendGroupsIdentifyMutation } from 'data/telemetry/send-groups-identify-mutation'
import { useSendGroupsResetMutation } from 'data/telemetry/send-groups-reset-mutation'
import { usePrevious } from 'hooks/deprecated'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { 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
}
const GroupsTelemetry = ({ hasAcceptedConsent }: { hasAcceptedConsent: boolean }) => {
// Although this is "technically" breaking the rules of hooks
// IS_PLATFORM never changes within a session, so this won't cause any issues
if (!IS_PLATFORM) return null
const user = useUser()
const router = useRouter()
const { ref, slug } = useParams()
const organization = useSelectedOrganization()
const previousPathname = usePrevious(router.pathname)
const { mutate: sendGroupsIdentify } = useSendGroupsIdentifyMutation()
const { mutate: sendGroupsReset } = useSendGroupsResetMutation()
const title = typeof document !== 'undefined' ? document?.title : ''
const referrer = typeof document !== 'undefined' ? document?.referrer : ''
useTelemetryCookie({ hasAcceptedConsent, title, referrer })
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])
useEffect(() => {
const isLandingOnProjectRoute =
router.pathname.includes('[ref]') && previousPathname === router.pathname
const isEnteringProjectRoute =
!(previousPathname ?? '').includes('[ref]') && router.pathname.includes('[ref]')
const isLeavingProjectRoute =
(previousPathname ?? '').includes('[ref]') && !router.pathname.includes('[ref]')
const isLandingOnOrgRoute =
router.pathname.includes('[slug]') && previousPathname === router.pathname
const isEnteringOrgRoute =
!(previousPathname ?? '').includes('[slug]') && router.pathname.includes('[slug]')
const isLeavingOrgRoute =
(previousPathname ?? '').includes('[slug]') && !router.pathname.includes('[slug]')
if (hasAcceptedConsent) {
if (ref && (isLandingOnProjectRoute || isEnteringProjectRoute)) {
sendGroupsIdentify({ organization_slug: organization?.slug, project_ref: ref as string })
} else if (slug && (isLandingOnOrgRoute || isEnteringOrgRoute)) {
sendGroupsIdentify({ organization_slug: slug, project_ref: undefined })
} else if (isLeavingProjectRoute || isLeavingOrgRoute) {
sendGroupsReset({
reset_organization: isLeavingOrgRoute || isLeavingProjectRoute,
reset_project: isLeavingProjectRoute,
})
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hasAcceptedConsent, slug, ref, router.pathname])
return null
}
export default GroupsTelemetry