RealtimeTokensPopover.tsx95 lines · main
1// @ts-nocheck
2import { PermissionAction } from '@supabase/shared-types/out/constants'
3import { useParams } from 'common'
4import { Dispatch, SetStateAction, useEffect, useRef } from 'react'
5import { toast } from 'sonner'
6
7import { RealtimeConfig } from './useRealtimeMessages'
8import { RoleImpersonationPopover } from '@/components/interfaces/RoleImpersonationSelector/RoleImpersonationPopover'
9import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
10import { getTemporaryAPIKey } from '@/data/api-keys/temp-api-keys-query'
11import { useProjectPostgrestConfigQuery } from '@/data/config/project-postgrest-config-query'
12import { useSendEventMutation } from '@/data/telemetry/send-event-mutation'
13import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
14import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
15import { IS_PLATFORM } from '@/lib/constants'
16import { getRoleImpersonationJWT } from '@/lib/role-impersonation'
17import { useRoleImpersonationStateSnapshot } from '@/state/role-impersonation-state'
18
19interface RealtimeTokensPopoverProps {
20 config: RealtimeConfig
21 onChangeConfig: Dispatch<SetStateAction<RealtimeConfig>>
22}
23
24export const RealtimeTokensPopover = ({ config, onChangeConfig }: RealtimeTokensPopoverProps) => {
25 const { ref } = useParams()
26 const { data: org } = useSelectedOrganizationQuery()
27 const snap = useRoleImpersonationStateSnapshot()
28
29 const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*')
30 const { data: apiKeys } = useAPIKeysQuery(
31 {
32 projectRef: config.projectRef,
33 reveal: true,
34 },
35 { enabled: canReadAPIKeys }
36 )
37 const { anonKey, publishableKey } = getKeys(apiKeys)
38
39 const { data: postgrestConfig } = useProjectPostgrestConfigQuery(
40 { projectRef: config.projectRef },
41 { enabled: IS_PLATFORM }
42 )
43
44 const jwtSecret = postgrestConfig?.jwt_secret
45
46 const { mutate: sendEvent } = useSendEventMutation()
47
48 // only send a telemetry event if the user changes the role. Don't send an event during initial render.
49 const isMounted = useRef(false)
50
51 useEffect(() => {
52 if (isMounted.current) {
53 sendEvent({
54 action: 'realtime_inspector_database_role_updated',
55 groups: { project: ref ?? 'Unknown', organization: org?.slug ?? 'Unknown' },
56 })
57 }
58 isMounted.current = true
59 // eslint-disable-next-line react-hooks/exhaustive-deps
60 }, [snap.role])
61
62 useEffect(() => {
63 const triggerUpdateTokenBearer = async () => {
64 let token: string | undefined
65 let bearer: string | null = null
66
67 if (
68 config.projectRef !== undefined &&
69 jwtSecret !== undefined &&
70 snap.role !== undefined &&
71 snap.role.type === 'postgrest'
72 ) {
73 token = publishableKey?.api_key ?? anonKey?.api_key
74 await getRoleImpersonationJWT(config.projectRef, jwtSecret, snap.role)
75 .then((b) => (bearer = b))
76 .catch((err) => toast.error(`Failed to get JWT for role: ${err.message}`))
77 } else {
78 try {
79 const data = await getTemporaryAPIKey({ projectRef: config.projectRef, expiry: 3600 })
80 token = data.api_key
81 } catch (error) {
82 token = publishableKey?.api_key
83 }
84 }
85 if (token) {
86 onChangeConfig({ ...config, token, bearer })
87 }
88 }
89
90 triggerUpdateTokenBearer()
91 // eslint-disable-next-line react-hooks/exhaustive-deps
92 }, [snap.role, anonKey])
93
94 return <RoleImpersonationPopover align="start" variant="connected-on-both" />
95}