openclaw/apps/web/app/components/posthog-provider.tsx
kumarabhirup 351b71fd05
feat(telemetry): add person identity support and enable session replay
Add optional name, email, avatar, and denchOrgId fields to
telemetry.json. When present, all telemetry layers (CLI, web server,
web client, OpenClaw plugin) call PostHog identify() with $name,
$email, $avatar, and dench_org_id person properties.

Remove $process_person_profile:false from all layers so every install
gets a PostHog person profile. Enable session replay with masking
controlled by privacy mode (all text/inputs masked when on, nothing
masked when off).
2026-03-18 00:08:23 -07:00

104 lines
2.9 KiB
TypeScript

"use client";
import posthog from "posthog-js";
import { PostHogProvider as PHProvider } from "posthog-js/react";
import { useEffect, useRef } from "react";
import { usePathname, useSearchParams } from "next/navigation";
const POSTHOG_KEY = process.env.NEXT_PUBLIC_POSTHOG_KEY || "";
const POSTHOG_HOST = "https://us.i.posthog.com";
const DENCHCLAW_VERSION = process.env.NEXT_PUBLIC_DENCHCLAW_VERSION || "";
const OPENCLAW_VERSION = process.env.NEXT_PUBLIC_OPENCLAW_VERSION || "";
type PersonInfo = {
name?: string;
email?: string;
avatar?: string;
denchOrgId?: string;
};
let initialized = false;
function initPostHog(anonymousId?: string, personInfo?: PersonInfo, privacyMode?: boolean) {
if (initialized || !POSTHOG_KEY || typeof window === "undefined") return;
const privacy = privacyMode !== false;
posthog.init(POSTHOG_KEY, {
api_host: POSTHOG_HOST,
capture_pageview: false,
capture_pageleave: true,
persistence: "memory",
autocapture: false,
disable_session_recording: false,
person_profiles: "always",
session_recording: {
maskAllInputs: privacy,
maskTextSelector: privacy ? "*" : undefined,
},
bootstrap: anonymousId
? { distinctID: anonymousId, isIdentifiedID: false }
: undefined,
});
const superProps: Record<string, string> = {};
if (DENCHCLAW_VERSION) superProps.denchclaw_version = DENCHCLAW_VERSION;
if (OPENCLAW_VERSION) superProps.openclaw_version = OPENCLAW_VERSION;
if (Object.keys(superProps).length > 0) posthog.register(superProps);
if (personInfo && anonymousId) {
const props: Record<string, string> = {};
if (personInfo.name) props.$name = personInfo.name;
if (personInfo.email) props.$email = personInfo.email;
if (personInfo.avatar) props.$avatar = personInfo.avatar;
if (personInfo.denchOrgId) props.dench_org_id = personInfo.denchOrgId;
posthog.identify(anonymousId, props);
}
initialized = true;
}
function PageviewTracker() {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
if (!initialized) return;
const wsPath = searchParams?.get("path") ?? "";
if (wsPath.startsWith("~cron")) return;
const url = pathname + (searchParams?.toString() ? `?${searchParams.toString()}` : "");
posthog.capture("$pageview", { $current_url: url });
}, [pathname, searchParams]);
return null;
}
export function PostHogProvider({
children,
anonymousId,
personInfo,
privacyMode,
}: {
children: React.ReactNode;
anonymousId?: string;
personInfo?: PersonInfo;
privacyMode?: boolean;
}) {
const initRef = useRef(false);
useEffect(() => {
if (initRef.current) return;
initRef.current = true;
initPostHog(anonymousId, personInfo, privacyMode);
}, [anonymousId, personInfo, privacyMode]);
if (!POSTHOG_KEY) return <>{children}</>;
return (
<PHProvider client={posthog}>
<PageviewTracker />
{children}
</PHProvider>
);
}