Skip to content

Commit 2d99066

Browse files
Merge 6552165 into 5880e0a
2 parents 5880e0a + 6552165 commit 2d99066

4 files changed

Lines changed: 36 additions & 8 deletions

File tree

extensions/telegram/src/fetch.test.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -957,7 +957,7 @@ describe("resolveTelegramFetch", () => {
957957
expect(eighthDispatcher).toBe(firstDispatcher);
958958
expect(ninthDispatcher).toBe(firstDispatcher);
959959
expectPinnedFallbackIpDispatcher(3);
960-
expectLoggerMessageContaining(loggerWarn, "fetch fallback: DNS-resolved IP unreachable");
960+
expectLoggerMessageContaining(loggerWarn, "fetch fallback: primary connection path failed");
961961
expectLoggerMessageContaining(
962962
loggerDebug,
963963
"fetch fallback: recovered from attempt 2 to attempt 0",
@@ -1193,6 +1193,31 @@ describe("resolveTelegramFetch", () => {
11931193
expect(undiciFetch).toHaveBeenCalledTimes(1);
11941194
});
11951195

1196+
it("does not automatically retry structured EADDRNOTAVAIL fetch failures", async () => {
1197+
const fetchError = buildFetchFallbackError("EADDRNOTAVAIL");
1198+
undiciFetch.mockRejectedValue(fetchError);
1199+
1200+
const resolved = resolveTelegramFetchOrThrow(undefined, STICKY_IPV4_FALLBACK_NETWORK);
1201+
1202+
await expect(resolved("https://api.telegram.org/botx/sendMessage")).rejects.toThrow(
1203+
"fetch failed",
1204+
);
1205+
1206+
expect(undiciFetch).toHaveBeenCalledTimes(1);
1207+
});
1208+
1209+
it("preserves EADDRNOTAVAIL in forced fallback diagnostics", () => {
1210+
const transport = resolveTelegramTransport(undefined, STICKY_IPV4_FALLBACK_NETWORK);
1211+
const fetchError = buildFetchFallbackError("EADDRNOTAVAIL");
1212+
1213+
expect(transport.forceFallback?.("probe timeout/network error", fetchError)).toBe(true);
1214+
expect(transport.forceFallback?.("probe timeout/network error", fetchError)).toBe(true);
1215+
1216+
expectLoggerMessageContaining(loggerWarn, "primary connection path failed");
1217+
expectLoggerMessageContaining(loggerWarn, "codes=EADDRNOTAVAIL");
1218+
expectNoLoggerMessageContaining(loggerWarn, "DNS-resolved IP unreachable");
1219+
});
1220+
11961221
it("retries sticky fallback when the local network is down during connect", async () => {
11971222
undiciFetch
11981223
.mockRejectedValueOnce(buildFetchFallbackError("ENETDOWN"))

extensions/telegram/src/fetch.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -488,9 +488,10 @@ export type TelegramTransport = {
488488
dispatcherAttempts?: TelegramDispatcherAttempt[];
489489
/**
490490
* Promote this transport to its next fallback dispatcher before the next
491-
* request. Returns false when no fallback path exists.
491+
* request. The original error, when available, is retained in diagnostics.
492+
* Returns false when no fallback path exists.
492493
*/
493-
forceFallback?: (reason: string) => boolean;
494+
forceFallback?: (reason: string, err?: unknown) => boolean;
494495
/**
495496
* Release all dispatchers owned by this transport and the TCP sockets they
496497
* hold. Safe to call multiple times; subsequent calls resolve immediately.
@@ -563,7 +564,8 @@ function createTelegramTransportAttempts(params: {
563564
},
564565
exportAttempt: { dispatcherPolicy: fallbackIpPolicy },
565566
logLevel: "warn",
566-
logMessage: "fetch fallback: DNS-resolved IP unreachable; trying alternative Telegram API IP",
567+
logMessage:
568+
"fetch fallback: primary connection path failed; trying alternative Telegram API IP",
567569
});
568570

569571
return attempts;
@@ -864,8 +866,8 @@ export function resolveTelegramTransport(
864866
fetch: resolvedFetch,
865867
sourceFetch,
866868
dispatcherAttempts: transportAttempts.map((attempt) => attempt.exportAttempt),
867-
forceFallback: (reason: string) =>
868-
promoteStickyAttempt(stickyAttemptIndex + 1, new Error("forced fallback"), reason),
869+
forceFallback: (reason: string, err?: unknown) =>
870+
promoteStickyAttempt(stickyAttemptIndex + 1, err ?? new Error("forced fallback"), reason),
869871
close,
870872
};
871873
}

extensions/telegram/src/probe.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ describe("probeTelegram retry logic", () => {
362362

363363
const result = await probePromise;
364364
expect(result.ok).toBe(true);
365-
expect(localForceFallback).toHaveBeenCalledWith("probe timeout/network error");
365+
expect(localForceFallback).toHaveBeenCalledWith("probe timeout/network error", timeoutError);
366366
expect(fetchMock).toHaveBeenCalledTimes(3); // 1 failed + 1 getMe success + 1 webhook
367367
} finally {
368368
vi.useRealTimers();

extensions/telegram/src/probe.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ export async function probeTelegram(
162162
// On timeout or network error, promote the transport to its IPv4
163163
// fallback dispatcher so the next retry (and all future probes
164164
// sharing this cached transport) skip the stalled IPv6 path.
165-
transport.forceFallback?.("probe timeout/network error");
165+
// Keep the original socket code in transport fallback diagnostics.
166+
transport.forceFallback?.("probe timeout/network error", err);
166167
if (i < 2) {
167168
const remainingAfterAttemptMs = resolveRemainingBudgetMs();
168169
if (remainingAfterAttemptMs <= 0) {

0 commit comments

Comments
 (0)