





















@@ -19,6 +19,10 @@ const botApiTimeoutMs = readPositiveInt(
1919process.env.OPENCLAW_NPM_TELEGRAM_BOT_API_TIMEOUT_MS,
202030000,
2121);
22+const botApiBodyMaxBytes = readPositiveInt(
23+process.env.OPENCLAW_NPM_TELEGRAM_BOT_API_BODY_MAX_BYTES,
24+1024 * 1024,
25+);
2226const maxWarmFailures = Number(
2327process.env.OPENCLAW_NPM_TELEGRAM_MAX_FAILURES ?? String(warmSampleCount),
2428);
@@ -56,20 +60,84 @@ function readPositiveInt(raw, fallback) {
5660return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
5761}
586259-async function fetchTelegramJson(url, init) {
63+function taggedError(message, code) {
64+return Object.assign(new Error(message), { code });
65+}
66+67+async function readBoundedResponseText(response, label, byteLimit, timeoutPromise) {
68+const contentLength = response.headers.get("content-length");
69+if (contentLength) {
70+const parsedLength = Number(contentLength);
71+if (Number.isSafeInteger(parsedLength) && parsedLength > byteLimit) {
72+await response.body?.cancel().catch(() => {});
73+throw taggedError(`${label} response body exceeded ${byteLimit} bytes`, "ETOOBIG");
74+}
75+}
76+if (!response.body) {
77+return "";
78+}
79+80+const reader = response.body.getReader();
81+const decoder = new TextDecoder();
82+let byteCount = 0;
83+let text = "";
84+try {
85+while (true) {
86+const { done, value } = await Promise.race([reader.read(), timeoutPromise]);
87+if (done) {
88+return text + decoder.decode();
89+}
90+byteCount += value.byteLength;
91+if (byteCount > byteLimit) {
92+await reader.cancel().catch(() => {});
93+throw taggedError(`${label} response body exceeded ${byteLimit} bytes`, "ETOOBIG");
94+}
95+text += decoder.decode(value, { stream: true });
96+}
97+} finally {
98+reader.releaseLock();
99+}
100+}
101+102+function parseJsonPayload(rawPayload, label) {
103+try {
104+return JSON.parse(rawPayload);
105+} catch (error) {
106+throw new Error(`${label} returned invalid JSON`, { cause: error });
107+}
108+}
109+110+async function fetchTelegramJson(url, init, label) {
60111const controller = new AbortController();
61-const timer = setTimeout(() => {
62-controller.abort();
63-}, botApiTimeoutMs);
112+const timeoutError = taggedError(`${label} timed out after ${botApiTimeoutMs}ms`, "ETIMEDOUT");
113+let timeout;
114+const timeoutPromise = new Promise((_, reject) => {
115+timeout = setTimeout(() => {
116+controller.abort(timeoutError);
117+reject(timeoutError);
118+}, botApiTimeoutMs);
119+timeout.unref?.();
120+});
64121try {
65-const response = await fetch(url, {
66- ...init,
67-signal: controller.signal,
68-});
69-const payload = await response.json();
122+const response = await Promise.race([
123+fetch(url, {
124+ ...init,
125+signal: controller.signal,
126+}),
127+timeoutPromise,
128+]);
129+const rawPayload = await readBoundedResponseText(
130+response,
131+label,
132+botApiBodyMaxBytes,
133+timeoutPromise,
134+);
135+const payload = parseJsonPayload(rawPayload, label);
70136return { payload, response };
71137} finally {
72-clearTimeout(timer);
138+if (timeout) {
139+clearTimeout(timeout);
140+}
73141}
74142}
75143@@ -79,11 +147,15 @@ class TelegramBot {
79147}
8014881149async call(method, body) {
82-const { payload, response } = await fetchTelegramJson(`${this.baseUrl}/${method}`, {
83-method: "POST",
84-headers: { "content-type": "application/json" },
85-body: JSON.stringify(body),
86-});
150+const { payload, response } = await fetchTelegramJson(
151+`${this.baseUrl}/${method}`,
152+{
153+method: "POST",
154+headers: { "content-type": "application/json" },
155+body: JSON.stringify(body),
156+},
157+`Telegram Bot API ${method}`,
158+);
87159if (!response.ok || payload.ok !== true) {
88160throw new Error(`${method} failed: ${JSON.stringify(payload)}`);
89161}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。