perf(update-check): throttle + single-flight the build check (batch81)
The check itself is cheap (one ~35-byte response, served from a variable the server reads once at startup), but it was hooked to BOTH `focus` and `visibilitychange` with no throttle — and a single alt-tab back into the app fires both, so every refocus cost two redundant requests. Now: one check per minute at most, never overlapping itself, and it stops checking entirely once a new build is known (the retry loop takes over). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+20
-7
@@ -1002,7 +1002,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<script src="/icons.js?v=6"></script>
|
<script src="/icons.js?v=6"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@twemoji/api@15.1.0/dist/twemoji.min.js" crossorigin="anonymous"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@twemoji/api@15.1.0/dist/twemoji.min.js" crossorigin="anonymous"></script>
|
||||||
<script>window.__BUILD='2026-07-14-batch80';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
<script>window.__BUILD='2026-07-14-batch81';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
||||||
// Render modern (Twemoji) emojis in place of the OS's flat ones. No-op if the CDN didn't load
|
// Render modern (Twemoji) emojis in place of the OS's flat ones. No-op if the CDN didn't load
|
||||||
// (emojis stay as plain Unicode). (#5)
|
// (emojis stay as plain Unicode). (#5)
|
||||||
function twemojify(el){ try{ if(el && window.twemoji) window.twemoji.parse(el, { folder:'svg', ext:'.svg' }); }catch(_){} }</script>
|
function twemojify(el){ try{ if(el && window.twemoji) window.twemoji.parse(el, { folder:'svg', ext:'.svg' }); }catch(_){} }</script>
|
||||||
@@ -2789,16 +2789,27 @@ function wireUpdateBanner(){
|
|||||||
// We poll the server's build marker and, as soon as it's SAFE, silently reload onto the new code. Safe =
|
// We poll the server's build marker and, as soon as it's SAFE, silently reload onto the new code. Safe =
|
||||||
// not in a call, no live screen session, nothing half-typed, no dialog open. If the user is busy we simply
|
// not in a call, no live screen session, nothing half-typed, no dialog open. If the user is busy we simply
|
||||||
// keep waiting and apply it the moment they're free (or when the window is next hidden).
|
// keep waiting and apply it the moment they're free (or when the window is next hidden).
|
||||||
let _newBuild=null, _autoRefreshTimer=null;
|
let _newBuild=null, _autoRefreshTimer=null, _lastBuildCheck=0, _buildChecking=false;
|
||||||
async function checkWebBuild(){
|
// Throttled + single-flight. `focus` and `visibilitychange` both fire on a single alt-tab back into the
|
||||||
|
// app, so an unthrottled check fired TWICE every time the window was refocused. One check per minute is
|
||||||
|
// plenty (the payload is ~35 bytes and the server answers from memory), and it never overlaps itself.
|
||||||
|
const BUILD_CHECK_MIN_MS=60*1000;
|
||||||
|
async function checkWebBuild(force){
|
||||||
|
if(_newBuild) return; // already know: the retry loop owns it now
|
||||||
|
const now=Date.now();
|
||||||
|
if(_buildChecking) return;
|
||||||
|
if(!force && now-_lastBuildCheck < BUILD_CHECK_MIN_MS) return;
|
||||||
|
_buildChecking=true; _lastBuildCheck=now;
|
||||||
try{
|
try{
|
||||||
const r=await fetch('/api/build',{cache:'no-store'}); if(!r.ok) return;
|
const r=await fetch('/api/build',{cache:'no-store'}); if(!r.ok) return;
|
||||||
const d=await r.json();
|
const d=await r.json();
|
||||||
if(d && d.build && window.__BUILD && d.build!==window.__BUILD){
|
if(d && d.build && window.__BUILD && d.build!==window.__BUILD){
|
||||||
if(_newBuild!==d.build){ _newBuild=d.build; console.log('[build] new version on server:', d.build, '(running', window.__BUILD+')'); }
|
_newBuild=d.build;
|
||||||
|
console.log('[build] new version on server:', d.build, '(running', window.__BUILD+')');
|
||||||
scheduleAutoRefresh();
|
scheduleAutoRefresh();
|
||||||
}
|
}
|
||||||
}catch(_){}
|
}catch(_){}
|
||||||
|
finally{ _buildChecking=false; }
|
||||||
}
|
}
|
||||||
// Never yank the page out from under someone mid-task.
|
// Never yank the page out from under someone mid-task.
|
||||||
function safeToRefresh(){
|
function safeToRefresh(){
|
||||||
@@ -2830,10 +2841,12 @@ async function hardReloadApp(){
|
|||||||
try{ if(window.caches && caches.keys){ const ks=await caches.keys(); await Promise.all(ks.map(k=>caches.delete(k).catch(()=>{}))); } }catch(_){}
|
try{ if(window.caches && caches.keys){ const ks=await caches.keys(); await Promise.all(ks.map(k=>caches.delete(k).catch(()=>{}))); } }catch(_){}
|
||||||
try{ location.replace(location.pathname+location.search); }catch(_){ location.reload(); }
|
try{ location.replace(location.pathname+location.search); }catch(_){ location.reload(); }
|
||||||
}
|
}
|
||||||
setInterval(checkWebBuild, 5*60*1000); // every 5 min
|
// Cheap by design: one ~35-byte request, answered from a variable the server read at startup. The
|
||||||
window.addEventListener('focus', checkWebBuild); // and whenever the user comes back to the app
|
// throttle above means refocusing the app repeatedly costs nothing.
|
||||||
|
setInterval(()=>checkWebBuild(true), 5*60*1000); // every 5 min (the interval IS the schedule)
|
||||||
|
window.addEventListener('focus', ()=>checkWebBuild()); // coming back to the app (throttled)
|
||||||
document.addEventListener('visibilitychange', ()=>{ if(!document.hidden) checkWebBuild(); });
|
document.addEventListener('visibilitychange', ()=>{ if(!document.hidden) checkWebBuild(); });
|
||||||
setTimeout(checkWebBuild, 3000); // shortly after boot
|
setTimeout(()=>checkWebBuild(true), 3000); // shortly after boot
|
||||||
// Deliberately NO banner and NO toast: a web build is an implementation detail. Surfacing it would make
|
// Deliberately NO banner and NO toast: a web build is an implementation detail. Surfacing it would make
|
||||||
// users think about "web build vs app version". It just updates.
|
// users think about "web build vs app version". It just updates.
|
||||||
// Small 'i' badge on the profile button when an update is pending; clicking opens Settings.
|
// Small 'i' badge on the profile button when an update is pending; clicking opens Settings.
|
||||||
|
|||||||
Reference in New Issue
Block a user