fix: flush CONCAT_FIELDS when payload exceeds MAX_FIELD_LENGTH
Build and Deploy / build-and-deploy (push) Successful in 1m55s

This commit is contained in:
2026-07-05 19:03:25 +00:00 Unverified
parent 2ad53df691
commit 9a936c9133
+52 -41
View File
@@ -32,27 +32,21 @@ export function webhookMerger(app: App) {
if (existing) { if (existing) {
clearTimeout(existing.timer); clearTimeout(existing.timer);
existing.data = merge(existing.data, body); const oldData = existing.data;
const flushed = new Set<string>();
existing.data = merge(oldData, body, undefined, MAX_FIELD_LENGTH, flushed);
existing.count++; existing.count++;
if (flushed.size > 0) {
existing.count = 1;
forward(id, url, oldData);
existing.timer = setTimeout(() => forward(id, url, existing.data), bufferTime);
return status(202);
}
log.debug`collected ${existing}`; log.debug`collected ${existing}`;
existing.timer = setTimeout(async () => { existing.timer = setTimeout(() => forward(id, url, existing.data), bufferTime);
log.debug`running ${existing}`;
try {
const res = await fetch(url, {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify(existing.data),
});
if (!res.ok)
log.warn`Could not forward webhook: ${res.status}\n${await res.json().catch(() => res.text())}`;
} finally {
pending.delete(id);
}
}, bufferTime);
return status(202); return status(202);
} }
@@ -65,22 +59,7 @@ export function webhookMerger(app: App) {
log.debug`collected ${state}`; log.debug`collected ${state}`;
state.timer = setTimeout(async () => { state.timer = setTimeout(() => forward(id, url, state.data), bufferTime);
log.debug`running ${state}`;
try {
const res = await fetch(url, {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify(state.data),
});
if (!res.ok)
log.warn`Could not forward webhook: ${res.status}\n${await res.json().catch(() => res.text())}`;
} finally {
pending.delete(id);
}
}, bufferTime);
pending.set(id, state); pending.set(id, state);
@@ -104,22 +83,54 @@ async function getRoutes(): Promise<RouteMap> {
return res; return res;
} }
const MAX_FIELD_LENGTH = Number(env.MAX_FIELD_LENGTH) || 4000;
async function forward(id: string, url: string, data: unknown) {
log.debug`running ${id}`;
try {
const res = await fetch(url, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(data),
});
if (!res.ok)
log.warn`Could not forward webhook: ${res.status}\n${await res.json().catch(() => res.text())}`;
} finally {
pending.delete(id);
}
}
const CONCAT_FIELDS = new Set(["content"]); const CONCAT_FIELDS = new Set(["content"]);
function merge(a: unknown, b: unknown, key?: string): unknown { function merge(
a: unknown,
b: unknown,
key?: string,
maxLength = Infinity,
flushed?: Set<string>,
): unknown {
if ( if (
key && key &&
CONCAT_FIELDS.has(key) && CONCAT_FIELDS.has(key) &&
typeof a === "string" && typeof a === "string" &&
typeof b === "string" typeof b === "string"
) { ) {
const CODEBLOCK_MATCHER = /^([^`]+)```(?:([a-z]*)\n)?([^`]+)```\n?/m; const combined = (() => {
const [ma, mb] = [a.match(CODEBLOCK_MATCHER), b.match(CODEBLOCK_MATCHER)]; const CODEBLOCK_MATCHER = /^([^`]+)```(?:([a-z]*)\n)?([^`]+)```\n?/m;
log.debug`${ma} ${mb}`; const [ma, mb] = [a.match(CODEBLOCK_MATCHER), b.match(CODEBLOCK_MATCHER)];
if (ma && mb && ma[1] === mb[1]) { log.debug`${ma} ${mb}`;
return `${ma[1]}\`\`\`${ma[2] ? `${ma[2]}\n` : ""}${ma[3]}${mb[3]}\`\`\``; if (ma && mb && ma[1] === mb[1]) {
return `${ma[1]}\`\`\`${ma[2] ? `${ma[2]}\n` : ""}${ma[3]}${mb[3]}\`\`\``;
}
return `${a}${a.endsWith("\n") || (a.endsWith("```") && !b.startsWith("`") && !b.startsWith("-# ")) ? "" : "\n"}${b}`;
})();
if (flushed && combined.length > maxLength) {
flushed.add(key ?? "");
return b;
} }
return `${a}${a.endsWith("\n") || (a.endsWith("```") && !b.startsWith("`") && !b.startsWith("-# ")) ? "" : "\n"}${b}`;
return combined;
} }
if (Array.isArray(a) && Array.isArray(b)) { if (Array.isArray(a) && Array.isArray(b)) {
@@ -139,7 +150,7 @@ function merge(a: unknown, b: unknown, key?: string): unknown {
}; };
for (const [k, v] of Object.entries(b)) { for (const [k, v] of Object.entries(b)) {
out[k] = k in out ? merge(out[k], v, k) : v; out[k] = k in out ? merge(out[k], v, k, maxLength, flushed) : v;
} }
return out; return out;