/* ---------------- live events ---------------- */ let sse; function connect(){ sse=new EventSource('/api/events'); const soon=(fn,ms=500)=>{ let t; return ()=>{ clearTimeout(t); t=setTimeout(fn,ms); }; }; const refreshEntries=soon(()=>{ if(S.feed) loadEntries(); }); // Every scan's events reach everyone; only this person's feeds are theirs to show or refresh. const mine=id=>S.feeds.some(f=>f.id===id); sse.onmessage=m=>{ let ev; try{ ev=JSON.parse(m.data) }catch{ return } if(ev.ev==='progress'){ const pct=ev.total?ev.done/ev.total*100:0; // Only the row actually downloading. Without the enclosure id this used to paint // every pending bar at once, so adding a feed looked like it was fetching the lot. const bar=document.querySelector(`.dlbar[data-bar="${ev.enclosure}"] i`); if(bar){ bar.style.width=pct+'%'; bar.parentElement.classList.add('live'); } $('#count') && ($('#count').textContent=`downloading ${ev.file} — ${pct.toFixed(0)}%`); } else if(ev.ev==='download_done'){ const bar=document.querySelector(`.dlbar[data-bar="${ev.enclosure}"]`); // Said only for a file on screen, as one downloaded by hand is: the scheduled downloads of // everyone's feeds used to announce themselves to everyone. if(bar){ bar.classList.remove('live'); toast('Downloaded '+ev.path.split('/').pop()); } refreshEntries(); } else if(ev.ev==='download_error'){ const bar=document.querySelector(`.dlbar[data-bar="${ev.enclosure}"]`); if(bar) bar.classList.remove('live'); toast('Download failed: '+ev.msg,true); refreshEntries(); } // A spinner on the feed's row while it is checked, in place of a toast per feed (issue #37). else if(ev.ev==='feed_start') setScanning(ev.feed,true); else if(ev.ev==='feed_skip') setScanning(ev.feed,false); else if(ev.ev==='feed_done'){ setScanning(ev.feed,false); if(!mine(ev.feed)) return; if(ev.feed===S.feed||S.feed===':all') refreshEntries(); } // No toast: a scan of every feed raised one per failure, to everyone. The feed list's // red ! marks the feed instead, and its page says why. else if(ev.ev==='feed_error') setScanning(ev.feed,false); // The server sends a feed's new row after anything changes it (counts, error, last check), // only to those who subscribe; the page used to reload the whole list after each event. else if(ev.ev==='feed_row') patchFeed(ev.row); // Only a scan that checked something: the scheduler scans every minute, due or not, and // every open page reloaded the whole list each time (#103). // Rows came as feed_row events. ponytail: a feed an OPML drops stays in the list until the // page reloads; reload the list here when a scan synced an OPML if that ever matters. else if(ev.ev==='scan_done'){ scanning.clear(); paintScanning(); if(ev.feeds) refreshEntries(); } }; sse.onerror=()=>{ sse.close(); setTimeout(connect,4000); }; } connect(); loadFeeds();