feat: store workspace preferences and drafts locally
This commit is contained in:
+28
-10
@@ -12,23 +12,36 @@ export class ApiError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiFetch<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
// Only declare a JSON content-type when we actually send a body. Body-less
|
||||
// POSTs (resume, close) would otherwise make Fastify reject the empty body
|
||||
// with FST_ERR_CTP_EMPTY_JSON_BODY (400).
|
||||
const headers: Record<string, string> = {
|
||||
...(init?.headers as Record<string, string> | undefined),
|
||||
};
|
||||
if (init?.body != null && !("content-type" in headers) && !("Content-Type" in headers)) {
|
||||
headers["content-type"] = "application/json";
|
||||
function requestHeaders(init: RequestInit | undefined): Headers {
|
||||
const headers = new Headers(init?.headers);
|
||||
// Fetch supplies the multipart boundary for FormData. Declaring JSON here
|
||||
// would prevent Fastify from parsing an imported workspace bundle.
|
||||
if (
|
||||
init?.body != null
|
||||
&& !(typeof FormData !== "undefined" && init.body instanceof FormData)
|
||||
&& !headers.has("content-type")
|
||||
) {
|
||||
headers.set("content-type", "application/json");
|
||||
}
|
||||
const res = await fetch(joinBackendPath(BASE, path), { ...init, headers });
|
||||
return headers;
|
||||
}
|
||||
|
||||
async function request(path: string, init?: RequestInit): Promise<Response> {
|
||||
const res = await fetch(joinBackendPath(BASE, path), { ...init, headers: requestHeaders(init) });
|
||||
if (!res.ok) {
|
||||
const bodyText = await res.text().catch(() => "");
|
||||
let payload: unknown;
|
||||
try { payload = bodyText ? JSON.parse(bodyText) : undefined; } catch { payload = undefined; }
|
||||
throw new ApiError(res.status, bodyText, payload);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function apiFetch<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
// Only declare a JSON content-type when we actually send a body. Body-less
|
||||
// POSTs (resume, close) would otherwise make Fastify reject the empty body
|
||||
// with FST_ERR_CTP_EMPTY_JSON_BODY (400).
|
||||
const res = await request(path, init);
|
||||
if (res.status === 204) return undefined as T;
|
||||
// Accepted fire-and-forget endpoints may legitimately return 202 with no
|
||||
// representation. Keep apiFetch useful for both 202 and 204 contracts.
|
||||
@@ -36,4 +49,9 @@ export async function apiFetch<T>(path: string, init?: RequestInit): Promise<T>
|
||||
return body ? (JSON.parse(body) as T) : (undefined as T);
|
||||
}
|
||||
|
||||
/** Download registry bundles without attempting to parse their ZIP body as JSON. */
|
||||
export async function apiFetchBlob(path: string, init?: RequestInit): Promise<Blob> {
|
||||
return (await request(path, init)).blob();
|
||||
}
|
||||
|
||||
export { BASE };
|
||||
|
||||
Reference in New Issue
Block a user