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
1 change: 1 addition & 0 deletions .github/workflows/fast-video-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ jobs:
- name: Validate fast-capture video (drawElement + BeginFrame)
run: |
docker run --rm \
-e HYPERFRAMES_NO_TELEMETRY=1 \
--security-opt seccomp=unconfined \
--shm-size=4g \
-e PRODUCER_VALIDATE_COMP='${{ inputs.composition }}' \
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/regression.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ jobs:
echo "Mode: ${{ matrix.mode }}"
echo "Args: ${{ matrix.args }}"
docker run --rm \
-e HYPERFRAMES_NO_TELEMETRY=1 \
--security-opt seccomp=unconfined \
--shm-size=4g \
-v ${{ github.workspace }}/packages/producer/tests:/app/packages/producer/tests \
Expand Down
28 changes: 19 additions & 9 deletions packages/cli/src/server/telemetryIdentity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ describe("resolveCliTelemetryDistinctId", () => {
detectAgent.mockReturnValue(null);
});

it.each([true, false])("passes telemetry posture to Studio on every host: %s", (enabled) => {
shouldTrack.mockReturnValue(enabled);
const html = buildCliIdentityScript({ includeIdentity: false });
expect(html).toContain(`window.__HF_CLI_TELEMETRY_DISABLED=${!enabled};`);
});

it("returns the CLI anonymousId when telemetry is enabled", () => {
shouldTrack.mockReturnValue(true);
readConfig.mockReturnValue({ anonymousId: "machine-uuid" });
Expand Down Expand Up @@ -101,21 +107,23 @@ describe("buildCliIdentityScript", () => {
shouldTrack.mockReturnValue(true);
readConfig.mockReturnValue({ anonymousId: "machine-uuid" });
expect(buildCliIdentityScript()).toBe(
'<script>window.__HF_CLI_DISTINCT_ID="machine-uuid";</script>',
'<script>window.__HF_CLI_TELEMETRY_DISABLED=false;window.__HF_CLI_DISTINCT_ID="machine-uuid";</script>',
);
});

it("also seeds window.__HF_CLI_BUCKET_SEED when the config carries a bucket seed", () => {
shouldTrack.mockReturnValue(true);
readConfig.mockReturnValue({ anonymousId: "machine-uuid", bucketSeed: "seed-uuid" });
expect(buildCliIdentityScript()).toBe(
'<script>window.__HF_CLI_DISTINCT_ID="machine-uuid";window.__HF_CLI_BUCKET_SEED="seed-uuid";</script>',
'<script>window.__HF_CLI_TELEMETRY_DISABLED=false;window.__HF_CLI_DISTINCT_ID="machine-uuid";window.__HF_CLI_BUCKET_SEED="seed-uuid";</script>',
);
});

it("emits an empty string when telemetry is off and there are no canaries", () => {
it("emits the browser opt-out when telemetry is off and there are no canaries", () => {
shouldTrack.mockReturnValue(false);
expect(buildCliIdentityScript()).toBe("");
expect(buildCliIdentityScript()).toBe(
"<script>window.__HF_CLI_TELEMETRY_DISABLED=true;</script>",
);
});

// The cross-surface fix: with telemetry off the CLI resolves every canary
Expand All @@ -127,7 +135,7 @@ describe("buildCliIdentityScript", () => {
canaryDecisions.mockReturnValue({ "de-parallel-router": { enabled: false, forced: false } });
const script = buildCliIdentityScript();
expect(script).toBe(
"<script>window.__HF_CLI_CANARY_DECISIONS=" +
"<script>window.__HF_CLI_TELEMETRY_DISABLED=true;window.__HF_CLI_CANARY_DECISIONS=" +
'{"de-parallel-router":{"enabled":false,"forced":false}};</script>',
);
expect(script).not.toContain("__HF_CLI_DISTINCT_ID");
Expand All @@ -139,7 +147,7 @@ describe("buildCliIdentityScript", () => {
readConfig.mockReturnValue({ anonymousId: "machine-uuid", bucketSeed: "seed-uuid" });
canaryDecisions.mockReturnValue({ "de-parallel-router": { enabled: true, forced: true } });
expect(buildCliIdentityScript()).toBe(
'<script>window.__HF_CLI_DISTINCT_ID="machine-uuid";' +
'<script>window.__HF_CLI_TELEMETRY_DISABLED=false;window.__HF_CLI_DISTINCT_ID="machine-uuid";' +
'window.__HF_CLI_BUCKET_SEED="seed-uuid";' +
"window.__HF_CLI_CANARY_DECISIONS=" +
'{"de-parallel-router":{"enabled":true,"forced":true}};</script>',
Expand All @@ -163,7 +171,7 @@ describe("buildCliIdentityScript", () => {
throw new Error("registry blew up");
});
expect(buildCliIdentityScript()).toBe(
'<script>window.__HF_CLI_DISTINCT_ID="machine-uuid";</script>',
'<script>window.__HF_CLI_TELEMETRY_DISABLED=false;window.__HF_CLI_DISTINCT_ID="machine-uuid";</script>',
);
});

Expand Down Expand Up @@ -195,9 +203,11 @@ describe("buildStudioHeadScripts", () => {
expect(head.indexOf("__HF_CLI_DISTINCT_ID")).toBeLessThan(head.indexOf("__HF_STUDIO_ENV__"));
});

it("returns just the env script when there is no identity and no canary", () => {
it("returns the browser opt-out and env script when there is no identity and no canary", () => {
shouldTrack.mockReturnValue(false);
expect(buildStudioHeadScripts(ENV_SCRIPT)).toBe(ENV_SCRIPT);
expect(buildStudioHeadScripts(ENV_SCRIPT)).toBe(
"<script>window.__HF_CLI_TELEMETRY_DISABLED=true;</script>" + ENV_SCRIPT,
);
});
});

Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/server/telemetryIdentity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ function resolveCliCanaryDecisions(): Record<string, CliCanaryDecision> | null {
export function buildCliIdentityScript(options: { includeIdentity?: boolean } = {}): string {
const { includeIdentity = true } = options;
const parts: string[] = [];
parts.push(`window.__HF_CLI_TELEMETRY_DISABLED=${!telemetryShouldTrack()};`);

// Identity is the only part gated on a trusted Host. The decisions map below
// is not identifying, and withholding it would push a LAN/remote Studio
Expand Down
26 changes: 26 additions & 0 deletions packages/studio/src/telemetry/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe("studio client shouldTrack", () => {
setDev(false);
setNoTelemetry(undefined);
localStorage.clear();
delete window.__HF_CLI_TELEMETRY_DISABLED;
vi.unstubAllGlobals();
});

Expand Down Expand Up @@ -89,3 +90,28 @@ describe("studio client shouldTrack", () => {
expect(shouldTrack()).toBe(false);
});
});

it("suppresses actual render events in a CLI-opted-out Studio while the enabled control sends", async () => {
vi.useFakeTimers();
vi.resetModules();
setDev(false);
setNoTelemetry(undefined);
localStorage.clear();
const send = vi.fn().mockResolvedValue(new Response());
vi.stubGlobal("fetch", send);
try {
const { trackEvent } = await import("./client");
window.__HF_CLI_TELEMETRY_DISABLED = true;
trackEvent("render_complete", { duration: 1 });
await vi.advanceTimersByTimeAsync(1100);
expect(send).not.toHaveBeenCalled();
window.__HF_CLI_TELEMETRY_DISABLED = false;
trackEvent("render_complete", { duration: 1 });
await vi.advanceTimersByTimeAsync(1100);
expect(send).toHaveBeenCalledOnce();
} finally {
delete window.__HF_CLI_TELEMETRY_DISABLED;
vi.useRealTimers();
vi.unstubAllGlobals();
}
});
10 changes: 10 additions & 0 deletions packages/studio/src/telemetry/policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ describe("browserTelemetryAllowed", () => {

beforeEach(async () => {
localStorage.clear();
delete window.__HF_CLI_TELEMETRY_DISABLED;
vi.resetModules();
// vitest sets import.meta.env.DEV; the policy suppresses under it, so the
// baseline has to be an explicitly production-like env.
Expand All @@ -28,6 +29,15 @@ describe("browserTelemetryAllowed", () => {
vi.unstubAllEnvs();
});

it("refuses a CLI opt-out even when browser storage permits telemetry", () => {
expect(browserTelemetryAllowed()).toBe(true);
window.__HF_CLI_TELEMETRY_DISABLED = true;
expect(browserTelemetryAllowed()).toBe(false);
expect(localStorage.getItem(DOCUMENTED_OPT_OUT)).toBeNull();
window.__HF_CLI_TELEMETRY_DISABLED = false;
expect(browserTelemetryAllowed()).toBe(true);
});

it("allows telemetry with no control set", () => {
expect(browserTelemetryAllowed()).toBe(true);
});
Expand Down
7 changes: 7 additions & 0 deletions packages/studio/src/telemetry/policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,17 @@ export function browserTelemetryAllowed(): boolean {
}
}

declare global {
interface Window {
__HF_CLI_TELEMETRY_DISABLED?: boolean;
}
}

function allowed(): boolean {
return (
isApiKeyConfigured() &&
!isBuildTimeOptOut() &&
!(typeof window !== "undefined" && window.__HF_CLI_TELEMETRY_DISABLED === true) &&
!isViteDevMode() &&
!isOptedOut() &&
!isLegacyOptedOut() &&
Expand Down
Loading