Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 122 additions & 6 deletions web-common/src/lib/analytics/posthog.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,49 @@
import posthog, { type Properties } from "posthog-js";

const POSTHOG_API_KEY = import.meta.env.RILL_UI_PUBLIC_POSTHOG_API_KEY;
const MASK_CHARACTER = "*";
const REGULATED_REGION_PROPERTY = "is_regulated_region";
const GEOIP_COUNTRY_PROPERTY = "$geoip_country_code";
const GEOIP_SUBDIVISION_PROPERTY = "$geoip_subdivision_1_code";

const EU_COUNTRIES = new Set([
"AT",
"BE",
"BG",
"HR",
"CY",
"CZ",
"DK",
"EE",
"FI",
"FR",
"DE",
"GR",
"HU",
"IE",
"IT",
"LV",
"LT",
"LU",
"MT",
"NL",
"PL",
"PT",
"RO",
"SK",
"SI",
"ES",
"SE",
]);

type PosthogWithGeo = typeof posthog & {
get_property?: (property: string) => unknown;
set_person_properties?: (properties: Record<string, unknown>) => void;
onFeatureFlags?: (callback: () => void) => void;
};

const maskValue = (text?: string | null) =>
text ? MASK_CHARACTER.repeat(text.length) : (text ?? "");

export function initPosthog(rillVersion: string, sessionId?: string | null) {
// No need to proceed if PostHog is already initialized
Expand All @@ -11,12 +54,84 @@ export function initPosthog(rillVersion: string, sessionId?: string | null) {
return;
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
const geoAwarePosthog = posthog as PosthogWithGeo;
let isRegulatedUser = true;
let persistedRegulatedStatus: boolean | undefined;

const persistRegulatedStatus = (value: boolean) => {
if (persistedRegulatedStatus === value) return;
geoAwarePosthog.set_person_properties?.({
[REGULATED_REGION_PROPERTY]: value,
});
persistedRegulatedStatus = value;
};

const evaluateGeoRegulation = () => {
const existingValue = geoAwarePosthog.get_property?.(
REGULATED_REGION_PROPERTY,
) as unknown;
if (typeof existingValue === "boolean") {
isRegulatedUser = existingValue;
persistRegulatedStatus(existingValue);
return;
}
if (typeof existingValue === "string") {
const normalized = existingValue.toLowerCase();
if (normalized === "true" || normalized === "false") {
const boolValue = normalized === "true";
isRegulatedUser = boolValue;
persistRegulatedStatus(boolValue);
return;
}
}

const countryRaw = geoAwarePosthog.get_property?.(
GEOIP_COUNTRY_PROPERTY,
) as unknown;
const subdivisionRaw = geoAwarePosthog.get_property?.(
GEOIP_SUBDIVISION_PROPERTY,
) as unknown;

const normalizeRegionCode = (value: unknown) => {
if (typeof value === "string") return value.toUpperCase();
if (typeof value === "number") return value.toString().toUpperCase();
return undefined;
};

const country = normalizeRegionCode(countryRaw);
const subdivision = normalizeRegionCode(subdivisionRaw);

if (!country) return;

const computed =
EU_COUNTRIES.has(country) || (country === "US" && subdivision === "CA");
isRegulatedUser = computed;
persistRegulatedStatus(computed);
};

const geoMaskInputFn = (text: string, element?: HTMLElement | null) => {
if (!text) return text;
const inputType =
element instanceof HTMLInputElement
? element.type?.toLowerCase()
: element instanceof HTMLTextAreaElement
? element.getAttribute("type")?.toLowerCase()
: element?.getAttribute?.("type")?.toLowerCase();
if (inputType === "password") {
return maskValue(text);
}
if (!isRegulatedUser) return text;
return maskValue(text);
};

posthog.init(POSTHOG_API_KEY, {
api_host: "https://us.i.posthog.com", // TODO: use a reverse proxy https://posthog.com/docs/advanced/proxy
session_recording: {
maskAllInputs: true,
maskTextSelector: "*",
maskAllInputs: false,
maskInputFn: geoMaskInputFn,
maskInputOptions: {
password: true,
},
recordHeaders: true,
recordBody: false,
},
Expand All @@ -25,16 +140,17 @@ export function initPosthog(rillVersion: string, sessionId?: string | null) {
bootstrap: {
sessionID: sessionId ?? undefined,
},
loaded: (posthog) => {
posthog.register_for_session({
loaded: (client) => {
client.register_for_session({
"Rill version": rillVersion,
});
evaluateGeoRegulation();
geoAwarePosthog.onFeatureFlags?.(evaluateGeoRegulation);
},
});
}

export function posthogIdentify(userID: string, userProperties?: Properties) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
posthog.identify(userID, userProperties);
}

Expand Down
Loading