An open page takes up a new version by itself after a deploy (#388)
The live stream already came back after a deploy and the lists caught up, but the page kept running the script it had loaded, in a tab or the home-screen app, until reloaded by hand. Back after a drop, the page now asks for / again without the cache; another app.js named there is a new version. With nothing to lose it reloads, and the item that was open, carried across in sessionStorage whole since a read one is off the Unread list, is opened again. While an episode plays, a dialog is open, a search is set or a field is typed in, it offers a reload in a toast. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ let dropped=false;
|
||||
function connect(){
|
||||
sse=new EventSource('/api/events');
|
||||
// Back after a drop: whatever was sent meanwhile went to nobody.
|
||||
sse.onopen=()=>{ if(dropped){ dropped=false; catchUp(); } };
|
||||
sse.onopen=()=>{ if(dropped){ dropped=false; catchUp(); checkVersion(); } };
|
||||
const soon=(fn: ()=>void,ms=500)=>{ let t: number; return ()=>{ clearTimeout(t); t=setTimeout(fn,ms); }; };
|
||||
// Caught: a network gone when one fires (a laptop asleep) was an unhandled rejection (#219).
|
||||
const refreshEntries=soon(()=>{ if(S.feed) loadEntries(false,true).catch(()=>{}); });
|
||||
@@ -84,5 +84,51 @@ function connect(){
|
||||
};
|
||||
sse.onerror=()=>{ sse.close(); dropped=true; setTimeout(connect,4000); };
|
||||
}
|
||||
|
||||
/// A deploy of ipx-web drops the stream and the page kept running the script it loaded, in an
|
||||
/// open tab or the home-screen app, until reloaded by hand (#388). Back after a drop, the page
|
||||
/// asks for itself again: a different app.js named there is a new version.
|
||||
const runningScript=document.querySelector('script[src^="/app.js"]')?.getAttribute('src');
|
||||
async function checkVersion(){
|
||||
if(!runningScript) return;
|
||||
let page: string;
|
||||
// Mid-deploy, / can be a 502 or the sign-in page: no script named, nothing to compare.
|
||||
try{ const r=await fetch('/',{cache:'no-store'}); if(!r.ok) return; page=await r.text(); }catch{ return; }
|
||||
const live=page.match(/\/app\.js\?v=[0-9a-f]+/)?.[0];
|
||||
if(!live||live===runningScript) return;
|
||||
if(reloadWouldLose()) offerReload();
|
||||
else reloadKeepingPlace();
|
||||
}
|
||||
/// Something a reload throws away: an episode playing, a dialog open, a search or a field typed in.
|
||||
function reloadWouldLose(){
|
||||
const typing=document.activeElement instanceof HTMLTextAreaElement
|
||||
|| (document.activeElement instanceof HTMLInputElement && document.activeElement.type!=='range'
|
||||
&& document.activeElement.type!=='checkbox' && document.activeElement.value!=='');
|
||||
return !audio.paused || !!$('#modal')?.classList.contains('on') || !!S.q || typing;
|
||||
}
|
||||
/// The feed and tab come back from localStorage; the item open is carried across in the session,
|
||||
/// whole: read, it is off the Unread list the server sends after the reload.
|
||||
function reloadKeepingPlace(){
|
||||
try{ const e=cur(); if(e) sessionStorage.setItem('ipx.reopen',JSON.stringify(e)); }catch{}
|
||||
location.reload();
|
||||
}
|
||||
/// The item open before a reload for a new version, once, for showList to open again.
|
||||
function takeReopen(): Entry|null{
|
||||
try{
|
||||
const j=sessionStorage.getItem('ipx.reopen'); sessionStorage.removeItem('ipx.reopen');
|
||||
return j ? JSON.parse(j) as Entry : null;
|
||||
}catch{ return null; }
|
||||
}
|
||||
let offered=false;
|
||||
function offerReload(){
|
||||
if(offered) return;
|
||||
offered=true;
|
||||
const t=document.createElement('div'), b=document.createElement('button');
|
||||
t.className='toast'; t.setAttribute('role','status'); t.textContent='A new version of iPX is ready. ';
|
||||
b.type='button'; b.className='btn'; b.textContent='Reload';
|
||||
b.onclick=reloadKeepingPlace;
|
||||
t.appendChild(b);
|
||||
$('#toasts')?.appendChild(t);
|
||||
}
|
||||
connect();
|
||||
loadFeeds().then(preloadLists).catch(()=>{});
|
||||
|
||||
@@ -123,8 +123,12 @@ function showList(r: ListPage, append: boolean, asked: number){
|
||||
const gone=S.entries.filter(e=>kept(e)&&!entries.some(n=>entryKey(n)===entryKey(e)));
|
||||
entries=[...gone,...entries];
|
||||
}
|
||||
// Reloaded for a new version (#388): the item that was open, open again, where it was in the list.
|
||||
const back=append ? null : takeReopen();
|
||||
if(back && !entries.some(e=>entryKey(e)===entryKey(back))) entries=[back,...entries];
|
||||
S.entries = entries;
|
||||
renderEntries();
|
||||
if(back){ const e=S.entries.find(x=>entryKey(x)===entryKey(back)); if(e) selectEntry(e); }
|
||||
}
|
||||
/// A list being waited for: said with a spinner, so the click is seen to have been heard.
|
||||
function listLoading(box=$('#eps')){
|
||||
|
||||
Reference in New Issue
Block a user