fix(studio): guard import.meta.env access for non-Vite bundlers

import.meta.env is undefined in Next.js Turbopack/Webpack, causing
"Cannot read properties of undefined" when the studio telemetry client
loads. Wrap accesses in try-catch so they gracefully fall back.

Also hardcode the PostHog API key and host — they're public write-only
values with no reason to be overridable via env.
This commit is contained in:
Miguel Ángel
2026-05-23 13:59:08 -04:00
parent 179b09ec9d
commit 377f081941
2 changed files with 18 additions and 38 deletions
+3 -26
View File
@@ -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;
@@ -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();
@@ -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);
});
+15 -12
View File
@@ -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>;
@@ -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 {