fix: SFU participants, duplicate peers, RC without login, devices, mobile bar (batch75)

#4 Participants list only ever showed "you": in SFU mode meetMakePeer drew a tile but
   NEVER added to meetPeers, so the roster (and the meeting-chat "To" list) was empty.
#5 Same person as several tiles: each tab/reload/reconnect gets a new peerId, leaving
   ghost peers. One uid = one tile now (dropDupPeers keeps the newest).
#1 Remote control needed a login: /share opened top-level (the no-login "Share my
   screen") has the preload bridge RIGHT THERE, but we only postMessage'd to a parent
   frame that doesn't exist — so control never armed. Use the native bridge directly.
#2 Sharer's session bar moved to the shared Lucide icon set (was old inline SVGs).
#3/#6 Device menu: collapse Windows' "Default -"/"Communications -" triplicates into one
   clean list (single "System default" + each real device once), and actually re-apply the
   saved mic/speaker to the room on connect so a picked headset/Bluetooth is used.
#7 Guest "left the meeting" screen rebranded to match the new pre-join.
#9 Tapping a member in group info opens the private conversation with them.
#8a iOS bottom rail: border-box + height incl. the home-indicator inset, px env() fallbacks
   (unitless 0 breaks calc() in Safari), promoted layer so it stops drifting.
#8c/#8e Mobile meeting bar: Mic / Camera / Speaker / End only; screen, record, transcript,
   chat, participants move behind a ⋮ More menu. No mic device dropdown on mobile.
#8d Speaker button reflects the real route: speaker-on / speaker-off (dimmed) / bluetooth
   when a headset is in use; re-detected on devicechange.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 15:39:05 +05:30
parent 94b8fac32f
commit 48b6a4050a
4 changed files with 165 additions and 44 deletions
+23 -8
View File
@@ -72,7 +72,7 @@
@keyframes rcpulse{0%{box-shadow:0 0 0 0 rgba(34,197,94,.6)}70%{box-shadow:0 0 0 8px rgba(34,197,94,0)}100%{box-shadow:0 0 0 0 rgba(34,197,94,0)}}
.rc-banner button{background:#ef4444;color:#fff;border:none;border-radius:999px;padding:.35rem .8rem;font-size:.8rem;font-weight:700;cursor:pointer;}
</style>
<script src="/icons.js?v=3"></script>
<script src="/icons.js?v=6"></script>
</head>
<body>
<script>if(new URLSearchParams(location.search).get('embed')==='1')document.documentElement.classList.add('embed');</script>
@@ -226,9 +226,22 @@ function stopCustTranscription(){ crecogActive=false; if(crecog){ try{crecog.sto
// after the user explicitly taps "Allow control", and only inside the desktop app. In a plain browser
// there's no OS injection possible, so it stays view-only. The user can stop control at any time.
let rcDesktop=false, rcAllowed=false, rcPrompted=false;
// Two ways this page runs inside the desktop app:
// • EMBEDDED in the signed-in app (Share tab iframe) → no direct bridge, relay via the parent frame.
// • TOP-LEVEL, no login (the landing page's "Share my screen") → the preload bridge is right here.
// The second case is the whole point of a support tool (the customer isn't a Connect user), and it was
// broken: we only ever postMessage'd to a parent that didn't exist, so control never armed. #1
const rcNative=(window.bizConnectNative && typeof window.bizConnectNative.rcAvailable==='function') ? window.bizConnectNative : null;
if(rcNative){ try{ rcDesktop=!!rcNative.rcAvailable(); }catch(_){ rcDesktop=false; } }
try{ window.addEventListener('message',(e)=>{ if(e.origin!==location.origin) return; const d=e.data||{}; if(d.type==='rc-pong'){ rcDesktop=!!d.desktop; try{ updateRcBtn(); }catch(_){} } }); }catch(_){}
try{ if(window.parent && window.parent!==window) window.parent.postMessage({type:'rc-ping'}, location.origin); }catch(_){}
function rcPost(msg){ try{ if(window.parent && window.parent!==window) window.parent.postMessage(msg, location.origin); }catch(_){} }
try{ if(!rcNative && window.parent && window.parent!==window) window.parent.postMessage({type:'rc-ping'}, location.origin); }catch(_){}
function rcPost(msg){
if(rcNative){
try{ if(msg.type==='rc-arm') rcNative.rcArm(!!msg.on); else if(msg.type==='rc-input') rcNative.rcInput(msg.evt); }catch(_){}
return;
}
try{ if(window.parent && window.parent!==window) window.parent.postMessage(msg, location.origin); }catch(_){}
}
function rcOnInput(data){
let evt; try{ evt=JSON.parse(data); }catch(_){ return; }
if(rcAllowed){ rcPost({type:'rc-input', evt}); return; }
@@ -304,16 +317,18 @@ function buildBar(){
if(document.getElementById('sessionBar'))return;
const bar=document.createElement('div'); bar.id='sessionBar';
bar.style.cssText='position:fixed;right:18px;bottom:18px;z-index:2147483000;display:flex;gap:10px;align-items:center;background:rgba(15,23,42,.94);padding:8px 12px;border-radius:16px;box-shadow:0 10px 28px rgba(0,0,0,.35)';
const mic=_btn('micBtn',SVG_MICOFF,'Muted','#6b7280');
// #2: use the shared Lucide icon set (ic) — the bar was still on the old hand-rolled SVGs.
const I=(n,s)=>(window.ic?window.ic(n,s||18):'');
const mic=_btn('micBtn',I('micOff'),'Muted','#6b7280');
// Remote-control access, right next to mic/chat, so the customer can see AND flip it at a glance.
const rcb=_btn('rcBtn',(window.ic?window.ic('monitor',18):''),'Remote control is OFF','#6b7280');
const chat=_btn('chatBtn',SVG_CHAT,'Chat','#475569');
const end=_btn('endBtn2',SVG_END,'End','#dc2626');
const rcb=_btn('rcBtn',I('monitor'),'Remote control is OFF','#6b7280');
const chat=_btn('chatBtn',I('chat'),'Chat','#475569');
const end=_btn('endBtn2',I('callEnd'),'End','#dc2626');
bar.appendChild(mic);bar.appendChild(rcb);bar.appendChild(chat);bar.appendChild(end);
document.body.appendChild(bar);
rcb.onclick=()=>{ if(rcAllowed) rcStopControl(); else rcGrantControl(); };
updateRcBtn();
const setMic=(on)=>{mic.title=on?'Mute':'Unmute';mic.innerHTML='<span style="display:inline-flex">'+(on?SVG_MIC:SVG_MICOFF)+'</span>';mic.style.background=on?'#2563eb':'#6b7280';};
const setMic=(on)=>{mic.title=on?'Mute':'Unmute';mic.innerHTML='<span style="display:inline-flex">'+I(on?'mic':'micOff')+'</span>';mic.style.background=on?'#2563eb':'#6b7280';};
mic.onclick=async()=>{
if(!window.__mic){
// First unmute: NOW request mic permission, add the track, and renegotiate.