Files
supabase/packages/common/feature-flags.tsx
Charis 30b6af4ff3 fix (docs): non-platform integrations page (#35285)
There were two bugs when trying to run the integrations page locally
with NEXT_PUBLIC_IS_PLATFORM=false:

1. The IS_PLATFORM check imported from common was not evaluating
correctly to a boolean. This is because I slapped a 'use client' on the
entire common package last year -_-""" which caused all its imports to
be evaluated to functions when used in server components. I have now
moved the 'use client's down to the submodules that actually need it.

2. When the integrations submenu is empty, the navigation menu errors
out because it expects all navigation items to either have children or
have links. Have updated this to gracefully hide empty headers.
2025-04-25 15:22:06 -04:00

141 lines
4.0 KiB
TypeScript

'use client'
import { FlagValues } from '@vercel/flags/react'
import { createContext, PropsWithChildren, useContext, useEffect, useState } from 'react'
import { components } from 'api-types'
import { useUser } from './auth'
import { LOCAL_STORAGE_KEYS } from './constants'
import { get, post } from './fetchWrappers'
import { ensurePlatformSuffix } from './helpers'
type TrackFeatureFlagVariables = components['schemas']['TelemetryFeatureFlagBodyDto']
export type CallFeatureFlagsResponse = components['schemas']['TelemetryCallFeatureFlagsResponseDto']
export async function getFeatureFlags(API_URL: string) {
const data = await get(`${ensurePlatformSuffix(API_URL)}/telemetry/feature-flags`)
return data as CallFeatureFlagsResponse
}
export async function trackFeatureFlag(API_URL: string, body: TrackFeatureFlagVariables) {
const consent =
(typeof window !== 'undefined'
? localStorage.getItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT)
: null) === 'true'
if (!consent) return undefined
await post(`${ensurePlatformSuffix(API_URL)}/telemetry/feature-flags/track`, { body })
}
export type FeatureFlagContextType = {
API_URL?: string
configcat: { [key: string]: boolean | number | string | null }
posthog: CallFeatureFlagsResponse
hasLoaded?: boolean
}
export const FeatureFlagContext = createContext<FeatureFlagContextType>({
API_URL: undefined,
configcat: {},
posthog: {},
hasLoaded: false,
})
function getCookies() {
const pairs = document.cookie.split(';')
let cookies: Record<string, string> = {}
for (var i = 0; i < pairs.length; i++) {
var [t_key, value] = pairs[i].split('=')
const key = t_key.trim()
cookies[key] = unescape(value)
}
return cookies
}
export const FeatureFlagProvider = ({
API_URL,
enabled = true,
getConfigCatFlags,
children,
}: PropsWithChildren<{
API_URL: string
enabled?: boolean
getConfigCatFlags?: (
userEmail?: string
) => Promise<{ settingKey: string; settingValue: boolean | number | string | null | undefined }[]>
}>) => {
const user = useUser()
const [store, setStore] = useState<FeatureFlagContextType>({
API_URL,
configcat: {},
posthog: {},
hasLoaded: false,
})
useEffect(() => {
let mounted = true
async function processFlags() {
if (!enabled) return
let flagStore: FeatureFlagContextType = { configcat: {}, posthog: {} }
// Load PH flags
const flags = await getFeatureFlags(API_URL)
if (flags) {
flagStore.posthog = flags
}
// Load ConfigCat flags
if (typeof getConfigCatFlags === 'function') {
const flagValues = await getConfigCatFlags(user?.email)
let overridesCookieValue: Record<string, boolean> = {}
try {
const cookies = getCookies()
overridesCookieValue = JSON.parse(cookies['vercel-flag-overrides'])
} catch {}
flagValues.forEach((item) => {
flagStore['configcat'][item.settingKey] =
overridesCookieValue[item.settingKey] ??
(item.settingValue === null ? null : item.settingValue ?? false)
})
}
flagStore.hasLoaded = true
if (mounted) {
setStore(flagStore)
}
}
// [Joshen] getFlags get triggered everytime the tab refocuses but this should be okay
// as per https://configcat.com/docs/sdk-reference/js/#polling-modes:
// The polling downloads the config.json at the set interval and are stored in the internal cache
// which subsequently all getValueAsync() calls are served from there
processFlags()
return () => {
mounted = false
}
}, [enabled, user?.email])
return (
<FeatureFlagContext.Provider value={store}>
{/*
[Joshen] Just support configcat flags in Vercel flags for now for simplicity
although I think it should be fairly simply to support PH too
*/}
<FlagValues values={store.configcat} />
{children}
</FeatureFlagContext.Provider>
)
}
export const useFeatureFlags = () => {
return useContext(FeatureFlagContext)
}