Files
supabase/apps/studio/lib/telemetry.ts
Ivan Vasilov 79dbfd1ccc fix: Add sourcemaps and error stacks in Sentry errors (#26474)
* Use Sentry Error boundary.

* Add a button for throwing error to test the sentry error boundary.

* trigger an error during rendering.

* Move the throwing of error.

* Fix the error boundary state.

* Try to throw an error from the project list.

* Revert to using react error boundary.

* Minor fixes.

* Add an example page for throwing an exception to be caught by Sentry. Remove the error filter from the Sentry client config.

* Remove config for allowing errors only from our code.

* When capturing an exception with Sentry, add a tag if the exception is in the boundary.

* Minor fixes.

* Remove testing code.

* Move forgotten code.

* Add a function for anonymized user id.

* Set the sentry user id by anonymizing the actual user id.

* Don't send Sentry reports if the user hasn't consented to the telemetry.

* Add a helper function whether the studio can send telemetry data.

* Reuse the helper function in the _app file.
2024-05-22 10:35:15 +00:00

148 lines
3.4 KiB
TypeScript

import { Sha256 } from '@aws-crypto/sha256-browser'
import type { NextRouter } from 'next/router'
import { post } from 'lib/common/fetch'
import { API_URL, IS_PLATFORM, LOCAL_STORAGE_KEYS } from 'lib/constants'
import type { User } from 'types'
export interface TelemetryProps {
screenResolution?: string
language: string
}
/**
* Sends a telemetry event to Logflare for tracking by the product team.
*/
const sendEvent = (
event: {
category: string
action: string
label: string
value?: string
},
gaProps: TelemetryProps,
router: NextRouter
) => {
if (!IS_PLATFORM) return
const consent =
typeof window !== 'undefined'
? localStorage.getItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT)
: null
if (consent !== 'true') return
const { category, action, label, value } = event
// remove # section from router.asPath as it
// often includes sensitive information
// such as access/refresh tokens
const page_location = router.asPath.split('#')[0]
return post(`${API_URL}/telemetry/event`, {
action: action,
category: category,
label: label,
value: value,
page_referrer: document?.referrer,
page_title: document?.title,
page_location,
ga: {
screen_resolution: gaProps?.screenResolution,
language: gaProps?.language,
},
})
}
/**
* TODO: GA4 doesn't have identify method.
* We may or may not need gaClientId here. Confirm later
*/
const sendIdentify = (user: User, gaProps?: TelemetryProps) => {
if (!IS_PLATFORM) return
const consent =
typeof window !== 'undefined'
? localStorage.getItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT)
: null
if (consent !== 'true') return
return post(`${API_URL}/telemetry/identify`, {
user,
ga: {
screen_resolution: gaProps?.screenResolution,
language: gaProps?.language,
},
})
}
/**
* @deprecated use sendEvent instead.
*/
const sendActivity = (
event: {
activity: string
source: string
projectRef?: string
orgSlug?: string
data?: object
},
router: NextRouter
) => {
if (!IS_PLATFORM) return
const consent =
typeof window !== 'undefined'
? localStorage.getItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT)
: null
if (consent !== 'true') return
const { activity, source, projectRef, orgSlug, data } = event
const properties = {
activity,
source,
page: {
path: router.route,
location: router.asPath,
referrer: document?.referrer || '',
title: document?.title || '',
},
...(data && { data }),
...(projectRef && { projectRef }),
...(orgSlug && { orgSlug }),
}
return post(`${API_URL}/telemetry/activity`, properties)
}
/**
* Checks if the user has consented to sending telemetry data. Mainly used for Sentry reports.
*/
export const canSendTelemetry = () => {
const consent =
typeof window !== 'undefined'
? localStorage.getItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT)
: null
return IS_PLATFORM && consent === 'true'
}
/**
* Generates a unique identifier for an anonymous user based on their gotrue id.
*/
export const getAnonId = async (id: string) => {
const hash = new Sha256()
hash.update(id)
const u8Array = await hash.digest()
const binString = Array.from(u8Array, (byte) => String.fromCodePoint(byte)).join('')
const b64encoded = btoa(binString)
return b64encoded
}
const Telemetry = {
sendEvent,
sendIdentify,
sendActivity,
}
export default Telemetry