瀏覽代碼

Mobile Browser connection fix.

Sravan 1 周之前
父節點
當前提交
3e24cacc39
共有 2 個文件被更改,包括 22 次插入9 次删除
  1. 4
    2
      server/public/connect.html
  2. 18
    7
      server/public/share.html

+ 4
- 2
server/public/connect.html 查看文件

@@ -52,7 +52,8 @@
52 52
 
53 53
 <script>
54 54
 let ICE={iceServers:[{urls:'stun:stun.l.google.com:19302'}]};
55
-try{fetch('/api/ice').then(r=>r.ok?r.json():null).then(c=>{if(c&&c.iceServers)ICE=c;}).catch(()=>{});}catch(_){}
55
+let __icePromise=Promise.resolve();try{__icePromise=fetch('/api/ice').then(r=>r.ok?r.json():null).then(c=>{if(c&&c.iceServers)ICE=c;}).catch(()=>{});}catch(_){}
56
+async function ensureIce(){try{await __icePromise;}catch(_){}return ICE;}
56 57
 function pEsc(s){return String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));}
57 58
 function profileHTML(name){return '<div class="profile"><button class="pbtn" id="pbtn">'+pEsc(name)+' <span style="font-size:.65rem">&#9662;</span></button><div class="pmenu" id="pmenu"><a href="/console">Console / Dashboard</a><a id="plogout" class="danger">Logout</a></div></div>';}
58 59
 function wireProfile(){const btn=document.getElementById('pbtn'),menu=document.getElementById('pmenu');if(!btn)return;btn.onclick=(e)=>{e.stopPropagation();menu.classList.toggle('open');};document.addEventListener('click',()=>menu.classList.remove('open'));const lo=document.getElementById('plogout');if(lo)lo.onclick=async()=>{try{await fetch('/api/logout',{method:'POST'});}catch(_){}location.href='/';};}
@@ -218,7 +219,8 @@ try{['pointerdown','keydown','touchstart','click'].forEach(ev=>document.addEvent
218 219
 function sendChat(){const i=document.getElementById('chatInput');if(!i)return;const t=i.value.trim();if(!t)return;if(chatChannel&&chatChannel.readyState==='open'){chatChannel.send(JSON.stringify({name:(me&&(me.name||me.email))||'Support agent',text:t}));}addChat({from:'__self',name:'You',text:t});i.value='';}
219 220
 function removeSessionUI(){['sessionBar','chatPanel','remoteAudio','muteBtn','msgToast'].forEach(id=>{const e=document.getElementById(id);if(e)e.remove();});}
220 221
 
221
-function setupPeer(){
222
+async function setupPeer(){
223
+  await ensureIce();
222 224
   pc=new RTCPeerConnection(ICE);
223 225
   inputChannel=pc.createDataChannel('input',{ordered:true});
224 226
   pc.ondatachannel=(ev)=>{ if(ev.channel.label==='chat'){ chatChannel=ev.channel; chatChannel.onmessage=(e)=>{try{addChat(JSON.parse(e.data));}catch(_){}}; } };

+ 18
- 7
server/public/share.html 查看文件

@@ -71,7 +71,10 @@
71 71
 </div>
72 72
 <script>
73 73
 let ICE={iceServers:[{urls:'stun:stun.l.google.com:19302'}]};
74
-try{fetch('/api/ice').then(r=>r.ok?r.json():null).then(c=>{if(c&&c.iceServers)ICE=c;}).catch(()=>{});}catch(_){}
74
+let SHARER_NAME='Customer';
75
+try{fetch('/api/me').then(r=>r.ok?r.json():null).then(m=>{if(m&&(m.name||m.email))SHARER_NAME=m.name||m.email;}).catch(()=>{});}catch(_){}
76
+let __icePromise=Promise.resolve();try{__icePromise=fetch('/api/ice').then(r=>r.ok?r.json():null).then(c=>{if(c&&c.iceServers)ICE=c;}).catch(()=>{});}catch(_){}
77
+async function ensureIce(){try{await __icePromise;}catch(_){}return ICE;}
75 78
 function pEsc(s){return String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));}
76 79
 function profileHTML(name){return '<div class="profile"><button class="pbtn" id="pbtn">'+pEsc(name)+' <span style="font-size:.65rem">&#9662;</span></button><div class="pmenu" id="pmenu"><a href="/console">Console / Dashboard</a><a id="plogout" class="danger">Logout</a></div></div>';}
77 80
 function wireProfile(){const btn=document.getElementById('pbtn'),menu=document.getElementById('pmenu');if(!btn)return;btn.onclick=(e)=>{e.stopPropagation();menu.classList.toggle('open');};document.addEventListener('click',()=>menu.classList.remove('open'));const lo=document.getElementById('plogout');if(lo)lo.onclick=async()=>{try{await fetch('/api/logout',{method:'POST'});}catch(_){}location.href='/';};}
@@ -101,7 +104,7 @@ ws.onmessage=async(e)=>{const m=JSON.parse(e.data);switch(m.type){
101 104
   case 'session-ended': endShareSession('Your support agent ended the session. Tap below for a new code if you still need help.'); break;
102 105
   case 'error': setStatus(m.message,''); break;
103 106
 }};
104
-ws.onclose=()=>{ if(document.getElementById('sessionBar')||localStream){ endShareSession('The connection was closed.'); } else { setStatus('Connection closed. Refresh the page to start again.'); } };
107
+ws.onclose=()=>{ if(document.getElementById('sessionBar')||localStream){ /* keep the live session: media flows independently of signaling. a real end is detected via pc 'failed' or an explicit session-ended message. */ } else { setStatus('Connection closed. Refresh the page to start again.'); } };
105 108
 function onAgentConnected(m){
106 109
   const cw=document.querySelector('.codewrap');
107 110
   if(cw) cw.style.display='none';
@@ -119,16 +122,24 @@ function showConsent(m){
119 122
   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.');};
120 123
 }
121 124
 async function startStreaming(){
122
-  setStatus('In the popup: click the screen preview so it is selected, then press Share.','on');
123
-  try{ localStream=await navigator.mediaDevices.getDisplayMedia({video:{displaySurface:'monitor',frameRate:{ideal:30}},audio:false,monitorTypeSurfaces:'include'}); }
124
-  catch(err){ 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; }
125
+  // Make sure TURN/ICE is loaded before building the connection (needed for mobile/cellular relay).
126
+  await ensureIce();
127
+  // Ask for the microphone FIRST. On Android the screen capture must be the LAST
128
+  // capture started, otherwise the mic permission prompt interrupts it and the share ends.
129
+  let mic=null;
130
+  try{ mic=await navigator.mediaDevices.getUserMedia({audio:true}); }catch(e){ mic=null; }
131
+  setStatus('In the popup: choose your screen, then tap Share / Start.','on');
132
+  try{ localStream=await navigator.mediaDevices.getDisplayMedia({video:{frameRate:{ideal:30}},audio:false}); }
133
+  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; }
134
+  if(mic){ window.__mic=mic; try{mic.getAudioTracks().forEach(t=>localStream.addTrack(t));}catch(_){} }
125 135
   indicator.classList.add('show'); setStatus('You are now sharing your screen with your agent.','on');
126 136
   pc=new RTCPeerConnection(ICE);
127
-  try{ const mic=await navigator.mediaDevices.getUserMedia({audio:true}); window.__mic=mic; mic.getAudioTracks().forEach(t=>localStream.addTrack(t)); buildBar(); }catch(e){ buildBar(); }
137
+  buildBar();
128 138
   localStream.getTracks().forEach(t=>pc.addTrack(t,localStream));
129 139
   pc.ondatachannel=(ev)=>{ev.channel.onmessage=()=>{};};
130 140
   pc.ontrack=(ev)=>{ if(ev.track.kind==='audio'){ let a=document.getElementById('remoteAudio'); if(!a){a=document.createElement('audio');a.id='remoteAudio';a.autoplay=true;document.body.appendChild(a);} a.srcObject=ev.streams[0]; } };
131 141
   pc.onicecandidate=(ev)=>{if(ev.candidate)ws.send(JSON.stringify({type:'ice-candidate',sessionId,candidate:ev.candidate}));};
142
+  pc.onconnectionstatechange=()=>{ if(pc&&pc.connectionState==='failed'){ endShareSession('The connection was lost. Tap below for a new code if you still need help.'); } };
132 143
   chatChannel=pc.createDataChannel('chat',{ordered:true});
133 144
   chatChannel.onmessage=(e)=>{try{addChat(JSON.parse(e.data));}catch(_){}};
134 145
   const offer=await pc.createOffer(); await pc.setLocalDescription(offer);
@@ -187,7 +198,7 @@ let __ac=null;
187 198
 function ensureAudio(){try{__ac=__ac||new (window.AudioContext||window.webkitAudioContext)();if(__ac.state==='suspended')__ac.resume();}catch(_){}}
188 199
 function beep(){ensureAudio();if(!__ac)return;try{const o=__ac.createOscillator(),g=__ac.createGain();o.type='sine';o.connect(g);g.connect(__ac.destination);const t0=__ac.currentTime;o.frequency.setValueAtTime(880,t0);o.frequency.setValueAtTime(660,t0+0.09);g.gain.setValueAtTime(0.0001,t0);g.gain.exponentialRampToValueAtTime(0.12,t0+0.02);g.gain.exponentialRampToValueAtTime(0.0001,t0+0.22);o.start(t0);o.stop(t0+0.24);}catch(_){}}
189 200
 try{['pointerdown','keydown','touchstart','click'].forEach(ev=>document.addEventListener(ev,ensureAudio,{passive:true}));}catch(_){}
190
-function sendChat(){const i=document.getElementById('chatInput');if(!i)return;const t=i.value.trim();if(!t)return;if(chatChannel&&chatChannel.readyState==='open'){chatChannel.send(JSON.stringify({name:'Customer',text:t}));}addChat({from:'__self',name:'You',text:t});i.value='';}
201
+function sendChat(){const i=document.getElementById('chatInput');if(!i)return;const t=i.value.trim();if(!t)return;if(chatChannel&&chatChannel.readyState==='open'){chatChannel.send(JSON.stringify({name:SHARER_NAME,text:t}));}addChat({from:'__self',name:'You',text:t});i.value='';}
191 202
 function removeSessionUI(){['sessionBar','chatPanel','remoteAudio','muteBtn','msgToast'].forEach(id=>{const e=document.getElementById(id);if(e)e.remove();});}
192 203
 
193 204
 function esc(s){return String(s).replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));}

Loading…
取消
儲存