80 lines
2.2 KiB
TypeScript
80 lines
2.2 KiB
TypeScript
/**
|
|
* Safely read a Response/Request body with sensible defaults:
|
|
* - clones the response/request to avoid consuming the original response/request
|
|
* - Skips GET/HEAD
|
|
* - Tries JSON first regardless of content-type
|
|
* - Falls back to text and optionally parses when it "looks" like JSON
|
|
*/
|
|
export async function readBody<T extends Response | Request>(
|
|
r: T,
|
|
): Promise<unknown> {
|
|
// skip GET/HEAD requests (unchanged)
|
|
const method = "method" in r ? r.method.toUpperCase() : undefined;
|
|
if (method === "GET" || method === "HEAD") {
|
|
return undefined;
|
|
}
|
|
|
|
// no body at all → undefined (unchanged)
|
|
if (!("body" in r) || r.body == null) {
|
|
return undefined;
|
|
}
|
|
|
|
// 1) try JSON (unchanged)
|
|
try {
|
|
return await r.clone().json();
|
|
} catch {
|
|
// 2) try text (unchanged + your whitespace/JSON-heuristic)
|
|
try {
|
|
const text = await r.clone().text();
|
|
const trimmed = text.trim();
|
|
|
|
if (trimmed.length === 0) return text;
|
|
|
|
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
try {
|
|
return JSON.parse(trimmed);
|
|
} catch {
|
|
return text;
|
|
}
|
|
}
|
|
return text;
|
|
} catch {
|
|
// 3) FINAL FALLBACK: manual read that accepts string or bytes
|
|
try {
|
|
const c = r.clone();
|
|
const stream: ReadableStream | null = c.body ?? null;
|
|
if (!stream) return undefined;
|
|
|
|
const reader = stream.getReader();
|
|
const decoder = new TextDecoder();
|
|
let out = "";
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) break;
|
|
if (typeof value === "string") {
|
|
out += value; // accept string chunks
|
|
} else {
|
|
out += decoder.decode(value, { stream: true }); // bytes
|
|
}
|
|
}
|
|
out += decoder.decode(); // flush
|
|
|
|
const trimmed = out.trim();
|
|
if (trimmed.length === 0) return out;
|
|
|
|
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
try {
|
|
return JSON.parse(trimmed);
|
|
} catch {
|
|
return out;
|
|
}
|
|
}
|
|
return out;
|
|
} catch {
|
|
return undefined; // same "give up" behavior you had
|
|
}
|
|
}
|
|
}
|
|
}
|