mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-10-28 23:57:17 +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.
35 lines
923 B
TypeScript
35 lines
923 B
TypeScript
export const privateNoStoreResponseHeaders = {
|
|
"Cache-Control": "private, no-store, max-age=0",
|
|
Pragma: "no-cache",
|
|
Expires: "0",
|
|
Vary: "Cookie",
|
|
};
|
|
|
|
export const appendVaryHeader = (headers: Headers, value: string) => {
|
|
const vary = headers.get("Vary");
|
|
if (vary === null || vary.trim() === "") {
|
|
headers.set("Vary", value);
|
|
return;
|
|
}
|
|
|
|
const varyValues = vary.split(",").map((item) => item.trim().toLowerCase());
|
|
if (varyValues.includes(value.toLowerCase()) === false) {
|
|
headers.set("Vary", `${vary}, ${value}`);
|
|
}
|
|
};
|
|
|
|
export const createPrivateNoStoreHeaders = (init?: HeadersInit) => {
|
|
const headers = new Headers(init);
|
|
|
|
for (const [name, value] of Object.entries(privateNoStoreResponseHeaders)) {
|
|
if (name.toLowerCase() === "vary") {
|
|
continue;
|
|
}
|
|
headers.set(name, value);
|
|
}
|
|
|
|
appendVaryHeader(headers, privateNoStoreResponseHeaders.Vary);
|
|
|
|
return headers;
|
|
};
|