mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-10-29 16:17:39 +09:00
**Summary** Migrates Builder bootstrap data loading from the legacy `/rest/data/:projectId` endpoint to `build.loadData` tRPC, while keeping the old REST route as a no-store compatibility shim. Hardens authenticated Builder/dashboard responses by explicitly sending: - `Cache-Control: private, no-store, max-age=0` - `Pragma: no-cache` - `Expires: 0` - `Vary: Cookie` **Details** - Added `build.loadData` tRPC query for full Builder state. - Switched Builder client bootstrap loading to tRPC. - Added shared cache-control helpers for private no-store responses. - Made tRPC responses private/no-store by default unless anonymous and explicitly public-cacheable. - Added no-store headers to Builder/dashboard/auth/resource/asset-related responses. - Updated redirect handling to preserve headers while enforcing no-store. - Prevented stale dashboard account data from flashing by applying user-scoped loader data before paint and clearing stores on teardown. - Added tests for cache headers, tRPC response metadata, and no-store redirects. **Validation** - `pnpm --filter @webstudio-is/builder test -- app/services/cache-control.server.test.ts app/services/trpc-response-meta.server.test.ts app/services/no-store-redirect.test.ts app/routes/rest.assets.test.ts app/shared/sync/project-queue.test.ts app/dashboard/dashboard.test.ts` - `pnpm exec oxlint ...` on touched files - `git diff --check` `pnpm --filter @webstudio-is/builder typecheck` still fails on existing unrelated Tailwind/css-engine errors.
70 lines
2.1 KiB
TypeScript
70 lines
2.1 KiB
TypeScript
import { toast } from "@webstudio-is/design-system";
|
|
import {
|
|
apiClientHeader,
|
|
apiClientVersionHeader,
|
|
getApiCompatibilityPayload,
|
|
} from "@webstudio-is/trpc-interface/api-compatibility";
|
|
import { csrfToken } from "./csrf.client";
|
|
import { $authToken } from "./nano-states";
|
|
import { publicStaticEnv } from "~/env/env.static";
|
|
|
|
/**
|
|
* To avoid fetch interception from the canvas, i.e., `globalThis.fetch = () => console.log('INTERCEPTED');`,
|
|
*/
|
|
const _fetch = globalThis.fetch;
|
|
|
|
/**
|
|
* To avoid fetch interception from the canvas, i.e., `globalThis.fetch = () => console.log('INTERCEPTED');`,
|
|
* To add csrf token to the headers.
|
|
*/
|
|
export const fetch: typeof globalThis.fetch = async (
|
|
requestInfo,
|
|
requestInit
|
|
) => {
|
|
if (csrfToken === undefined) {
|
|
toast.error("CSRF token is not set.");
|
|
throw new Error("CSRF token is not set.");
|
|
}
|
|
|
|
const headers = new Headers(requestInit?.headers);
|
|
|
|
headers.set("X-CSRF-Token", csrfToken);
|
|
headers.set(apiClientHeader, "browser");
|
|
headers.set(apiClientVersionHeader, publicStaticEnv.VERSION);
|
|
|
|
const authToken = $authToken.get();
|
|
|
|
// Do not override the existing x-auth-token header.
|
|
// As some mutations are queue based and they need to be authenticated with the same token as in the queue.
|
|
if (authToken !== undefined && headers.get("x-auth-token") === null) {
|
|
headers.set("x-auth-token", authToken);
|
|
}
|
|
|
|
const modifiedInit: RequestInit = {
|
|
...requestInit,
|
|
cache: requestInit?.cache ?? "no-store",
|
|
headers,
|
|
};
|
|
|
|
const response = await _fetch(requestInfo, modifiedInit);
|
|
if (response.ok === false) {
|
|
const contentType = response.headers.get("Content-Type") ?? "";
|
|
if (contentType.includes("application/json")) {
|
|
const body: unknown = await response
|
|
.clone()
|
|
.json()
|
|
.catch(() => undefined);
|
|
const payload = getApiCompatibilityPayload(body);
|
|
if (payload?.action.type === "reloadBrowser") {
|
|
toast.error(payload.message, {
|
|
id: "api-compatibility",
|
|
duration: Number.POSITIVE_INFINITY,
|
|
});
|
|
throw new Error(payload.message, { cause: payload });
|
|
}
|
|
}
|
|
}
|
|
|
|
return response;
|
|
};
|