function defaultTooLargeMessage(label, maxBytes) { return `${label} response body exceeded ${maxBytes} bytes`; } function defaultTooLargeError(message) { return new Error(message); } export async function readBoundedResponseText(response, label, maxBytes, options = {}) { const formatTooLargeMessage = options.formatTooLargeMessage ?? defaultTooLargeMessage; const createTooLargeError = options.createTooLargeError ?? defaultTooLargeError; const tooLargeError = () => createTooLargeError(formatTooLargeMessage(label, maxBytes)); const contentLength = Number(response.headers.get("content-length") ?? ""); if (Number.isSafeInteger(contentLength) && contentLength > maxBytes) { await response.body?.cancel().catch(() => undefined); throw tooLargeError(); } if (!response.body) { return ""; } const reader = response.body.getReader(); const decoder = new TextDecoder(); const chunks = []; let totalBytes = 0; let canceled = false; try { for (;;) { const { done, value } = await (options.timeoutPromise ? Promise.race([reader.read(), options.timeoutPromise]) : reader.read()); if (done) { const tail = decoder.decode(); if (tail) { chunks.push(tail); } break; } totalBytes += value.byteLength; if (totalBytes > maxBytes) { canceled = true; await reader.cancel().catch(() => undefined); throw tooLargeError(); } chunks.push(decoder.decode(value, { stream: true })); } } finally { if (!canceled) { reader.releaseLock(); } } return chunks.join(""); }