From 375458dfabc17c76dc3c741c665d6cea6b20ee93 Mon Sep 17 00:00:00 2001 From: Miguel Angel Simon Sierra Date: Thu, 1 Oct 2026 21:22:59 -0700 Subject: [PATCH] feat(studio): a host whose API needs same-origin cookies can have Studio send them --- .../editor/manualEditingAvailability.ts | 8 ++++++ .../studio/src/utils/studioApiFetch.test.ts | 28 +++++++++++++++++-- packages/studio/src/utils/studioApiFetch.ts | 7 ++++- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/packages/studio/src/components/editor/manualEditingAvailability.ts b/packages/studio/src/components/editor/manualEditingAvailability.ts index cc3c806f7a..dd746b41a7 100644 --- a/packages/studio/src/components/editor/manualEditingAvailability.ts +++ b/packages/studio/src/components/editor/manualEditingAvailability.ts @@ -78,4 +78,12 @@ export const STUDIO_FLAT_INSPECTOR_ENABLED = resolveStudioBooleanEnvFlag( true, ); +// A host whose mounted API authenticates with same-origin cookies sets this; studioApiFetch then sends them, and its +// requests share the preview media's sockets again. +export const STUDIO_API_SAME_ORIGIN_CREDENTIALS = resolveStudioBooleanEnvFlag( + env, + ["VITE_STUDIO_API_SAME_ORIGIN_CREDENTIALS"], + false, +); + import { resolveEnabledSdkFamilies } from "../../utils/sdkCutoverPolicy"; diff --git a/packages/studio/src/utils/studioApiFetch.test.ts b/packages/studio/src/utils/studioApiFetch.test.ts index 7b3817861f..64957e9cb3 100644 --- a/packages/studio/src/utils/studioApiFetch.test.ts +++ b/packages/studio/src/utils/studioApiFetch.test.ts @@ -1,12 +1,22 @@ import { afterEach, describe, expect, it, vi } from "vitest"; -import { studioApiFetch } from "./studioApiFetch"; + +async function loadWithEnv(env: Record) { + vi.resetModules(); + for (const [key, value] of Object.entries(env)) vi.stubEnv(key, value); + return import("./studioApiFetch"); +} describe("studioApiFetch", () => { - afterEach(() => vi.unstubAllGlobals()); + afterEach(() => { + vi.unstubAllGlobals(); + vi.unstubAllEnvs(); + vi.resetModules(); + }); it("sends Studio's requests without credentials, keeping the caller's options", async () => { const fetchMock = vi.fn(async () => new Response("ok")); vi.stubGlobal("fetch", fetchMock); + const { studioApiFetch } = await loadWithEnv({}); const signal = new AbortController().signal; await studioApiFetch("/api/projects/demo/history/step", { method: "POST", signal }); @@ -17,4 +27,18 @@ describe("studioApiFetch", () => { credentials: "omit", }); }); + + it("sends same-origin credentials for a host that opted out", async () => { + const fetchMock = vi.fn(async () => new Response("ok")); + vi.stubGlobal("fetch", fetchMock); + const { studioApiFetch } = await loadWithEnv({ + VITE_STUDIO_API_SAME_ORIGIN_CREDENTIALS: "true", + }); + + await studioApiFetch("/api/projects/demo/files/index.html"); + + expect(fetchMock).toHaveBeenCalledWith("/api/projects/demo/files/index.html", { + credentials: "same-origin", + }); + }); }); diff --git a/packages/studio/src/utils/studioApiFetch.ts b/packages/studio/src/utils/studioApiFetch.ts index 20b46cc5c9..38c88ae739 100644 --- a/packages/studio/src/utils/studioApiFetch.ts +++ b/packages/studio/src/utils/studioApiFetch.ts @@ -1,8 +1,13 @@ +import { STUDIO_API_SAME_ORIGIN_CREDENTIALS } from "../components/editor/manualEditingAvailability"; + /** * Studio's one way to fetch. Without credentials Chrome pools these sockets apart from the preview's media, * whose paused videos can hold all six a host gets and leave an undo waiting behind them. */ export function studioApiFetch(input: RequestInfo | URL, init?: RequestInit): Promise { // eslint-disable-next-line no-restricted-globals -- the owner every other call routes through - return fetch(input, { ...init, credentials: "omit" }); + return fetch(input, { + ...init, + credentials: STUDIO_API_SAME_ORIGIN_CREDENTIALS ? "same-origin" : "omit", + }); }