The root layout called getOrCreateAnonymousId() in a Server Component without marking the route as dynamic. Next.js treated it as static, pre-rendering the developer's UUID into the standalone build shipped via npm — so every `npx denchclaw` user shared the same PostHog identity. - Add `export const dynamic = "force-dynamic"` to root layout - Replace `process.env.HOME || "~"` fallback with `homedir()` in web telemetry and posthog-analytics plugin (Node.js path.join doesn't expand "~", creating a relative path under cwd instead)
75 lines
2.0 KiB
TypeScript
75 lines
2.0 KiB
TypeScript
import { randomUUID } from "node:crypto";
|
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
import { homedir } from "node:os";
|
|
import { join, dirname } from "node:path";
|
|
import { PostHog } from "posthog-node";
|
|
|
|
const POSTHOG_KEY = process.env.NEXT_PUBLIC_POSTHOG_KEY || "";
|
|
const POSTHOG_HOST = "https://us.i.posthog.com";
|
|
|
|
let client: PostHog | null = null;
|
|
|
|
function ensureClient(): PostHog | null {
|
|
if (!POSTHOG_KEY) return null;
|
|
if (!client) {
|
|
client = new PostHog(POSTHOG_KEY, {
|
|
host: POSTHOG_HOST,
|
|
flushAt: 10,
|
|
flushInterval: 30_000,
|
|
});
|
|
}
|
|
return client;
|
|
}
|
|
|
|
let _cachedAnonymousId: string | null = null;
|
|
|
|
/**
|
|
* Read the persisted install-scoped anonymous ID from ~/.openclaw-dench/telemetry.json,
|
|
* generating and writing one if absent.
|
|
*/
|
|
export function getOrCreateAnonymousId(): string {
|
|
if (_cachedAnonymousId) return _cachedAnonymousId;
|
|
|
|
try {
|
|
const stateDir = join(process.env.HOME || homedir(), ".openclaw-dench");
|
|
const configPath = join(stateDir, "telemetry.json");
|
|
|
|
let raw: Record<string, unknown> = {};
|
|
if (existsSync(configPath)) {
|
|
raw = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
}
|
|
if (typeof raw.anonymousId === "string" && raw.anonymousId) {
|
|
_cachedAnonymousId = raw.anonymousId;
|
|
return raw.anonymousId;
|
|
}
|
|
const id = randomUUID();
|
|
raw.anonymousId = id;
|
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n", "utf-8");
|
|
_cachedAnonymousId = id;
|
|
return id;
|
|
} catch {
|
|
const id = randomUUID();
|
|
_cachedAnonymousId = id;
|
|
return id;
|
|
}
|
|
}
|
|
|
|
export function trackServer(
|
|
event: string,
|
|
properties?: Record<string, unknown>,
|
|
distinctId?: string,
|
|
): void {
|
|
const ph = ensureClient();
|
|
if (!ph) return;
|
|
|
|
ph.capture({
|
|
distinctId: distinctId || getOrCreateAnonymousId(),
|
|
event,
|
|
properties: {
|
|
...properties,
|
|
$process_person_profile: false,
|
|
},
|
|
});
|
|
}
|