mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-25 11:10:18 +00:00
Merge pull request #2318 into stage-361
fix: defer mobile stream errors while tab is hidden (Michaelyklam, closes #2307) # Conflicts: # CHANGELOG.md
This commit is contained in:
@@ -592,6 +592,54 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
// ── Shared SSE handler wiring (used for initial connection and reconnect) ──
|
||||
let _reconnectAttempted=false;
|
||||
let _terminalStateReached=false;
|
||||
let _deferredStreamRecoveryBound=false;
|
||||
|
||||
function _pageHiddenForStreamError(){
|
||||
return (typeof document!=='undefined'&&document.visibilityState==='hidden')||
|
||||
(typeof document!=='undefined'&&document.wasDiscarded===true);
|
||||
}
|
||||
|
||||
function _reattachOrRestoreAfterDeferredStreamError(){
|
||||
if(_terminalStateReached||_streamFinalized) return;
|
||||
(async()=>{
|
||||
try{
|
||||
if(streamId){
|
||||
const st=await api(`/api/chat/stream/status?stream_id=${encodeURIComponent(streamId)}`);
|
||||
if(st.active){
|
||||
setComposerStatus('Reconnected');
|
||||
_wireSSE(new EventSource(new URL(`api/chat/stream?stream_id=${encodeURIComponent(streamId)}`,document.baseURI||location.href).href,{withCredentials:true}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}catch(_){
|
||||
if(_deferStreamErrorIfOffline()||_pageHiddenForStreamError()) return;
|
||||
}
|
||||
if(await _restoreSettledSession()) return;
|
||||
if(_deferStreamErrorIfOffline()||_pageHiddenForStreamError()) return;
|
||||
_handleStreamError();
|
||||
})();
|
||||
}
|
||||
|
||||
function _deferStreamErrorIfPageHidden(){
|
||||
if(!_pageHiddenForStreamError()) return false;
|
||||
setComposerStatus('Connection paused. Reconnecting when this tab returns…');
|
||||
if(S.session&&S.session.session_id===activeSid&&streamId) S.activeStreamId=streamId;
|
||||
if(!_deferredStreamRecoveryBound){
|
||||
_deferredStreamRecoveryBound=true;
|
||||
const resume=()=>{
|
||||
if(_pageHiddenForStreamError()) return;
|
||||
window.removeEventListener('focus',resume);
|
||||
window.removeEventListener('pageshow',resume);
|
||||
document.removeEventListener('visibilitychange',resume);
|
||||
_deferredStreamRecoveryBound=false;
|
||||
_reattachOrRestoreAfterDeferredStreamError();
|
||||
};
|
||||
document.addEventListener('visibilitychange',resume);
|
||||
window.addEventListener('focus',resume);
|
||||
window.addEventListener('pageshow',resume);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Bug A fix (#631): track whether the stream has been finalized so any rAF
|
||||
// scheduled by a trailing 'token'/'reasoning' event that arrives in the same
|
||||
@@ -1633,6 +1681,7 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
source.addEventListener('error',async e=>{
|
||||
source.close();
|
||||
if(_deferStreamErrorIfOffline()) return;
|
||||
if(_deferStreamErrorIfPageHidden()) return;
|
||||
if(_terminalStateReached || _streamFinalized){
|
||||
_closeSource();
|
||||
return;
|
||||
@@ -1654,12 +1703,14 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
}
|
||||
if(await _restoreSettledSession()) return;
|
||||
if(_deferStreamErrorIfOffline()) return;
|
||||
if(_deferStreamErrorIfPageHidden()) return;
|
||||
_handleStreamError();
|
||||
},1500);
|
||||
return;
|
||||
}
|
||||
if(await _restoreSettledSession()) return;
|
||||
if(_deferStreamErrorIfOffline()) return;
|
||||
if(_deferStreamErrorIfPageHidden()) return;
|
||||
_handleStreamError();
|
||||
});
|
||||
|
||||
|
||||
@@ -70,3 +70,24 @@ def test_sse_network_error_defers_to_offline_banner_instead_of_inline_error():
|
||||
assert "if(_deferStreamErrorIfOffline()) return;" in MESSAGES_JS
|
||||
error_handler = MESSAGES_JS.split("source.addEventListener('error',async e=>{", 1)[1].split("source.addEventListener('cancel'", 1)[0]
|
||||
assert error_handler.find("_deferStreamErrorIfOffline()") < error_handler.rfind("_handleStreamError()")
|
||||
|
||||
|
||||
def test_sse_error_defers_while_page_hidden_until_tab_returns():
|
||||
assert "function _deferStreamErrorIfPageHidden()" in MESSAGES_JS
|
||||
assert "document.visibilityState==='hidden'" in MESSAGES_JS
|
||||
assert "document.wasDiscarded===true" in MESSAGES_JS
|
||||
assert "Connection paused. Reconnecting when this tab returns…" in MESSAGES_JS
|
||||
assert "document.addEventListener('visibilitychange',resume)" in MESSAGES_JS
|
||||
assert "window.addEventListener('pageshow',resume)" in MESSAGES_JS
|
||||
error_handler = MESSAGES_JS.split("source.addEventListener('error',async e=>{", 1)[1].split("source.addEventListener('cancel'", 1)[0]
|
||||
assert "if(_deferStreamErrorIfPageHidden()) return;" in error_handler
|
||||
assert error_handler.find("_deferStreamErrorIfPageHidden()") < error_handler.rfind("_handleStreamError()")
|
||||
|
||||
|
||||
def test_deferred_hidden_stream_error_reattaches_or_restores_before_inline_error():
|
||||
recovery_block = MESSAGES_JS.split("function _reattachOrRestoreAfterDeferredStreamError(){", 1)[1].split("function _deferStreamErrorIfPageHidden()", 1)[0]
|
||||
assert "api(`/api/chat/stream/status?stream_id=${encodeURIComponent(streamId)}`)" in recovery_block
|
||||
assert "if(st.active)" in recovery_block
|
||||
assert "_wireSSE(new EventSource" in recovery_block
|
||||
assert "if(await _restoreSettledSession()) return;" in recovery_block
|
||||
assert recovery_block.find("if(await _restoreSettledSession()) return;") < recovery_block.rfind("_handleStreamError()")
|
||||
|
||||
Reference in New Issue
Block a user