UNPKG

@flags-sdk/posthog

Version:

PostHog adapter for the Flags SDK

237 lines (235 loc) 6.86 kB
// src/index.ts import { PostHog } from "posthog-node"; // src/provider/index.ts async function getProviderData(options) { const hints = []; if (!options.personalApiKey) { hints.push({ key: "posthog/missing-personal-api-key", text: "Missing PostHog Personal API Key" }); } let host = options.appHost; if (!host) { try { host = getAppHost(); } catch (e) { hints.push({ key: "posthog/missing-app-host", text: "Missing NEXT_PUBLIC_POSTHOG_HOST environment variable" }); } } if (!options.projectId) { hints.push({ key: "posthog/missing-project-id", text: "Missing PostHog Project ID" }); } if (hints.length > 0) { return { definitions: {}, hints }; } const headers = { Authorization: `Bearer ${options.personalApiKey}` }; const res = await fetch( `${host}/api/projects/${options.projectId}/feature_flags`, { method: "GET", headers, // @ts-expect-error used by some Next.js versions cache: "no-store" } ); if (res.status !== 200) { return { definitions: {}, hints: [ { key: `posthog/response-not-ok/${options.projectId}`, text: `Failed to fetch PostHog (Received ${res.status} response)` } ] }; } try { const data = await res.json(); const items = [...data.results]; for (let offset = 100; offset < data.count; offset += 100) { const paginatedRes = await fetch( `${host}/api/projects/${options.projectId}/feature_flags?offset=${offset}&limit=100`, { method: "GET", headers, // @ts-expect-error used by some Next.js versions cache: "no-store" } ); if (paginatedRes.status === 200) { const paginatedData = await paginatedRes.json(); items.push(...paginatedData.results); } else { hints.push({ key: `posthog/response-not-ok/${options.projectId}-${offset}`, text: `Failed to fetch PostHog (Received ${paginatedRes.status} response)` }); } } return { definitions: items.reduce((acc, item) => { acc[item.key] = { origin: `${host}/project/${options.projectId}/feature_flags/${item.id}`, description: item.name, createdAt: new Date(item.created_at).getTime(), options: !item.filters.payloads ? [{ value: false }, { value: true }] : Object.entries(item.filters.payloads ?? {}).map( ([key, value]) => ({ value: JSON.parse(value), label: key }) ) }; return acc; }, {}), hints }; } catch (e) { return { definitions: {}, hints: [ { key: `posthog/response-not-ok/${options.projectId}`, text: "Failed to fetch PostHog" } ] }; } } var getAppHost = (apiHost) => { const host = apiHost ?? process.env.NEXT_PUBLIC_POSTHOG_HOST; if (!host) { throw new Error("NEXT_PUBLIC_POSTHOG_HOST is not set"); } if (host.includes("us.i.posthog.com")) { return "https://us.posthog.com"; } if (host.includes("eu.i.posthog.com")) { return "https://eu.posthog.com"; } return host; }; // src/index.ts function createPostHogAdapter({ postHogKey, postHogOptions }) { const client = new PostHog(postHogKey, postHogOptions); const result = { isFeatureEnabled: (options) => { return { async decide({ key, entities, defaultValue }) { const parsedEntities = parseEntities(entities); const result2 = await client.isFeatureEnabled( trimKey(key), parsedEntities.distinctId, options ) ?? defaultValue; if (result2 === void 0) { throw new Error( `PostHog Adapter isFeatureEnabled returned undefined for ${trimKey(key)} and no default value was provided.` ); } return result2; } }; }, featureFlagValue: (options) => { return { async decide({ key, entities, defaultValue }) { const parsedEntities = parseEntities(entities); const flagValue = await client.getFeatureFlag( trimKey(key), parsedEntities.distinctId, options ); if (flagValue === void 0) { if (typeof defaultValue !== "undefined") { return defaultValue; } throw new Error( `PostHog Adapter featureFlagValue found undefined for ${trimKey(key)} and no default value was provided.` ); } return flagValue; } }; }, featureFlagPayload: (getValue, options) => { return { async decide({ key, entities, defaultValue }) { const parsedEntities = parseEntities(entities); const payload = await client.getFeatureFlagPayload( trimKey(key), parsedEntities.distinctId, void 0, options ); if (!payload) { if (typeof defaultValue !== "undefined") { return defaultValue; } throw new Error( `PostHog Adapter featureFlagPayload found undefined for ${trimKey(key)} and no default value was provided.` ); } return getValue(payload); } }; } }; return result; } function parseEntities(entities) { if (!entities) { throw new Error( "PostHog Adapter: Missing entities, flag must be defined with an identify() function." ); } return entities; } function assertEnv(name) { const value = process.env[name]; if (!value) { throw new Error(`PostHog Adapter: Missing ${name} environment variable`); } return value; } function trimKey(key) { return key.split(".")[0]; } var defaultPostHogAdapter; function getOrCreateDefaultAdapter() { if (!defaultPostHogAdapter) { defaultPostHogAdapter = createPostHogAdapter({ postHogKey: assertEnv("NEXT_PUBLIC_POSTHOG_KEY"), postHogOptions: { host: assertEnv("NEXT_PUBLIC_POSTHOG_HOST"), personalApiKey: process.env.POSTHOG_PERSONAL_API_KEY, featureFlagsPollingInterval: 1e4, // Presumption: Server IP is likely not a good proxy for user location disableGeoip: true } }); } return defaultPostHogAdapter; } var postHogAdapter = { isFeatureEnabled: (...args) => getOrCreateDefaultAdapter().isFeatureEnabled(...args), featureFlagValue: (...args) => getOrCreateDefaultAdapter().featureFlagValue(...args), featureFlagPayload: (...args) => getOrCreateDefaultAdapter().featureFlagPayload(...args) }; export { createPostHogAdapter, getProviderData, postHogAdapter }; //# sourceMappingURL=index.js.map