mirror of
https://github.com/documenso/documenso.git
synced 2025-11-11 13:02:31 +10:00
## Description
Feature flags are broken on SSR due to this error
```
TypeError: fetch failed
at Object.fetch (node:internal/deps/undici/undici:11731:11)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
cause: RequestContentLengthMismatchError: Request body length does not match content-length header
at write (node:internal/deps/undici/undici:8590:41)
at _resume (node:internal/deps/undici/undici:8563:33)
at resume (node:internal/deps/undici/undici:8459:7)
at [dispatch] (node:internal/deps/undici/undici:7704:11)
at Client.Intercept (node:internal/deps/undici/undici:7377:20)
at Client.dispatch (node:internal/deps/undici/undici:6023:44)
at [dispatch] (node:internal/deps/undici/undici:6254:32)
at Pool.dispatch (node:internal/deps/undici/undici:6023:44)
at [dispatch] (node:internal/deps/undici/undici:9343:27)
at Agent.Intercept (node:internal/deps/undici/undici:7377:20) {
code: 'UND_ERR_REQ_CONTENT_LENGTH_MISMATCH'
}
}
```
I've removed content-length header since it isn't mandatory to my
knowledge for get requests.
## Changes
- Add fallback local flags when individual flag request fails
- Add error logging
- Remove `content-length` from headers being passed to Posthog
112 lines
3.0 KiB
TypeScript
112 lines
3.0 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
import type { TFeatureFlagValue } from '@documenso/lib/client-only/providers/feature-flag.types';
|
|
import { ZFeatureFlagValueSchema } from '@documenso/lib/client-only/providers/feature-flag.types';
|
|
import { APP_BASE_URL } from '@documenso/lib/constants/app';
|
|
import { LOCAL_FEATURE_FLAGS, isFeatureFlagEnabled } from '@documenso/lib/constants/feature-flags';
|
|
|
|
/**
|
|
* Evaluate whether a flag is enabled for the current user.
|
|
*
|
|
* @param flag The flag to evaluate.
|
|
* @param options See `GetFlagOptions`.
|
|
* @returns Whether the flag is enabled, or the variant value of the flag.
|
|
*/
|
|
export const getFlag = async (
|
|
flag: string,
|
|
options?: GetFlagOptions,
|
|
): Promise<TFeatureFlagValue> => {
|
|
const requestHeaders = options?.requestHeaders ?? {};
|
|
delete requestHeaders['content-length'];
|
|
|
|
if (!isFeatureFlagEnabled()) {
|
|
return LOCAL_FEATURE_FLAGS[flag] ?? true;
|
|
}
|
|
|
|
const url = new URL(`${APP_BASE_URL()}/api/feature-flag/get`);
|
|
url.searchParams.set('flag', flag);
|
|
|
|
return await fetch(url, {
|
|
headers: {
|
|
...requestHeaders,
|
|
},
|
|
next: {
|
|
revalidate: 60,
|
|
},
|
|
})
|
|
.then(async (res) => res.json())
|
|
.then((res) => ZFeatureFlagValueSchema.parse(res))
|
|
.catch((err) => {
|
|
console.error(err);
|
|
return LOCAL_FEATURE_FLAGS[flag] ?? false;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Get all feature flags for the current user if possible.
|
|
*
|
|
* @param options See `GetFlagOptions`.
|
|
* @returns A record of flags and their values for the user derived from the headers.
|
|
*/
|
|
export const getAllFlags = async (
|
|
options?: GetFlagOptions,
|
|
): Promise<Record<string, TFeatureFlagValue>> => {
|
|
const requestHeaders = options?.requestHeaders ?? {};
|
|
delete requestHeaders['content-length'];
|
|
|
|
if (!isFeatureFlagEnabled()) {
|
|
return LOCAL_FEATURE_FLAGS;
|
|
}
|
|
|
|
const url = new URL(`${APP_BASE_URL()}/api/feature-flag/all`);
|
|
|
|
return fetch(url, {
|
|
headers: {
|
|
...requestHeaders,
|
|
},
|
|
next: {
|
|
revalidate: 60,
|
|
},
|
|
})
|
|
.then(async (res) => res.json())
|
|
.then((res) => z.record(z.string(), ZFeatureFlagValueSchema).parse(res))
|
|
.catch((err) => {
|
|
console.error(err);
|
|
return LOCAL_FEATURE_FLAGS;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Get all feature flags for anonymous users.
|
|
*
|
|
* @returns A record of flags and their values.
|
|
*/
|
|
export const getAllAnonymousFlags = async (): Promise<Record<string, TFeatureFlagValue>> => {
|
|
if (!isFeatureFlagEnabled()) {
|
|
return LOCAL_FEATURE_FLAGS;
|
|
}
|
|
|
|
const url = new URL(`${APP_BASE_URL()}/api/feature-flag/all`);
|
|
|
|
return fetch(url, {
|
|
next: {
|
|
revalidate: 60,
|
|
},
|
|
})
|
|
.then(async (res) => res.json())
|
|
.then((res) => z.record(z.string(), ZFeatureFlagValueSchema).parse(res))
|
|
.catch((err) => {
|
|
console.error(err);
|
|
return LOCAL_FEATURE_FLAGS;
|
|
});
|
|
};
|
|
|
|
interface GetFlagOptions {
|
|
/**
|
|
* The headers to attach to the request to evaluate flags.
|
|
*
|
|
* The authenticated user will be derived from the headers if possible.
|
|
*/
|
|
requestHeaders: Record<string, string>;
|
|
}
|