|
|
@@ -20,7 +20,7 @@
|
|
20
|
20
|
.sub{color:var(--muted);font-size:.97rem;line-height:1.5;margin-bottom:1.6rem;}
|
|
21
|
21
|
.codewrap{background:#fffdf2;border:2px dashed var(--brand);border-radius:14px;padding:1.2rem;}
|
|
22
|
22
|
.codelabel{font-size:.78rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-bottom:.3rem;}
|
|
23
|
|
- .code{font-size:3rem;letter-spacing:.5rem;font-weight:800;color:var(--ink);}
|
|
|
23
|
+ .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;}
|
|
24
|
24
|
.status{margin-top:1.3rem;padding:.7rem 1rem;border-radius:10px;background:#f1f5f9;color:#475569;font-size:.92rem;}
|
|
25
|
25
|
.status.on{background:#ecfdf3;color:#15803d;}
|
|
26
|
26
|
.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){
|
|
117
|
117
|
const name=(m.technician&&m.technician.trim())?m.technician:'Your support agent';
|
|
118
|
118
|
consentBox.innerHTML='<div class="consent">Your support agent <span class="who">'+esc(name)+'</span> would like to view your screen to help you.'+
|
|
119
|
119
|
'<div class="btns"><button class="grant" id="g">Allow</button><button class="deny" id="d">Not now</button></div></div>';
|
|
120
|
|
- const allow=()=>{consentBox.innerHTML='';document.removeEventListener('keydown',onKey);ws.send(JSON.stringify({type:'consent',sessionId:m.sessionId,granted:true}));};
|
|
|
120
|
+ const allow=async()=>{
|
|
|
121
|
+ document.removeEventListener('keydown',onKey);
|
|
|
122
|
+ setStatus('Opening the screen picker — choose your screen and tap Share / Start.','on');
|
|
|
123
|
+ const ok=await beginCapture();
|
|
|
124
|
+ 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; }
|
|
|
125
|
+ consentBox.innerHTML='';
|
|
|
126
|
+ try{ws.send(JSON.stringify({type:'consent',sessionId:m.sessionId,granted:true}));}catch(_){}
|
|
|
127
|
+ };
|
|
121
|
128
|
const onKey=(e)=>{if(e.key==='Enter'){e.preventDefault();allow();}};
|
|
122
|
129
|
document.addEventListener('keydown',onKey);
|
|
123
|
130
|
document.getElementById('g').onclick=allow;
|
|
124
|
131
|
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.');};
|
|
125
|
132
|
}
|
|
|
133
|
+// Capture the screen (+mic) DIRECTLY from the Allow tap. Mobile browsers reject
|
|
|
134
|
+// getDisplayMedia unless it is called from a user gesture, so this must not run
|
|
|
135
|
+// after a server round-trip. getDisplayMedia is called first to keep the gesture.
|
|
|
136
|
+async function beginCapture(){
|
|
|
137
|
+ try{ localStream=await navigator.mediaDevices.getDisplayMedia({video:{displaySurface:'monitor',frameRate:{ideal:30}},audio:false,monitorTypeSurfaces:'include'}); }
|
|
|
138
|
+ catch(err){ return false; }
|
|
|
139
|
+ try{ const mic=await navigator.mediaDevices.getUserMedia({audio:true}); window.__mic=mic; mic.getAudioTracks().forEach(t=>localStream.addTrack(t)); }catch(e){}
|
|
|
140
|
+ try{ ensureIce(); }catch(_){}
|
|
|
141
|
+ return true;
|
|
|
142
|
+}
|
|
126
|
143
|
async function startStreaming(){
|
|
127
|
|
- // Make sure TURN/ICE is loaded before building the connection (needed for mobile/cellular relay).
|
|
|
144
|
+ // If the Allow tap already captured the screen (mobile path), reuse it.
|
|
|
145
|
+ if(!localStream){
|
|
|
146
|
+ await ensureIce();
|
|
|
147
|
+ let mic=null; try{ mic=await navigator.mediaDevices.getUserMedia({audio:true}); }catch(e){ mic=null; }
|
|
|
148
|
+ setStatus('In the popup: choose your screen, then tap Share / Start.','on');
|
|
|
149
|
+ try{ localStream=await navigator.mediaDevices.getDisplayMedia({video:{displaySurface:'monitor',frameRate:{ideal:30}},audio:false,monitorTypeSurfaces:'include'}); }
|
|
|
150
|
+ 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; }
|
|
|
151
|
+ if(mic){ window.__mic=mic; try{mic.getAudioTracks().forEach(t=>localStream.addTrack(t));}catch(_){} }
|
|
|
152
|
+ }
|
|
128
|
153
|
await ensureIce();
|
|
129
|
|
- // Ask for the microphone FIRST. On Android the screen capture must be the LAST
|
|
130
|
|
- // capture started, otherwise the mic permission prompt interrupts it and the share ends.
|
|
131
|
|
- let mic=null;
|
|
132
|
|
- try{ mic=await navigator.mediaDevices.getUserMedia({audio:true}); }catch(e){ mic=null; }
|
|
133
|
|
- setStatus('In the popup: choose your screen, then tap Share / Start.','on');
|
|
134
|
|
- try{ localStream=await navigator.mediaDevices.getDisplayMedia({video:{frameRate:{ideal:30}},audio:false}); }
|
|
135
|
|
- 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; }
|
|
136
|
|
- if(mic){ window.__mic=mic; try{mic.getAudioTracks().forEach(t=>localStream.addTrack(t));}catch(_){} }
|
|
137
|
154
|
indicator.classList.add('show'); setStatus('You are now sharing your screen with your agent.','on');
|
|
138
|
155
|
{ const hl=document.getElementById('homeLink'); if(hl) hl.style.display='none'; }
|
|
139
|
156
|
window.onbeforeunload=function(){ if((localStream||document.getElementById('sessionBar'))&&!sessionOver){ return 'Leaving or refreshing this page will end your screen sharing session.'; } };
|