From f6962a0b4aff48f9e5d70aae47d2bfbb65258050 Mon Sep 17 00:00:00 2001 From: sravan Date: Wed, 8 Jul 2026 11:55:30 +0530 Subject: [PATCH] fix: screen-share mirror (#9), sidebar tick sync (#1), desktop notification reload (#3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #9: sharing your whole screen no longer creates a 'hall of mirrors' — your own screen is NOT shown back to you (you see your camera/avatar + a 'You're sharing' badge); other participants still see your screen. Your own tile is never the local stage. - #1: opening a DM now syncs the sidebar tick to the thread's true delivered/read state, so it can't show a single tick while the conversation shows double. - #3: nativePlatform() now also detects older desktop builds that expose bizConnectNative but not __NATIVE__. Those were wrongly subscribing to Web Push, so the service worker's openWindow RELOADED the page on notification click (losing the header DP). Desktop now uses only the native toast → opens in-place. Co-Authored-By: Claude Opus 4.8 --- server/public/home.html | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/server/public/home.html b/server/public/home.html index efd6fbc..8485e09 100644 --- a/server/public/home.html +++ b/server/public/home.html @@ -349,6 +349,7 @@ .meet{display:flex;flex-direction:column;width:100%;height:100%;} .meet-grid{flex:1;min-height:0;display:grid;gap:.6rem;padding:.8rem;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));align-content:center;overflow:hidden;background:#0b1220;} /* #16: center tiles + no stray scrollbar on 1:1/small calls */ .meet-tile{position:relative;background:#0b1220;border-radius:12px;overflow:hidden;aspect-ratio:4/3;border:1px solid #1e293b;transition:box-shadow .12s,border-color .12s;} + .meet-tile .meet-sharing-self{position:absolute;left:0;right:0;bottom:0;display:flex;align-items:center;justify-content:center;gap:.3rem;font-size:.72rem;color:#fff;background:rgba(37,99,235,.9);padding:.25rem;} .meet-tile.waiting{border-color:#334155;} .meet-tile.waiting .ringing{position:absolute;left:0;right:0;bottom:0;display:flex;align-items:center;justify-content:center;gap:.3rem;font-size:.8rem;color:#cbd5e1;background:rgba(0,0,0,.35);padding:.35rem;animation:ringPulse 1.4s ease-in-out infinite;} @keyframes ringPulse{0%,100%{opacity:.6;}50%{opacity:1;}} @@ -826,7 +827,7 @@ - @@ -1692,7 +1693,14 @@ function onChatDeleted(d){ if(!d||!d.id) return; markMsgDeleted(d.id); try{ load function onChatDelivered(d){ if(!d||!d.id) return; const m=THREAD.find(x=>x.id===d.id); if(m && !m.delivered_at){ m.delivered_at=Date.now(); updateBubble(m); } // #6: mirror the delivered state onto the chat-list tick (double grey), unless it's already read. - const it=d.with?rowFor('dm', d.with):null; if(it && it.last_from_me && it.last_status==='sent'){ it.last_status='delivered'; renderChats(searchVal()); } + const it=d.with?rowFor('dm', d.with):null; if(it && it.last_from_me && it.last_status!=='read' && it.last_status!=='delivered'){ it.last_status='delivered'; renderChats(searchVal()); } +} +// #1: opening a DM loads the true delivered/read state into the thread; make the sidebar tick match +// (it can lag when a delivered/read event arrived before the row reflected my latest message). +function syncDmRowTick(id){ + const it=rowFor('dm', id); if(!it || !THREAD.length) return; + const last=[...THREAD].reverse().find(m=>!m.system && !m.deleted); if(!last) return; + if(last.from===ME.id){ const st=last.read_at?'read':(last.delivered_at?'delivered':'sent'); if(it.last_status!==st || it.last_msg_id!==last.id){ it.last_status=st; it.last_from_me=true; it.last_msg_id=last.id; renderChats(searchVal()); } } } // Group read: a member opened the group → add them to "Seen by" on my messages up to that time. function onGroupRead(d){ if(!d||!d.group) return; if(selected && selected.kind==='group' && selected.id===d.group){ THREAD.forEach(m=>{ if(m.from===ME.id && m.created_at<=d.at){ m.seenBy=m.seenBy||[]; if(d.byName && !m.seenBy.includes(d.byName)){ m.seenBy.push(d.byName); updateBubble(m); } } }); refreshGroupRowTick(d.group); } } @@ -1982,6 +1990,7 @@ async function openConvo(kind,id){ THREAD_CACHE.set(ckey, THREAD.slice()); _hasMoreOlder = msgs.length >= PAGE; _loadingOlder=false; // a full page back means there may be older history renderThread(); + if(kind==='dm') syncDmRowTick(id); // #1: keep the sidebar tick consistent with the loaded thread // #3: if there were unread messages, drop a "New messages" divider, scroll to the first unread, // and show the "jump to newest" arrow so the user can return to the bottom. if(_unreadN>0 && THREAD.length>=_unreadN){ @@ -2221,7 +2230,7 @@ function urlB64ToUint8(base64){ const pad='='.repeat((4-base64.length%4)%4); con // plugin. We register the OS device token with /api/v1/devices so the server can deliver via // FCM/APNs (see server/push.js). No bundler needed — plugins live on Capacitor.Plugins. function capPlugin(name){ const C=window.Capacitor; return (C && C.isNativePlatform && C.isNativePlatform() && C.Plugins && C.Plugins[name]) ? C.Plugins[name] : null; } -function nativePlatform(){ const C=window.Capacitor; if(C && C.getPlatform){ const p=C.getPlatform(); if(p==='ios'||p==='android') return p; } if(window.__NATIVE__==='desktop') return 'desktop'; return ''; } +function nativePlatform(){ const C=window.Capacitor; if(C && C.getPlatform){ const p=C.getPlatform(); if(p==='ios'||p==='android') return p; } if(window.__NATIVE__==='desktop' || (window.bizConnectNative && window.bizConnectNative.platform==='desktop')) return 'desktop'; return ''; } // also detect older desktop builds that expose bizConnectNative but not __NATIVE__ (#3: those wrongly subscribed Web Push → SW openWindow reloaded on notification click) // Record this install against the signed-in user (desktop shell exposes a stable installId). // Fires post-login; the request carries the session so admins can see who installed the app. function reportInstall(){ @@ -3046,7 +3055,9 @@ async function toggleScreen(){ const st=pub&&pub.track&&pub.track.mediaStreamTrack; if(!st){ return; } meetScreen=true; meetScreenStream=new MediaStream([st]); try{ st.addEventListener('ended', ()=>stopScreen()); }catch(_){} // browser's native "Stop sharing" bar - addTile('__local', meetScreenStream, ((ME&&ME.name)?ME.name:'You')+' (screen)', true); setTileScreen('__local', true); + // #9: keep MY tile on camera/avatar — do NOT show my own screen back to me. Sharing the whole + // screen would then capture the tile showing it → infinite "hall of mirrors". Others still see it. + addTile('__local', meetLocalStream, (ME&&ME.name)?ME.name:'You', true); setLocalSharing(true); updateScreenBtn(); meetSend({ type:'meeting-screen', on:true }); return; } let ds; try{ ds=await navigator.mediaDevices.getDisplayMedia({ video:true, audio:false }); } @@ -3058,22 +3069,29 @@ async function toggleScreen(){ if(p.vsender){ try{ await p.vsender.replaceTrack(track); }catch(_){} } else { try{ p.vsender=p.pc.addTrack(track, meetLocalStream); const off=await p.pc.createOffer(); await p.pc.setLocalDescription(off); meetSend({type:'meeting-signal',to:pid,data:{sdp:p.pc.localDescription}}); }catch(_){} } } - addTile('__local', ds, ((ME&&ME.name)?ME.name:'You')+' (screen)', true); setTileScreen('__local', true); + addTile('__local', meetLocalStream, (ME&&ME.name)?ME.name:'You', true); setLocalSharing(true); // #9: keep camera/avatar locally, not my own screen updateScreenBtn(); meetSend({ type:'meeting-screen', on:true }); } +// #9: a badge on MY tile that I'm sharing (instead of showing my own screen, which recurses). +function setLocalSharing(on){ + const t=document.getElementById('meet-tile-__local'); if(!t) return; + let b=t.querySelector('.meet-sharing-self'); + if(on){ if(!b){ b=document.createElement('div'); b.className='meet-sharing-self'; b.innerHTML=ic('monitor',13)+' You’re sharing your screen'; t.appendChild(b); } } + else if(b){ b.remove(); } +} function stopScreen(){ if(!meetScreen) return; meetScreen=false; - if(SFU.on){ try{ SFU.room.localParticipant.setScreenShareEnabled(false); }catch(_){} meetScreenStream=null; addTile('__local', meetLocalStream, (ME&&ME.name)?ME.name:'You', true); setTileScreen('__local', false); updateScreenBtn(); meetSend({ type:'meeting-screen', on:false }); return; } + if(SFU.on){ try{ SFU.room.localParticipant.setScreenShareEnabled(false); }catch(_){} meetScreenStream=null; addTile('__local', meetLocalStream, (ME&&ME.name)?ME.name:'You', true); setLocalSharing(false); updateScreenBtn(); meetSend({ type:'meeting-screen', on:false }); return; } const cam=meetLocalStream && meetLocalStream.getVideoTracks()[0]; for(const [,p] of meetPeers){ if(p.vsender){ try{ p.vsender.replaceTrack(cam||null); }catch(_){} } } if(meetScreenStream){ try{ meetScreenStream.getTracks().forEach(t=>t.stop()); }catch(_){} meetScreenStream=null; } - addTile('__local', meetLocalStream, (ME&&ME.name)?ME.name:'You', true); setTileScreen('__local', false); + addTile('__local', meetLocalStream, (ME&&ME.name)?ME.name:'You', true); setLocalSharing(false); updateScreenBtn(); meetSend({ type:'meeting-screen', on:false }); } function updateScreenBtn(){ const b=document.getElementById('meetScreenBtn'); if(!b) return; b.classList.toggle('on', meetScreen); b.title=meetScreen?'Stop sharing':'Share screen'; b.innerHTML=ic('monitor',20); } function setTileScreen(id, on){ const t=document.getElementById('meet-tile-'+id); if(!t) return; t.classList.toggle('sharing', !!on); let b=t.querySelector('.meet-screen'); if(on){ if(!b){ b=document.createElement('div'); b.className='meet-screen'; b.innerHTML=ic('monitor',12)+' Screen'; t.appendChild(b); } } else if(b){ b.remove(); } updateShareMode(); } // Stage mode: one chosen shared screen fills the area; other screens + people are small on the right. -function getSharerIds(){ const a=[]; if(meetScreen) a.push('__local'); meetSharers.forEach(id=>a.push(id)); return a; } +function getSharerIds(){ const a=[]; meetSharers.forEach(id=>a.push(id)); return a; } // #9: '__local' excluded — I don't display my own screen, so it's never the local stage function updateShareMode(){ const g=document.getElementById('meetGrid'); if(!g) return; const sharers=getSharerIds(); const on=sharers.length>0; g.classList.toggle('sharing-mode', on);