refactor: Optimize Sentry user ids (#47342)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved signed-in user identification in error tracking to use the
current authenticated user ID directly.
* Removed reliance on previously stored anonymous identifiers, reducing
inconsistent user matching across sessions.
* **Tests**
* Added coverage to confirm error tracking user identity is set when a
user is signed in and not set when signed out.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Ivan Vasilov authored and GitHub committed 2026-06-26 17:25:52 +02:00
1 parent 4f80bb70cd
commit a9565c1645
3 files changed
+34 -25

No files matched your search

+32 -1
View File
@@ -7,6 +7,7 @@ const mocks = vi.hoisted(() => ({
identify: vi.fn(),
useUser: vi.fn(),
useOrganizationsQuery: vi.fn(),
setUser: vi.fn(),
}))
vi.mock('common', async (importOriginal) => {
@@ -34,7 +35,7 @@ vi.mock('@/hooks/misc/useSelectedOrganization', () => ({
}))
vi.mock('@sentry/nextjs', () => ({
setUser: vi.fn(),
setUser: (...args: unknown[]) => mocks.setUser(...args),
}))
const USER_ID = 'user-abc-123'
@@ -48,6 +49,7 @@ describe('Telemetry — posthog identify mirroring', () => {
mocks.identify.mockReset()
mocks.useUser.mockReset()
mocks.useOrganizationsQuery.mockReset()
mocks.setUser.mockReset()
})
it('fires identify with both org_count and signup_timestamp when user and orgs are loaded', async () => {
@@ -146,3 +148,32 @@ describe('Telemetry — posthog identify mirroring', () => {
expect(mocks.identify).not.toHaveBeenCalled()
})
})
describe('Telemetry — Sentry user identification', () => {
beforeEach(() => {
mocks.identify.mockReset()
mocks.useUser.mockReset()
mocks.useOrganizationsQuery.mockReset()
mocks.setUser.mockReset()
})
it('sets the raw (un-hashed) user id on Sentry', async () => {
mocks.useUser.mockReturnValue({ id: USER_ID, created_at: CREATED_AT })
mocks.useOrganizationsQuery.mockReturnValue({ data: orgs(1) })
render(<Telemetry />)
await waitFor(() => {
expect(mocks.setUser).toHaveBeenCalledWith({ id: USER_ID })
})
})
it('does not set a Sentry user when the user is not logged in', async () => {
mocks.useUser.mockReturnValue(null)
mocks.useOrganizationsQuery.mockReturnValue({ data: orgs(1) })
render(<Telemetry />)
await new Promise((r) => setTimeout(r, 10))
expect(mocks.setUser).not.toHaveBeenCalled()
})
})
+2 -22
View File
@@ -1,5 +1,5 @@
import * as Sentry from '@sentry/nextjs'
import { LOCAL_STORAGE_KEYS, PageTelemetry, posthogClient, safeLocalStorage, useUser } from 'common'
import { PageTelemetry, posthogClient, useUser } from 'common'
import { useEffect, useRef } from 'react'
import { useConsentToast } from 'ui-patterns/consent'
@@ -7,16 +7,6 @@ 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 as BufferSource)
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
@@ -64,17 +54,7 @@ export function Telemetry() {
return
}
const setSentryId = async () => {
let sentryUserId = safeLocalStorage.getItem(LOCAL_STORAGE_KEYS.SENTRY_USER_ID)
if (!sentryUserId) {
sentryUserId = await getAnonId(user?.id)
safeLocalStorage.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))
Sentry.setUser({ id: user.id })
}, [user?.id])
return (
@@ -58,8 +58,6 @@ export const LOCAL_STORAGE_KEYS = {
LINTER_SHOW_FOOTER: 'supabase-linter-show-footer',
// Key to track account deletion requests
ACCOUNT_DELETION_REQUEST: 'supabase-account-deletion-request',
// Used for storing a user id when sending reports to Sentry. The id is hashed for anonymity.
SENTRY_USER_ID: 'supabase-sentry-user-id',
// Used for storing the last sign in method used by the user
LAST_SIGN_IN_METHOD: 'supabase-last-sign-in-method',
// Key to track the last selected schema. The project ref is intentionally put at the end for easier search in the browser console.