@flags-sdk/posthog
Version:
PostHog adapter for the Flags SDK
237 lines (235 loc) • 6.86 kB
JavaScript
// 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