fix(desktop): notification click no longer reloads to a dead page (0.1.9/batch62)

Root cause of the persistent "notification opens a dead page / no Join" bug:
openFromNotif gated the in-place open on `window.ME`, but ME is declared with
`let` — which never creates a global property — so window.ME was ALWAYS
undefined and every click hit the full-page-reload fallback. Use bare `ME`.

Also:
- Notifications fire instantly: never block on the DP download. Use the photo
  only if already cached; warm the cache in the background + pre-warm all
  contact DPs on chat load (precache-avatars IPC). Removes the ~2.5s lag.
- Wire call Join/Decline handlers BEFORE firing the OS notification so Join is
  live the instant the invite popup appears (was dead until the toast settled).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-08 18:00:56 +05:30
parent 6ed0fa0ea0
commit 62a5f750af
4 changed files with 39 additions and 12 deletions
+18 -3
View File
@@ -840,7 +840,7 @@
<body>
<script src="/icons.js?v=5"></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-08-batch61';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
<script>window.__BUILD='2026-07-08-batch62';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
// (emojis stay as plain Unicode). (#5)
function twemojify(el){ try{ if(el && window.twemoji) window.twemoji.parse(el, { folder:'svg', ext:'.svg' }); }catch(_){} }</script>
@@ -1398,6 +1398,17 @@ async function loadSidebar(){
ROWS=items;
renderChats(searchVal());
updateRailUnread();
prewarmNotifAvatars();
}
// Desktop only: hand the shell every contact/group DP URL so it caches them up front — the first
// notification (chat OR call) from anyone then shows their photo instantly, with no per-toast wait.
let _avatarsWarmed=false;
function prewarmNotifAvatars(){
try{
const n=window.bizConnectNative; if(!n||!n.precacheAvatars||_avatarsWarmed) return;
const urls=[...new Set((ROWS||[]).map(r=>r.avatar).filter(u=>u && /^https?:\/\//i.test(u)))];
if(urls.length){ _avatarsWarmed=true; n.precacheAvatars(urls); }
}catch(_){}
}
// ----- conversation view -----
@@ -1794,11 +1805,13 @@ function showCallInvite(room, byName, ret, sub, quiet){
+'<button class="ci-join">'+ic('video',16)+' Join</button>'
+'<button class="ci-decline" title="Decline">'+ic('callEnd',16)+' Decline</button>';
document.body.appendChild(el);
if(!quiet){ try{ notify('📞 '+who, (sub?('Group call · '+sub):'is calling you'), ret&&ret.kind, ret&&ret.id, {persistent:true}); }catch(_){} } // OS notification too (skip when merely re-showing on chat open)
let closed=false;
const close=()=>{ if(closed) return; closed=true; try{ el.remove(); }catch(_){} stopRing(); };
// Wire Join/Decline BEFORE firing the OS notification so the buttons are live the instant the popup
// shows (previously notify() ran first — the click did nothing until the toast plumbing settled).
el.querySelector('.ci-join').onclick=()=>{ close(); meetReturn=ret||null; switchTab('meeting'); enterMeeting(room); };
el.querySelector('.ci-decline').onclick=()=>{ close(); if(ret&&ret.kind==='dm'){ postJSON('/api/calls/decline',{room}).catch(()=>{}); } }; // 1:1 → notify caller; group → silent
if(!quiet){ try{ notify('📞 '+who, (sub?('Group call · '+sub):'is calling you'), ret&&ret.kind, ret&&ret.id, {persistent:true}); }catch(_){} } // OS notification too (skip when merely re-showing on chat open)
setTimeout(close, 45000);
}
// Scheduled-meeting invitation (toast + the meeting shows up in their list).
@@ -2353,7 +2366,9 @@ function openFromNotif(kind,id){
// The app is already running (the notification came from it) → open the chat IN-PLACE. A full-page
// reload was slow (#19) and dropped live state: the sidebar hadn't reloaded so the header showed
// "Conversation" with no DP (#7), and an incoming call's Join/invite popup was lost (#2).
if(window.ME && ME.id && typeof selectChat==='function'){
// NB: use bare `ME`, NOT `window.ME` — ME is declared with `let`, which does NOT create a global
// property, so `window.ME` was always undefined and EVERY click hit the reload fallback below.
if(ME && ME.id && typeof selectChat==='function'){
if(id){ try{ switchTab('chat'); }catch(_){} try{ selectChat(kind||'dm', id); }catch(_){} }
return;
}