Two real bugs surfaced after the original retry helper:
1. Healthy long-polls were tripping our 15s per-attempt timeout. The
getUpdates request asks Telegram for a 30s server-side long-poll, so
our internal timeout aborted every healthy connection and turned it
into ABORT_ERR -> retry -> exhausted -> "disconnected", with no
auto-reconnect. The fix: long-poll bypasses the retry helper and uses
a 60s per-attempt timeout, since the poll loop already retries by
re-entering after sleep().
2. Our own internal AbortController timeout produced a DOMException
AbortError indistinguishable from a caller-abort. The poll loop's
shouldStopTelegramPolling treated that as "user wants to stop" and
exited. Now fetchWithRetry normalizes its own timeout into a tagged
Error with code ATTEMPT_TIMEOUT, so only real caller-aborts surface
as AbortError upstream.
Also: per-attempt timeout default dropped 15s -> 5s, retry budget
dropped from [500, 2000] to [500] (so 2 attempts, not 3) for outbound
sends, since they serialize and a long retry tail makes the bridge feel
hung.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wrap fetch calls in callTelegram, callTelegramMultipart, and
downloadTelegramFile with bounded retry (max 3 attempts, 500ms/2000ms
backoff with jitter) on transient network codes and a per-attempt 15s
AbortController timeout so a stuck connection can't wedge the bridge
indefinitely. HTTP 4xx/5xx still surface as before.
Add a process-level unhandledRejection handler so a stray rejection from
a fire-and-forget void f() (e.g. timer-driven preview flush) can't crash
the host under Node 22's default unhandledRejection=throw - which is how
a remote session got dropped. Also tighten the two timer-callback voids
that can fire after their turn's try/catch is gone.