const DEFAULT_HOST = "https://us.i.posthog.com"; const FLUSH_INTERVAL_MS = 15_000; const FLUSH_AT = 10; export interface CaptureEvent { distinctId: string; event: string; properties?: Record; } /** * Minimal PostHog client using the HTTP capture API directly. * Zero npm dependencies -- uses built-in fetch (Node 18+). */ export class PostHogClient { private apiKey: string; private host: string; private globalProperties: Record; private queue: Array> = []; private timer: ReturnType | null = null; constructor(apiKey: string, host?: string, globalProperties?: Record) { this.apiKey = apiKey; this.host = (host || DEFAULT_HOST).replace(/\/$/, ""); this.globalProperties = globalProperties ?? {}; this.timer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS); if (this.timer.unref) this.timer.unref(); } capture(event: CaptureEvent): void { this.queue.push({ event: event.event, distinct_id: event.distinctId, properties: { ...this.globalProperties, ...event.properties, $lib: "denchclaw-posthog-plugin", }, timestamp: new Date().toISOString(), }); if (this.queue.length >= FLUSH_AT) { this.flush(); } } flush(): void { if (this.queue.length === 0) return; const batch = this.queue.splice(0); const body = JSON.stringify({ api_key: this.apiKey, batch, }); fetch(`${this.host}/batch/`, { method: "POST", headers: { "Content-Type": "application/json" }, body, }).catch(() => { // Fail silently -- telemetry should never block the gateway. }); } async shutdown(): Promise { if (this.timer) { clearInterval(this.timer); this.timer = null; } this.flush(); } } export function createPostHogClient( apiKey: string, host?: string, globalProperties?: Record, ): PostHogClient { return new PostHogClient(apiKey, host, globalProperties); } export async function shutdownPostHogClient(client: PostHogClient): Promise { try { await client.shutdown(); } catch { // Non-fatal. } }