{"record":{"id":"d1848fbcca0fa482","repo":"koala73/worldmonitor","slug":"widget-body-too-large","errorCode":null,"errorMessage":"Widget body too large","messagePattern":"Widget body too large","errorType":"exception","errorClass":"WidgetBodyTooLargeError","httpStatus":null,"severity":"error","filePath":"api/widget-agent.ts","lineNumber":115,"sourceCode":"    this.name = 'WidgetBodyTooLargeError';\n  }\n}\n\nasync function readRequestBody(req: Request): Promise<string> {\n  if (!req.body) return '';\n  const reader = req.body.getReader();\n  let timer: ReturnType<typeof setTimeout> | undefined;\n  try {\n    return await Promise.race([\n      (async () => {\n        const decoder = new TextDecoder();\n        let total = 0;\n        let text = '';\n        while (true) {\n          const { done, value } = await reader.read();\n          if (done) return text + decoder.decode();\n          total += value.byteLength;\n          if (total > WIDGET_AGENT_MAX_BODY_BYTES) throw new WidgetBodyTooLargeError();\n          text += decoder.decode(value, { stream: true });\n        }\n      })(),\n      new Promise<never>((_, reject) => {\n        timer = setTimeout(() => reject(new Error('widget-agent body read timeout')), WIDGET_AGENT_BODY_TIMEOUT_MS);\n      }),\n    ]);\n  } finally {\n    clearTimeout(timer);\n    // Do not let a stalled producer's cancellation hold the response open.\n    void reader.cancel().catch(() => {});\n    reader.releaseLock();\n  }\n}\n\nasync function hasValidWorldMonitorKey(key: string): Promise<boolean> {\n  return timingSafeIncludes(key, WORLDMONITOR_VALID_KEYS);\n}","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/api/widget-agent.ts#L97-L133","documentation":"readRequestBody for the widget-agent proxy reads the incoming request stream and enforces WIDGET_AGENT_MAX_BODY_BYTES. As soon as the cumulative byte count exceeds the cap it throws WidgetBodyTooLargeError, surfacing as 'Widget body too large'. This protects the upstream widget-agent service from oversized payloads and bounds memory use in the edge function.","triggerScenarios":"POSTing a request body larger than WIDGET_AGENT_MAX_BODY_BYTES to the widget-agent proxy endpoint (proxyWidgetAgent -> readRequestBody).","commonSituations":"Clients embedding large HTML/JSON payloads in widget generation requests; uploading base64 assets inline; retry loops that keep re-sending an oversized body.","solutions":["Reduce the request payload sent to the widget-agent endpoint below the configured byte cap.","Chunk or split the work into multiple smaller widget-agent requests.","If the cap is too low for legitimate workloads, raise WIDGET_AGENT_MAX_BODY_BYTES.","Strip unnecessary fields/attachments from the payload before sending."],"exampleFix":"// before\nawait fetch('/api/widget-agent', { method: 'POST', body: JSON.stringify({ html: hugeHtml, ...rest }) });\n// after\nawait fetch('/api/widget-agent', { method: 'POST', body: JSON.stringify({ html: hugeHtml.slice(0, 100_000), ...rest }) });","handlingStrategy":"validation","validationCode":"const body = JSON.stringify(payload);\nconst bytes = new TextEncoder().encode(body).byteLength;\nif (bytes > WIDGET_AGENT_MAX_BODY_BYTES) throw new Error(`payload ${bytes}B exceeds limit`);","typeGuard":null,"tryCatchPattern":"try { return await proxyWidgetAgent(req); } catch (e) { if (e instanceof WidgetBodyTooLargeError) return json(413, { error: 'body exceeds limit' }); throw e; }","preventionTips":["Measure serialized payload size client-side before POSTing","Send assets by reference (URL) instead of inline base64","Split large widget jobs into multiple requests","Keep the byte cap documented next to the client integration"],"tags":["http","payload-size","proxy","request-body"],"backgroundTag":"payload-too-large","analyzedSha":"e586b8b4b80f595aa7ece295eec10d76f2921240","analyzedAt":"2026-09-22T01:50:49.965Z","contentChangedAt":"2026-09-22T01:50:49.965Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}