From ad1196173ad34c68f808da79c0e7b3c33c2e3c8e Mon Sep 17 00:00:00 2001 From: Sarto Date: Sat, 2 May 2026 00:37:47 +0330 Subject: [PATCH] fix dns media downlaod timeouts and video play in android --- internal/web/relay_info.go | 12 ++++++++---- internal/web/static/index.html | 29 +++++++++++++++++++---------- internal/web/web.go | 15 ++++++++++----- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/internal/web/relay_info.go b/internal/web/relay_info.go index f6998d9..f880b1a 100644 --- a/internal/web/relay_info.go +++ b/internal/web/relay_info.go @@ -41,16 +41,18 @@ func (e *ghRateLimitError) Error() string { // bionic libc → netd → the device's actual DNS, the same path any other // Android app uses. On desktop the OS resolver is similarly fine. var relayHTTPClient = &http.Client{ - Timeout: 30 * time.Second, + // Per-request budget — large enough to cover multi-MB downloads + // over a slow link without hugging short-circuit timeouts. + Timeout: 5 * time.Minute, Transport: &http.Transport{ DialContext: (&net.Dialer{ - Timeout: 10 * time.Second, + Timeout: 15 * time.Second, KeepAlive: 30 * time.Second, }).DialContext, ForceAttemptHTTP2: true, MaxIdleConns: 10, IdleConnTimeout: 90 * time.Second, - TLSHandshakeTimeout: 10 * time.Second, + TLSHandshakeTimeout: 15 * time.Second, ExpectContinueTimeout: 1 * time.Second, }, } @@ -131,7 +133,9 @@ func (s *Server) serveFromGitHubRelay(w http.ResponseWriter, r *http.Request, si return false } - ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second) + // Long enough to cover a multi-MB GitHub fetch over a slow link, plus + // a multi-block DNS-tunneled relay-info lookup if the cache is empty. + ctx, cancel := context.WithTimeout(r.Context(), 10*time.Minute) defer cancel() info, err := rc.get(ctx, fetcher) diff --git a/internal/web/static/index.html b/internal/web/static/index.html index 3a07929..ed4f687 100644 --- a/internal/web/static/index.html +++ b/internal/web/static/index.html @@ -3374,12 +3374,17 @@ await selectChannel(1); return } if (data && typeof data === 'object' && data.type === 'no_changes') { - // Only show the toast if the user is still viewing the channel - // that reported no changes — silently drop it if they switched. - if (data.channel === selectedChannel) showToast(t('no_new_messages')); + // Only show the toast if the user explicitly asked for this + // refresh AND is still on the same channel. Channel-open and + // auto-refresh ticks stay silent. + if (data.channel === manualRefreshChannel && data.channel === selectedChannel) { + showToast(t('no_new_messages')); + } + if (data.channel === manualRefreshChannel) manualRefreshChannel = 0; if (snapChannel > 0) { delete refreshingChannels[snapChannel]; var fb = document.getElementById('prog-fetch-ch-' + snapChannel); if (fb) fb.remove() } } else if (data && typeof data === 'object' && data.channel) { delete refreshingChannels[data.channel]; var fb2 = document.getElementById('prog-fetch-ch-' + data.channel); if (fb2) fb2.remove(); + if (data.channel === manualRefreshChannel) manualRefreshChannel = 0; // Only re-render if the user is STILL on that channel right now — // selectedChannel may have changed during the awaited loadChannels. if (data.channel === selectedChannel) await loadMessages(data.channel) @@ -4921,6 +4926,10 @@ showImageLightbox(entry.url, mediaTagLabel(tag)); return; } + if (mediaIsPlayableTag(tag)) { + showMediaPlayer(entry, tag); + return; + } if (androidBridge && androidBridge.openMedia) { var fname = mediaFilenameFor(msgID, tag, entry.mime); try { @@ -4929,13 +4938,6 @@ } catch (e) { } return; } - // Browser path: play video/audio inline in a lightbox instead of - // opening a blob URL in a new tab — some browsers refuse to render - // blob:video and download it as a hash-named file with no extension. - if (mediaIsPlayableTag(tag)) { - showMediaPlayer(entry, tag); - return; - } try { var a = document.createElement('a'); a.href = entry.url; @@ -5596,6 +5598,12 @@ } tick(); nextFetchInterval = setInterval(tick, 1000); } + // Channel number for which the user explicitly clicked Refresh. + // The "no new messages" toast fires only for that channel; auto- + // refresh ticks and channel-open refreshes leave it at 0 so the + // toast stays silent. + var manualRefreshChannel = 0; + async function doRefreshUI() { var btn = document.getElementById('refreshBtn'); btn.style.animation = 'spin .8s linear'; @@ -5605,6 +5613,7 @@ var name = (ch && (ch.Name || ch.name)) || 'Channel ' + selectedChannel; showChannelFetchProgress(selectedChannel, name); refreshingChannels[selectedChannel] = true; + manualRefreshChannel = selectedChannel; } doRefresh(false); setTimeout(function () { btn.style.animation = '' }, 800); diff --git a/internal/web/web.go b/internal/web/web.go index 44082ad..60ab9af 100644 --- a/internal/web/web.go +++ b/internal/web/web.go @@ -319,11 +319,16 @@ func (s *Server) Run() error { } srv := &http.Server{ - Addr: addr, - Handler: handler, - ReadTimeout: 30 * time.Second, - WriteTimeout: 60 * time.Second, - IdleTimeout: 120 * time.Second, + Addr: addr, + Handler: handler, + // ReadHeaderTimeout protects against slow-loris on the request + // header. The body itself can be large (Telegram send-message + // uploads), and the response can be slow (DNS-tunneled media + // streams take many minutes for multi-block files in censored + // networks). So zero out ReadTimeout/WriteTimeout and bound the + // idle period on the connection itself. + ReadHeaderTimeout: 30 * time.Second, + IdleTimeout: 30 * time.Minute, } return srv.ListenAndServe() }