mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-06 17:46:03 +00:00
revert(cli): keep HeyGen API traffic on stable prod (#3202)
* Revert "fix(cli): route HeyGen API calls through canary (#3201)"
This reverts commit 5545521556.
* fix(cli): remove remaining EF canary routes
This commit is contained in:
@@ -84,7 +84,6 @@ describe("auth/client", () => {
|
||||
authorization: "Bearer at_123",
|
||||
[HEYGEN_CLI_SOURCE_HEADER]: HEYGEN_CLI_SOURCE,
|
||||
[HEYGEN_CLIENT_SOURCE_HEADER]: HEYGEN_CLIENT_SOURCE,
|
||||
heygen_route: "canary",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -92,7 +91,6 @@ describe("auth/client", () => {
|
||||
expect(buildAuthHeaders(apiKeyCred())).toEqual({
|
||||
"x-api-key": "hg_x",
|
||||
[HEYGEN_CLIENT_SOURCE_HEADER]: HEYGEN_CLIENT_SOURCE,
|
||||
heygen_route: "canary",
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ import { ErrApi, ErrUnauthenticated, isAuthError } from "./errors.js";
|
||||
import type { ResolvedCredential } from "./resolver.js";
|
||||
import { scrubCredentials } from "./scrub.js";
|
||||
import type { OAuthTokens } from "./store.js";
|
||||
import { withHeygenCanaryRoute } from "../utils/heygenRoute.js";
|
||||
|
||||
const DEFAULT_BASE_URL = "https://api.heygen.com";
|
||||
export const HEYGEN_CLI_SOURCE_HEADER = "X-HeyGen-Source";
|
||||
@@ -186,20 +185,17 @@ export class AuthClient {
|
||||
|
||||
export function buildAuthHeaders(credential: ResolvedCredential): Record<string, string> {
|
||||
if (credential.type === "oauth") {
|
||||
return withHeygenCanaryRoute({
|
||||
return {
|
||||
authorization: `Bearer ${credential.access_token}`,
|
||||
[HEYGEN_CLI_SOURCE_HEADER]: HEYGEN_CLI_SOURCE,
|
||||
[HEYGEN_CLIENT_SOURCE_HEADER]: HEYGEN_CLIENT_SOURCE,
|
||||
});
|
||||
};
|
||||
}
|
||||
// API-key traffic keeps the normal billing path; the backend ignores the
|
||||
// cli-source header for it, so we don't send it (avoids a contradictory
|
||||
// "cli-source claim on an API-key request"). The tool-attribution header IS
|
||||
// sent here — an API-key hyperframes call is still hyperframes usage.
|
||||
return withHeygenCanaryRoute({
|
||||
"x-api-key": credential.key,
|
||||
[HEYGEN_CLIENT_SOURCE_HEADER]: HEYGEN_CLIENT_SOURCE,
|
||||
});
|
||||
return { "x-api-key": credential.key, [HEYGEN_CLIENT_SOURCE_HEADER]: HEYGEN_CLIENT_SOURCE };
|
||||
}
|
||||
|
||||
async function safeText(res: Response): Promise<string> {
|
||||
|
||||
@@ -173,10 +173,8 @@ describe("auth/oauth", () => {
|
||||
it("posts grant_type=refresh_token and persists the response", async () => {
|
||||
process.env["HEYGEN_API_URL"] = "https://api.test.example";
|
||||
let capturedBody: string | undefined;
|
||||
let capturedHeaders: HeadersInit | undefined;
|
||||
const fetchImpl = (async (_url: string, init?: RequestInit) => {
|
||||
capturedBody = init?.body as string;
|
||||
capturedHeaders = init?.headers;
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
access_token: "new_at",
|
||||
@@ -194,7 +192,6 @@ describe("auth/oauth", () => {
|
||||
expect(tokens.refresh_token).toBe("new_rt");
|
||||
expect(capturedBody).toContain("grant_type=refresh_token");
|
||||
expect(capturedBody).toContain("refresh_token=old_rt");
|
||||
expect(capturedHeaders).toMatchObject({ heygen_route: "canary" });
|
||||
|
||||
// Should have persisted.
|
||||
const { credentials } = await readStore();
|
||||
@@ -298,10 +295,8 @@ describe("auth/oauth", () => {
|
||||
|
||||
it("sends token_type_hint when provided", async () => {
|
||||
let capturedBody = "";
|
||||
let capturedHeaders: HeadersInit | undefined;
|
||||
const fetchImpl = (async (_url: string, init?: RequestInit) => {
|
||||
capturedBody = init?.body as string;
|
||||
capturedHeaders = init?.headers;
|
||||
return new Response("", { status: 200 });
|
||||
}) as unknown as typeof fetch;
|
||||
await revokeTokens("tok", {
|
||||
@@ -309,7 +304,6 @@ describe("auth/oauth", () => {
|
||||
token_type_hint: "refresh_token",
|
||||
});
|
||||
expect(capturedBody).toContain("token_type_hint=refresh_token");
|
||||
expect(capturedHeaders).toMatchObject({ heygen_route: "canary" });
|
||||
});
|
||||
|
||||
it("returns silently when client_id is unconfigured (no throw)", async () => {
|
||||
@@ -343,21 +337,6 @@ describe("auth/oauth", () => {
|
||||
});
|
||||
|
||||
describe("startAuthorizationCodeFlow persistence", () => {
|
||||
it("routes the authorization-code exchange through canary", async () => {
|
||||
let capturedHeaders: HeadersInit | undefined;
|
||||
const fetchImpl = (async (_url: string | URL | Request, init?: RequestInit) => {
|
||||
capturedHeaders = init?.headers;
|
||||
return new Response(JSON.stringify({ access_token: "new_at" }), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
}) as typeof fetch;
|
||||
|
||||
await startAuthorizationCodeFlow({ fetchImpl });
|
||||
|
||||
expect(capturedHeaders).toMatchObject({ heygen_route: "canary" });
|
||||
});
|
||||
|
||||
it("overwrites the OAuth block on fresh login (no inherited refresh_token)", async () => {
|
||||
// Pre-seed a prior session whose refresh_token must NOT leak into
|
||||
// the new login when the new response omits one.
|
||||
@@ -471,11 +450,7 @@ describe("auth/oauth", () => {
|
||||
});
|
||||
|
||||
it("polls pending and slow_down responses without persisting before identity verification", async () => {
|
||||
const requests: Array<{
|
||||
url: string;
|
||||
body: URLSearchParams;
|
||||
headers: HeadersInit | undefined;
|
||||
}> = [];
|
||||
const requests: Array<{ url: string; body: URLSearchParams }> = [];
|
||||
const responses = [
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
@@ -510,7 +485,6 @@ describe("auth/oauth", () => {
|
||||
requests.push({
|
||||
url: String(url),
|
||||
body: new URLSearchParams(String(init?.body ?? "")),
|
||||
headers: init?.headers,
|
||||
});
|
||||
});
|
||||
const sleeps: number[] = [];
|
||||
@@ -540,12 +514,6 @@ describe("auth/oauth", () => {
|
||||
expect(requests[1]?.body.get("grant_type")).toBe(
|
||||
"urn:ietf:params:oauth:grant-type:device_code",
|
||||
);
|
||||
expect(requests.map(({ headers }) => headers)).toEqual([
|
||||
expect.objectContaining({ heygen_route: "canary" }),
|
||||
expect.objectContaining({ heygen_route: "canary" }),
|
||||
expect.objectContaining({ heygen_route: "canary" }),
|
||||
expect.objectContaining({ heygen_route: "canary" }),
|
||||
]);
|
||||
expect((await readStore()).source).toBe("absent");
|
||||
|
||||
await persistFreshOAuth(tokens);
|
||||
|
||||
@@ -54,7 +54,6 @@ import {
|
||||
type StoredUserInfo,
|
||||
} from "./store.js";
|
||||
import { c } from "../ui/colors.js";
|
||||
import { withHeygenCanaryRoute } from "../utils/heygenRoute.js";
|
||||
|
||||
const REVOKE_TIMEOUT_MS = 5_000;
|
||||
const MIN_EXPIRES_IN_SECONDS = 30;
|
||||
@@ -256,10 +255,10 @@ async function requestDeviceAuthorization(
|
||||
async (signal) => {
|
||||
const response = await runtime.fetchImpl(deviceAuthorizationEndpoint(), {
|
||||
method: "POST",
|
||||
headers: withHeygenCanaryRoute({
|
||||
headers: {
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
accept: "application/json",
|
||||
}),
|
||||
},
|
||||
body: new URLSearchParams({ client_id: runtime.clientId, scope }).toString(),
|
||||
signal,
|
||||
});
|
||||
@@ -304,10 +303,10 @@ async function requestDeviceToken(
|
||||
async (signal) => {
|
||||
const response = await runtime.fetchImpl(tokenEndpoint(), {
|
||||
method: "POST",
|
||||
headers: withHeygenCanaryRoute({
|
||||
headers: {
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
accept: "application/json",
|
||||
}),
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
grant_type: DEVICE_CODE_GRANT_TYPE,
|
||||
device_code: deviceCode,
|
||||
@@ -395,10 +394,10 @@ export async function refreshTokens(
|
||||
|
||||
const res = await fetchImpl(tokenEndpoint(), {
|
||||
method: "POST",
|
||||
headers: withHeygenCanaryRoute({
|
||||
headers: {
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
accept: "application/json",
|
||||
}),
|
||||
},
|
||||
body: body.toString(),
|
||||
});
|
||||
|
||||
@@ -447,9 +446,7 @@ export async function revokeTokens(token: string, opts: RevokeOptions = {}): Pro
|
||||
try {
|
||||
const res = await fetchImpl(revokeEndpoint(), {
|
||||
method: "POST",
|
||||
headers: withHeygenCanaryRoute({
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
}),
|
||||
headers: { "content-type": "application/x-www-form-urlencoded" },
|
||||
body: body.toString(),
|
||||
signal: controller.signal,
|
||||
});
|
||||
@@ -510,10 +507,10 @@ async function exchangeCodeForTokens(args: {
|
||||
});
|
||||
const res = await fetchImpl(tokenEndpoint(), {
|
||||
method: "POST",
|
||||
headers: withHeygenCanaryRoute({
|
||||
headers: {
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
accept: "application/json",
|
||||
}),
|
||||
},
|
||||
body: body.toString(),
|
||||
});
|
||||
if (res.status === 400 || res.status === 401) {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
const HEYGEN_ROUTE_HEADER = "heygen_route";
|
||||
const HEYGEN_CANARY_ROUTE = "canary";
|
||||
|
||||
/** Route CLI-owned HeyGen API calls through the EF canary deployment. */
|
||||
export function withHeygenCanaryRoute(
|
||||
headers: Record<string, string> = {},
|
||||
): Record<string, string> {
|
||||
return { ...headers, [HEYGEN_ROUTE_HEADER]: HEYGEN_CANARY_ROUTE };
|
||||
}
|
||||
@@ -36,7 +36,6 @@ describeE2E("publish stable-URL round trip (live server)", () => {
|
||||
async function fetchPublicProject(projectId: string): Promise<Record<string, unknown>> {
|
||||
const response = await fetch(
|
||||
`${getPublishApiBaseUrl()}/v1/hyperframes/projects/${projectId}/public`,
|
||||
{ headers: { heygen_route: "canary" } },
|
||||
);
|
||||
expect(response.ok).toBe(true);
|
||||
const payload = (await response.json()) as { data: Record<string, unknown> };
|
||||
|
||||
@@ -551,7 +551,7 @@ afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
const jsonHeaders = { "content-type": "application/json", heygen_route: "canary" };
|
||||
const jsonHeaders = { "content-type": "application/json" };
|
||||
const signedStagedS3Url =
|
||||
"https://s3.example.com/upload?X-Amz-SignedHeaders=content-length;content-type;host;x-amz-server-side-encryption";
|
||||
|
||||
@@ -662,7 +662,7 @@ describe("publishProjectArchive", () => {
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||
expectFetchCall(fetchMock, 2, "https://api2.heygen.com/v1/hyperframes/projects/publish", {
|
||||
method: "POST",
|
||||
headers: { heygen_route: "canary" },
|
||||
headers: {},
|
||||
});
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
|
||||
@@ -5,7 +5,6 @@ import AdmZip from "adm-zip";
|
||||
import ignore, { type Ignore } from "ignore";
|
||||
import { CSS_URL_RE, isNonRelativeUrl, isPathInside } from "@hyperframes/core";
|
||||
import { buildAuthHeaders } from "../auth/client.js";
|
||||
import { withHeygenCanaryRoute } from "./heygenRoute.js";
|
||||
import { tryResolveCredential } from "../auth/index.js";
|
||||
import { writeProjectLink } from "./projectLink.js";
|
||||
|
||||
@@ -558,7 +557,7 @@ async function publishProjectArchiveDirect(
|
||||
"file",
|
||||
new File([archiveArrayBuffer(archive)], `${title}.zip`, { type: PUBLISH_CONTENT_TYPE }),
|
||||
);
|
||||
const headers = withHeygenCanaryRoute(authHeaders);
|
||||
const headers: Record<string, string> = { ...authHeaders };
|
||||
|
||||
const response = await fetchForPublish(
|
||||
`${apiBaseUrl}/v1/hyperframes/projects/publish`,
|
||||
@@ -621,10 +620,10 @@ async function publishProjectArchiveStaged(
|
||||
content_type: PUBLISH_CONTENT_TYPE,
|
||||
content_length: archive.buffer.byteLength,
|
||||
}),
|
||||
headers: withHeygenCanaryRoute({
|
||||
headers: {
|
||||
...authHeaders,
|
||||
"content-type": "application/json",
|
||||
}),
|
||||
},
|
||||
signal: AbortSignal.timeout(PUBLISH_METADATA_TIMEOUT_MS),
|
||||
}),
|
||||
"Failed to prepare project upload",
|
||||
@@ -654,10 +653,10 @@ async function publishProjectArchiveStaged(
|
||||
...(isPublic ? { is_public: true } : {}),
|
||||
...(projectId ? { project_id: projectId } : {}),
|
||||
}),
|
||||
headers: withHeygenCanaryRoute({
|
||||
headers: {
|
||||
...authHeaders,
|
||||
"content-type": "application/json",
|
||||
}),
|
||||
},
|
||||
signal: AbortSignal.timeout(uploadTimeoutMs(archive.buffer.byteLength)),
|
||||
}),
|
||||
"Failed to finalize project publish",
|
||||
|
||||
@@ -35,7 +35,7 @@ describe("submitFeedback", () => {
|
||||
"https://api.example.com/v1/hyperframes/feedback",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json", heygen_route: "canary" },
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
rating: 4,
|
||||
rating_scale: 10,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { getPublishApiBaseUrl } from "./publishProject.js";
|
||||
import { FEEDBACK_RATING_SCALE } from "./feedbackRating.js";
|
||||
import { withHeygenCanaryRoute } from "./heygenRoute.js";
|
||||
|
||||
// Match the backend DTO caps (HyperframesFeedbackRequest). Truncate here so an
|
||||
// over-long field (e.g. a pasted stack trace) is still forwarded truncated,
|
||||
@@ -31,9 +30,9 @@ export async function submitFeedback(input: {
|
||||
cli_version: cap(input.cliVersion, MAX_CLI_VERSION),
|
||||
env: cap(input.env, MAX_ENV),
|
||||
}),
|
||||
headers: withHeygenCanaryRoute({
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
}),
|
||||
},
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user