Skip to content
Merged
Show file tree
Hide file tree
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
29 changes: 3 additions & 26 deletions packages/studio/src/telemetry/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,11 @@

import { describe, expect, it, vi, beforeEach } from "vitest";

// `shouldTrack()` reads `POSTHOG_API_KEY` from module-level const that's
// evaluated at module load time, so changing `import.meta.env` after import
// has no effect on the key. Each test resets module cache and re-imports.
// `shouldTrack()` reads module-level constants evaluated at module load time,
// so changing env after import has no effect. Each test resets module cache.

const OPT_OUT_KEY = "hyperframes-studio:telemetryDisabled";

function setKey(value: string | undefined): void {
if (value === undefined) {
delete (import.meta.env as Record<string, unknown>).VITE_HYPERFRAMES_POSTHOG_KEY;
} else {
(import.meta.env as Record<string, unknown>).VITE_HYPERFRAMES_POSTHOG_KEY = value;
}
}

function setNoTelemetry(value: string | undefined): void {
if (value === undefined) {
delete (import.meta.env as Record<string, unknown>).VITE_HYPERFRAMES_NO_TELEMETRY;
Expand All @@ -37,29 +28,16 @@ async function loadShouldTrack(): Promise<() => boolean> {
describe("studio client shouldTrack", () => {
beforeEach(() => {
setDev(false);
setKey("phc_test_key");
setNoTelemetry(undefined);
localStorage.clear();
vi.unstubAllGlobals();
});

it("returns true when key is configured, not in dev mode, and no opt-outs", async () => {
it("returns true when not in dev mode and no opt-outs", async () => {
const shouldTrack = await loadShouldTrack();
expect(shouldTrack()).toBe(true);
});

it("returns false when API key does not start with phc_", async () => {
setKey("not_a_real_key");
const shouldTrack = await loadShouldTrack();
expect(shouldTrack()).toBe(false);
});

it("returns false when API key is empty string", async () => {
setKey("");
const shouldTrack = await loadShouldTrack();
expect(shouldTrack()).toBe(false);
});

it("returns false when user has opted out via localStorage", async () => {
localStorage.setItem(OPT_OUT_KEY, "1");
const shouldTrack = await loadShouldTrack();
Expand Down Expand Up @@ -93,7 +71,6 @@ describe("studio client shouldTrack", () => {
it("memoizes its decision after the first call", async () => {
const shouldTrack = await loadShouldTrack();
const first = shouldTrack();
// Flip an underlying input — memoized return must not change.
localStorage.setItem(OPT_OUT_KEY, "1");
expect(shouldTrack()).toBe(first);
});
Expand Down
27 changes: 15 additions & 12 deletions packages/studio/src/telemetry/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,9 @@
import { getAnonymousId, hasShownNotice, isOptedOut, markNoticeShown } from "./config";
import { getBrowserSystemMeta } from "./system";

// HeyGen's PostHog project key — write-only, safe to embed in client code.
// OSS builds can override via `VITE_HYPERFRAMES_POSTHOG_KEY` at build time,
// or set it to an empty string to disable telemetry entirely.
const POSTHOG_API_KEY =
(import.meta.env.VITE_HYPERFRAMES_POSTHOG_KEY as string | undefined) ??
"phc_zjjbX0PnWxERXrMHhkEJWj9A9BhGVLRReICgsfTMmpx";
const POSTHOG_HOST =
(import.meta.env.VITE_HYPERFRAMES_POSTHOG_HOST as string | undefined) ??
"https://us.i.posthog.com";
// Write-only PostHog project key, safe to embed in client code.
const POSTHOG_API_KEY = "phc_zjjbX0PnWxERXrMHhkEJWj9A9BhGVLRReICgsfTMmpx";
const POSTHOG_HOST = "https://us.i.posthog.com";
const FLUSH_INTERVAL_MS = 1_000;

type EventProperties = Record<string, string | number | boolean | undefined>;
Expand All @@ -41,15 +35,24 @@ function isApiKeyConfigured(): boolean {
// VITE_HYPERFRAMES_NO_TELEMETRY mirrors the CLI's HYPERFRAMES_NO_TELEMETRY=1
// opt-out so HeyGen's own dev/CI builds can suppress telemetry from the studio
// bundle the same way. Vite injects it at build time. Accepts "1" or "true".
// `import.meta.env` may be undefined in non-Vite bundlers (Next.js Turbopack).
function isBuildTimeOptOut(): boolean {
const v = import.meta.env.VITE_HYPERFRAMES_NO_TELEMETRY as string | undefined;
return v === "1" || v === "true";
try {
const v = import.meta.env.VITE_HYPERFRAMES_NO_TELEMETRY as string | undefined;
return v === "1" || v === "true";
} catch {
return false;
}
}

// `import.meta.env.DEV` is true under `vite dev` / `vite preview`. Auto-suppress
// so developers running `hyperframes preview` don't pollute production telemetry.
function isViteDevMode(): boolean {
return import.meta.env.DEV === true;
try {
return import.meta.env.DEV === true;
} catch {
return false;
}
}

export function shouldTrack(): boolean {
Expand Down
Loading