client: tolerate empty response bodies on 2xx

Every verb ended in an unconditional res.json(), which throws
"Unexpected end of JSON input" on 201/204 responses that carry no body —
so a request that actually succeeded still surfaced as an error (e.g. a
sent chat message toasting a failure). Read text first and only parse
when non-empty, otherwise resolve undefined. Non-empty JSON is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 21:35:40 +00:00
co-authored by Claude Opus 4.8
parent f4a47a035a
commit fd203138bf
+12 -10
View File
@@ -53,6 +53,13 @@ export const getHeaders = (isText: boolean = false) => {
return headers;
};
// 2xx responses can legitimately carry no body (201 Created / 204 No Content). res.json() throws on an
// empty body, so read text first and only parse when there's something — otherwise resolve undefined.
const parseBody = async <T>(res: Response): Promise<T> => {
const text = await res.text();
return (text ? JSON.parse(text) : undefined) as T;
};
export const getText = async (uri: string, baseUrl = '') => {
const headers = getHeaders(true);
const theUrl = baseUrl ? `${baseUrl}/${uri.replace(/^\//, '')}` : uri;
@@ -67,8 +74,7 @@ export const get = async <T>(uri: string, baseUrl = '') => {
const theUrl = baseUrl ? `${baseUrl}/${uri.replace(/^\//, '')}` : uri;
const res = await fetch(theUrl, { headers });
await validateResponse(res);
const data = await res.json();
return data as T;
return parseBody<T>(res);
};
export const getBlob = async (uri: string, baseUrl = '') => {
@@ -100,8 +106,7 @@ export const post = async <T>(uri: string, payload?: any, baseUrl = '') => {
headers,
});
await validateResponse(res);
const data = await res.json();
return data as T;
return parseBody<T>(res);
};
export const put = async <T>(uri: string, payload?: any, baseUrl = '') => {
@@ -119,8 +124,7 @@ export const put = async <T>(uri: string, payload?: any, baseUrl = '') => {
headers,
});
await validateResponse(res);
const data = await res.json();
return data as T;
return parseBody<T>(res);
};
export const patch = async <T>(uri: string, payload?: any, baseUrl = '') => {
@@ -132,8 +136,7 @@ export const patch = async <T>(uri: string, payload?: any, baseUrl = '') => {
headers,
});
await validateResponse(res);
const data = await res.json();
return data as T;
return parseBody<T>(res);
};
export const DELETE = async <T>(uri: string, payload?: any, baseUrl = '') => {
@@ -145,8 +148,7 @@ export const DELETE = async <T>(uri: string, payload?: any, baseUrl = '') => {
headers,
});
await validateResponse(res);
const data = await res.json();
return data as T;
return parseBody<T>(res);
};
const validateResponse = async (res: Response) => {