Skip to content

Commit 3d7df2b

Browse files
committed
fix(discord): bound delivery retry delays
1 parent e394e0f commit 3d7df2b

2 files changed

Lines changed: 26 additions & 14 deletions

File tree

extensions/discord/src/delivery-retry.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
} from "openclaw/plugin-sdk/retry-runtime";
77
import { resolveDiscordAccount } from "./accounts.js";
88
import { DiscordError } from "./internal/discord.js";
9+
import { parseDiscordRetryAfterBodySeconds } from "./retry-after.js";
910

1011
const DISCORD_DELIVERY_RETRY_DEFAULTS = {
1112
attempts: 3,
@@ -22,27 +23,21 @@ export function isRetryableDiscordDeliveryError(err: unknown): boolean {
2223
return status === 429 || (status !== undefined && status >= 500);
2324
}
2425

25-
function getDiscordDeliveryRetryAfterMs(err: unknown): number | undefined {
26+
export function getDiscordDeliveryRetryAfterMs(err: unknown): number | undefined {
2627
if (!err || typeof err !== "object") {
2728
return undefined;
2829
}
29-
if (
30-
"retryAfter" in err &&
31-
typeof err.retryAfter === "number" &&
32-
Number.isFinite(err.retryAfter)
33-
) {
34-
return err.retryAfter * 1000;
30+
const retryAfterSeconds =
31+
"retryAfter" in err ? parseDiscordRetryAfterBodySeconds(err.retryAfter) : undefined;
32+
if (retryAfterSeconds !== undefined) {
33+
return retryAfterSeconds * 1000;
3534
}
3635
const retryAfterRaw = (err as { headers?: Record<string, string> }).headers?.["retry-after"];
3736
if (!retryAfterRaw) {
3837
return undefined;
3938
}
40-
const trimmedRetryAfter = retryAfterRaw.trim();
41-
if (!/^\d+(?:\.\d+)?$/.test(trimmedRetryAfter)) {
42-
return undefined;
43-
}
44-
const retryAfterMs = Number(trimmedRetryAfter) * 1000;
45-
return Number.isFinite(retryAfterMs) ? retryAfterMs : undefined;
39+
const headerSeconds = parseDiscordRetryAfterBodySeconds(retryAfterRaw);
40+
return headerSeconds === undefined ? undefined : headerSeconds * 1000;
4641
}
4742

4843
export async function withDiscordDeliveryRetry<T>(params: {

extensions/discord/src/retry.test.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { describe, expect, it, vi } from "vitest";
2-
import { isRetryableDiscordDeliveryError } from "./delivery-retry.js";
2+
import {
3+
getDiscordDeliveryRetryAfterMs,
4+
isRetryableDiscordDeliveryError,
5+
} from "./delivery-retry.js";
36
import { DiscordError, RateLimitError } from "./internal/discord.js";
47
import { createDiscordRetryRunner, isRetryableDiscordTransientError } from "./retry.js";
58

@@ -86,3 +89,17 @@ describe("isRetryableDiscordDeliveryError", () => {
8689
expect(isRetryableDiscordDeliveryError(err)).toBe(false);
8790
});
8891
});
92+
93+
describe("getDiscordDeliveryRetryAfterMs", () => {
94+
it("reads finite retry delays from delivery errors", () => {
95+
expect(getDiscordDeliveryRetryAfterMs({ retryAfter: 0.25 })).toBe(250);
96+
expect(getDiscordDeliveryRetryAfterMs({ headers: { "retry-after": "0.25" } })).toBe(250);
97+
});
98+
99+
it("rejects unsafe retry delay magnitudes", () => {
100+
expect(getDiscordDeliveryRetryAfterMs({ retryAfter: 9_007_199_254_741 })).toBeUndefined();
101+
expect(
102+
getDiscordDeliveryRetryAfterMs({ headers: { "retry-after": "9007199254741" } }),
103+
).toBeUndefined();
104+
});
105+
});

0 commit comments

Comments
 (0)