Files
supabase/packages/common/configcat.ts
Charis 453c31da16 refactor(common): drop ConfigCat proxy probe in favor of waitForReady (#45939)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Refactor / performance.

## What is the current behavior?

`packages/common/configcat.ts` does a two-step setup: a probe `fetch` to
the ConfigCat proxy URL, followed by SDK client initialization with the
proxy as `baseUrl` (or against the direct ConfigCat CDN if the probe
fails). On the happy path this fires **two** network requests for the
same JSON config on cold start — the probe, then the SDK's own initial
AutoPoll fetch.

## What is the new behavior?

The probe is removed. We initialize the proxy client directly and
inspect the `ClientCacheState` returned by `waitForReady()`. On
`NoFlagData` (proxy unreachable, no cache) we `dispose()` the proxy
client and fall back to the direct SDK key client. Cold-start fetches
drop from 2 to 1 when the proxy is healthy. Worst-case fallback delay is
bounded by `maxInitWaitTimeSeconds` (5s default), comparable to today's
probe timeout on a broken proxy.

The unused exported \`fetchHandler\` is removed (no external importers —
verified via grep). Tests in \`configcat.test.ts\` are updated to mock
\`waitForReady\`/\`dispose\` and a new test covers the proxy-failure
fallback path.

## Additional context

Resolves FE-3174

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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved ConfigCat client initialization with robust fallback handling
when proxy is unavailable.

* **Chores**
  * Removed `fetchHandler` export from ConfigCat module.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45939)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-14 12:32:58 -04:00

82 lines
2.4 KiB
TypeScript

import * as configcat from 'configcat-js'
let client: configcat.IConfigCatClient
/**
* To set up ConfigCat for another app
* - Declare `FeatureFlagProvider` at the _app level
* - Pass in `getFlags` as `getConfigCatFlags` into `FeatureFlagProvider`
* - [Joshen] Wondering if this should just be baked into FeatureFlagProvider, rather than passed as a prop
* - Ensure that your app has the `NEXT_PUBLIC_CONFIGCAT_PROXY_URL` env var
* - [Joshen] Wondering if we can just set a default value for each env var, so can skip setting up env var in Vercel
* - Verify that your flags are now loading by console logging `flagValues` in `FeatureFlagProvider`'s useEffect
* - Can now use ConfigCat feature flags with the `useFlag` hook
*/
async function getClient() {
if (client) return client
const proxyUrl = process.env.NEXT_PUBLIC_CONFIGCAT_PROXY_URL
const sdkKey = process.env.NEXT_PUBLIC_CONFIGCAT_SDK_KEY
if (!sdkKey && !proxyUrl) {
console.log('Skipping ConfigCat set up as env vars are not present')
return undefined
}
const options = { pollIntervalSeconds: 7 * 60 } // 7 minutes
try {
if (proxyUrl) {
const proxyClient = configcat.getClient(
'configcat-proxy/frontend-v2',
configcat.PollingMode.AutoPoll,
{ ...options, baseUrl: proxyUrl }
)
const cacheState = await proxyClient.waitForReady()
if (cacheState !== configcat.ClientCacheState.NoFlagData) {
client = proxyClient
return client
}
proxyClient.dispose()
}
if (sdkKey) {
client = configcat.getClient(sdkKey, configcat.PollingMode.AutoPoll, options)
return client
}
console.error('ConfigCat proxy unreachable and SDK key is missing')
return undefined
} catch (error: any) {
console.error(`Failed to get ConfigCat client: ${error.message}`)
return undefined
}
}
export async function getFlags(userEmail: string = '', customAttributes?: Record<string, string>) {
const client = await getClient()
const _customAttributes = {
...customAttributes,
is_staff: !!userEmail ? userEmail.includes('@supabase.').toString() : 'false',
}
if (!client) {
return []
}
await client.waitForReady()
if (userEmail) {
return client.getAllValuesAsync(
new configcat.User(userEmail, undefined, undefined, _customAttributes)
)
} else {
return client.getAllValuesAsync(
new configcat.User('anonymous', undefined, undefined, _customAttributes)
)
}
}