diff --git a/server/public/share.html b/server/public/share.html index 5db061e..662e7bc 100644 --- a/server/public/share.html +++ b/server/public/share.html @@ -20,7 +20,7 @@ .sub{color:var(--muted);font-size:.97rem;line-height:1.5;margin-bottom:1.6rem;} .codewrap{background:#fffdf2;border:2px dashed var(--brand);border-radius:14px;padding:1.2rem;} .codelabel{font-size:.78rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-bottom:.3rem;} - .code{font-size:3rem;letter-spacing:.5rem;font-weight:800;color:var(--ink);} + .code{font-size:clamp(1.9rem,12vw,3rem);letter-spacing:clamp(.18rem,3vw,.5rem);font-weight:800;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:clip;} .status{margin-top:1.3rem;padding:.7rem 1rem;border-radius:10px;background:#f1f5f9;color:#475569;font-size:.92rem;} .status.on{background:#ecfdf3;color:#15803d;} .consent{margin-top:1.3rem;border:1px solid #c7d6f0;background:var(--blue-soft);border-left:5px solid var(--blue);border-radius:12px;padding:1.3rem;text-align:left;color:var(--blue-d);} @@ -117,23 +117,40 @@ function showConsent(m){ const name=(m.technician&&m.technician.trim())?m.technician:'Your support agent'; consentBox.innerHTML=''; - const allow=()=>{consentBox.innerHTML='';document.removeEventListener('keydown',onKey);ws.send(JSON.stringify({type:'consent',sessionId:m.sessionId,granted:true}));}; + const allow=async()=>{ + document.removeEventListener('keydown',onKey); + setStatus('Opening the screen picker — choose your screen and tap Share / Start.','on'); + const ok=await beginCapture(); + if(!ok){ consentBox.innerHTML=''; try{ws.send(JSON.stringify({type:'consent',sessionId:m.sessionId,granted:false}));}catch(_){} setStatus('Screen share was cancelled. Refresh this page if you need a new code.'); return; } + consentBox.innerHTML=''; + try{ws.send(JSON.stringify({type:'consent',sessionId:m.sessionId,granted:true}));}catch(_){} + }; const onKey=(e)=>{if(e.key==='Enter'){e.preventDefault();allow();}}; document.addEventListener('keydown',onKey); document.getElementById('g').onclick=allow; document.getElementById('d').onclick=()=>{consentBox.innerHTML='';document.removeEventListener('keydown',onKey);ws.send(JSON.stringify({type:'consent',sessionId:m.sessionId,granted:false}));setStatus('Connection declined. Refresh this page if you need a new code.');}; } +// Capture the screen (+mic) DIRECTLY from the Allow tap. Mobile browsers reject +// getDisplayMedia unless it is called from a user gesture, so this must not run +// after a server round-trip. getDisplayMedia is called first to keep the gesture. +async function beginCapture(){ + try{ localStream=await navigator.mediaDevices.getDisplayMedia({video:{displaySurface:'monitor',frameRate:{ideal:30}},audio:false,monitorTypeSurfaces:'include'}); } + catch(err){ return false; } + try{ const mic=await navigator.mediaDevices.getUserMedia({audio:true}); window.__mic=mic; mic.getAudioTracks().forEach(t=>localStream.addTrack(t)); }catch(e){} + try{ ensureIce(); }catch(_){} + return true; +} async function startStreaming(){ - // Make sure TURN/ICE is loaded before building the connection (needed for mobile/cellular relay). + // If the Allow tap already captured the screen (mobile path), reuse it. + if(!localStream){ + await ensureIce(); + let mic=null; try{ mic=await navigator.mediaDevices.getUserMedia({audio:true}); }catch(e){ mic=null; } + setStatus('In the popup: choose your screen, then tap Share / Start.','on'); + try{ localStream=await navigator.mediaDevices.getDisplayMedia({video:{displaySurface:'monitor',frameRate:{ideal:30}},audio:false,monitorTypeSurfaces:'include'}); } + catch(err){ if(mic){try{mic.getTracks().forEach(t=>t.stop());}catch(_){}} try{ws.send(JSON.stringify({type:'end-session',sessionId,reason:'share-cancelled'}));}catch(e){} setStatus('Screen share was cancelled. Refresh the page to try again.'); return; } + if(mic){ window.__mic=mic; try{mic.getAudioTracks().forEach(t=>localStream.addTrack(t));}catch(_){} } + } await ensureIce(); - // Ask for the microphone FIRST. On Android the screen capture must be the LAST - // capture started, otherwise the mic permission prompt interrupts it and the share ends. - let mic=null; - try{ mic=await navigator.mediaDevices.getUserMedia({audio:true}); }catch(e){ mic=null; } - setStatus('In the popup: choose your screen, then tap Share / Start.','on'); - try{ localStream=await navigator.mediaDevices.getDisplayMedia({video:{frameRate:{ideal:30}},audio:false}); } - catch(err){ if(mic){try{mic.getTracks().forEach(t=>t.stop());}catch(_){}} try{ws.send(JSON.stringify({type:'end-session',sessionId,reason:'share-cancelled'}));}catch(e){} setStatus('Screen share was cancelled. Refresh the page to try again.'); return; } - if(mic){ window.__mic=mic; try{mic.getAudioTracks().forEach(t=>localStream.addTrack(t));}catch(_){} } indicator.classList.add('show'); setStatus('You are now sharing your screen with your agent.','on'); { const hl=document.getElementById('homeLink'); if(hl) hl.style.display='none'; } window.onbeforeunload=function(){ if((localStream||document.getElementById('sessionBar'))&&!sessionOver){ return 'Leaving or refreshing this page will end your screen sharing session.'; } };