@moveo-ai/web-client 0.122.0-true.5 → 0.122.0-true.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/63.min.js.LICENSE.txt +1 -1
- package/dist/752.min.js.LICENSE.txt +1 -1
- package/dist/882.min.js +1 -1
- package/dist/882.min.js.LICENSE.txt +1 -1
- package/dist/929.min.js.LICENSE.txt +1 -1
- package/dist/audio-client.min.js.LICENSE.txt +1 -1
- package/dist/continue/index.html +47 -6
- package/dist/iframe.min.js +1 -1
- package/dist/iframe.min.js.LICENSE.txt +1 -1
- package/dist/index.html +1 -1
- package/dist/preview/index.html +2 -2
- package/dist/preview.css.min.js.LICENSE.txt +1 -1
- package/dist/telnyx-client.min.js.LICENSE.txt +1 -1
- package/dist/web-client.min.js.LICENSE.txt +1 -1
- package/dist/webrtc-client.min.js +1 -1
- package/dist/webrtc-client.min.js.LICENSE.txt +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -644,7 +644,7 @@ read the `--color-options-*` set. All five are in
|
|
|
644
644
|
### Inactivity Timeout
|
|
645
645
|
|
|
646
646
|
The widget opens an inactivity modal once the visitor goes quiet, and asks
|
|
647
|
-
whether to keep the conversation. Set `inactivity_timeout` in minutes to change
|
|
647
|
+
whether to keep the conversation. A voice call counts as activity. Set `inactivity_timeout` in minutes to change
|
|
648
648
|
when it appears. Set it in the integration's configuration, or override it on
|
|
649
649
|
`init()`:
|
|
650
650
|
|
package/dist/882.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/*! For license information please see 882.min.js.LICENSE.txt */
|
|
2
|
-
"use strict";(this.webpackChunkMoveoAI||=[]).push([[882],{2229(t,e,i){i.d(e,{h:()=>s});class s{constructor(){this.analyserNode=null,this.audioSource=null,this.animationId=null,this.audioContext=null}start(t,e){try{this.audioContext=new AudioContext,this.audioSource=this.audioContext.createMediaStreamSource(t),this.analyserNode=this.audioContext.createAnalyser(),this.analyserNode.fftSize=256,this.analyserNode.smoothingTimeConstant=.3,this.audioSource.connect(this.analyserNode);const i=new Float32Array(this.analyserNode.fftSize),s=()=>{if(!this.analyserNode)return void this.stop();this.analyserNode.getFloatTimeDomainData(i);let t=0;for(const e of i)t+=e*e;const n=Math.sqrt(t/i.length),r=Math.min(1,10*n);e((r>.01?r:0).toFixed(3)),this.animationId=requestAnimationFrame(s)};s()}catch{}}stop(){if(null!==this.animationId&&(cancelAnimationFrame(this.animationId),this.animationId=null),this.audioSource){try{this.audioSource.disconnect()}catch{}this.audioSource=null}this.audioContext&&(this.audioContext.close().catch(()=>{}),this.audioContext=null),this.analyserNode=null}}},6815(t,e,i){i.d(e,["n",0,{NotAllowedError:"Microphone permission denied",NotFoundError:"No microphone found",NotReadableError:"Microphone is in use by another application",OverconstrainedError:"Microphone does not support required settings"}])},3564(t,e,i){i.d(e,{M:()=>r});var s=i(2076);const n={"high-rtt":"High network latency detected","low-mos":"Poor call quality detected","high-jitter":"Network instability detected","high-packet-loss":"Packet loss detected"};class r{constructor(t,e,i,s=5e3){this.pc=t,this.onWarning=e,this.onWarningCleared=i,this.pollIntervalMs=s,this.interval=null,this.prevStats=null,this.activeWarnings=new Set}start(){this.interval=setInterval(()=>{this.pollStats()},this.pollIntervalMs)}stop(){null!==this.interval&&(clearInterval(this.interval),this.interval=null),this.prevStats=null,this.activeWarnings.clear()}async pollStats(){try{const t=await this.pc.getStats();let e=null,i=0,n=0;const r=(0,s._)(t),o=null!=r?.currentRoundTripTime?1e3*r.currentRoundTripTime:null;t.forEach(t=>{if("inbound-rtp"===t.type&&"audio"===t.kind){const s=t;e=s.jitter??null,i=s.packetsReceived??0,n=s.packetsLost??0}});let a=0;if(this.prevStats){const t=i-this.prevStats.packetsReceived,e=n-this.prevStats.packetsLost,s=t+e;a=s>0?e/s:0}if(this.prevStats={packetsReceived:i,packetsLost:n},this.checkWarning("high-rtt",null!==o&&o>400),this.checkWarning("high-jitter",null!==e&&e>.03),this.checkWarning("high-packet-loss",a>.03),null!==o&&null!==e){const t=93.2-1e3*e*2-100*a*40-.1*Math.max(0,o-150),i=1+.035*t+t*(t-60)*(100-t)*7e-6;this.checkWarning("low-mos",i<3)}}catch{}}checkWarning(t,e){e&&!this.activeWarnings.has(t)?(this.activeWarnings.add(t),this.onWarning(t,n[t]||t)):!e&&this.activeWarnings.has(t)&&(this.activeWarnings.delete(t),this.onWarningCleared(t))}}},6845(t,e,i){i.d(e,{PA:()=>s,TM:()=>r,Xq:()=>n});class s extends Error{constructor(t="Voice service is busy, please try again."){super(t),this.name="ServerBusyError"}}class n extends Error{constructor(t){super(t),this.name="ConfigUnavailableError"}}class r extends Error{constructor(t="Too many attempts. Please try again in a moment."){super(t),this.name="RateLimitedError"}}},7943(t,e,i){i.d(e,{$:()=>o});var s=i(7833),n=i.n(s),r=i(2076);class o{constructor(t,e,i,s=1e3,r=3,o=!function(t=navigator.userAgent){return/\b(iPad|iPhone|iPod)\b/.test(t)||/AppleWebKit/.test(t)&&!/Chrome|Chromium|CriOS|Edg|OPR/.test(t)}()){this.pc=t,this.onStall=e,this.onRecover=i,this.pollIntervalMs=s,this.flatPollsThreshold=r,this.escalate=o,this.log=n()("moveo:webrtc:media"),this.interval=null,this.prevCounts=null,this.flatPolls=0,this.stalled=!1,this.cannotAssessLogged=!1}start(){this.log("started (escalate=%s)",this.escalate),this.interval=setInterval(()=>{this.poll()},this.pollIntervalMs)}stop(){null!==this.interval&&(clearInterval(this.interval),this.interval=null),this.prevCounts=null,this.flatPolls=0,this.stalled=!1,this.cannotAssessLogged=!1}async poll(){try{const t=await this.pc.getStats(),e=function(t){const e=new Map;return t.forEach(t=>{if("inbound-rtp"===t.type){const i=t;"audio"===i.kind&&void 0!==i.packetsReceived&&e.set(i.id,i.packetsReceived)}}),e}(t);if(0===e.size)return void(this.cannotAssessLogged||(this.cannotAssessLogged=!0,this.log("cannot assess: no audio inbound-rtp with packetsReceived")));this.cannotAssessLogged=!1;const i=this.prevCounts;if(this.prevCounts=e,null===i)return void this.log("assessing %d audio inbound-rtp report(s)",e.size);let s=!1,n=!1,r=0,o=!1;if(e.forEach((t,e)=>{const a=i.get(e);void 0!==a?(r++,t>a?s=!0:t<a&&(n=!0)):o=!0}),s)return this.stalled&&(this.stalled=!1,this.log("recovered%s (pair %s)",this.escalate?"":" (log-only)",this.describeSelectedPair(t)),this.escalate&&this.onRecover()),void(this.flatPolls=0);if(0===r||n||o)return this.log("inbound-rtp reports reseeded (comparable=%d new=%s)",r,o),void(this.flatPolls=0);this.flatPolls++,this.log("flat poll reports=%d flat=%d",e.size,this.flatPolls),this.flatPolls>=this.flatPollsThreshold&&!this.stalled&&(this.stalled=!0,this.log("stall declared%s after %d flat polls (pair %s)",this.escalate?"":" (log-only, escalation disabled)",this.flatPolls,this.describeSelectedPair(t)),this.escalate&&this.onStall())}catch{}}describeSelectedPair(t){const e=(0,r._)(t);return e?(0,r.t)(t,e):"?"}}},2076(t,e,i){function s(t){let e;const i=[];t.forEach(t=>{if("transport"===t.type){const{selectedCandidatePairId:i}=t;i&&!e&&(e=i)}else"candidate-pair"===t.type&&i.push(t)});const s=e&&i.find(t=>t.id===e)||i.find(t=>!0===t.selected);if(s)return s;let n;for(const t of i){if(!t.nominated)continue;if(void 0===n){n=t;continue}const e=n.lastPacketReceivedTimestamp,i=t.lastPacketReceivedTimestamp;void 0!==i&&(void 0===e||i>e)&&(n=t)}return n}function n(t,e){const i=e=>{if(!e)return"?";const i=t.get(e);if(!i)return"?";const s=`${i.candidateType??"?"}/${i.protocol??"?"}`;return i.relayProtocol?`${s}(${i.relayProtocol})`:s};return`${i(e.localCandidateId)} -> ${i(e.remoteCandidateId)}`}i.d(e,{_:()=>s,t:()=>n})},4623(t,e,i){i.d(e,{g:()=>r});var s=i(7833),n=i.n(s);class r{constructor(t,e,i,s,r){this.url=t,this.sessionToken=e,this.onMessage=i,this.onClose=s,this.onError=r,this.log=n()("moveo:webrtc:media"),this.ws=null}connect(){return new Promise((t,e)=>{this.log("connecting to %s",this.url);const i=`${this.url}?token=${encodeURIComponent(this.sessionToken)}`;this.ws=new WebSocket(i);const s=setTimeout(()=>{this.close(),e(new Error("Signaling connection timed out"))},1e4);this.ws.onopen=()=>{},this.ws.onmessage=e=>{let i;try{i=JSON.parse(e.data)}catch{return}this.log("recv %s",i.type),"welcome"===i.type&&(clearTimeout(s),t()),this.onMessage(i)},this.ws.onerror=t=>{clearTimeout(s),this.log("websocket error"),this.onError(t),e(new Error("Signaling WebSocket error"))},this.ws.onclose=t=>{clearTimeout(s),this.log("closed code=%d reason=%s",t.code,t.reason),e(new Error("Signaling WebSocket closed before connection established")),this.onClose(t.code,t.reason)}})}connectWithFirstFrame(t){const e="attach"===t.type?"attach_ack":"reconnect_ack";return new Promise((i,s)=>{this.log("%s to %s",t.type,this.url);const n=`${this.url}?token=${encodeURIComponent(this.sessionToken)}`;this.ws=new WebSocket(n);let r=!1;const o=setTimeout(()=>{r||(r=!0,"reconnect"===t.type?this.close(4001,"Resume timed out"):this.close(),s(new Error(`Signaling ${t.type} timed out`)))},1e4);this.ws.onopen=()=>{this.ws?.send(JSON.stringify(t))},this.ws.onmessage=n=>{let a;try{a=JSON.parse(n.data)}catch{return}this.log("recv %s",a.type),this.onMessage(a),r||(a.type===e?(r=!0,clearTimeout(o),i()):"error"===a.type&&(r=!0,clearTimeout(o),s(new Error(`${t.type} rejected: ${a.error.code}`))))},this.ws.onerror=t=>{this.log("websocket error"),this.onError(t),r||(r=!0,clearTimeout(o),s(new Error("Signaling WebSocket error")))},this.ws.onclose=e=>{this.log("closed code=%d reason=%s",e.code,e.reason),r||(r=!0,clearTimeout(o),s(new Error(`Signaling WebSocket closed before ${t.type} established`))),this.onClose(e.code,e.reason)}})}send(t){this.ws&&this.ws.readyState===WebSocket.OPEN&&(this.log("send %s",t.type),this.ws.send(JSON.stringify(t)))}close(t=1e3,e="Client closed"){this.ws&&(this.ws.onclose=null,this.ws.onerror=null,this.ws.onmessage=null,this.ws.close(t,e),this.ws=null)}get isConnected(){return this.ws?.readyState===WebSocket.OPEN}}},5882(t,e,i){i.r(e),i.d(e,{WebRTCProvider:()=>R}),i(6752);var s=i(7833),n=i.n(s),r=i(2229),o=i(6815),a=i(3564),c=i(6845),h=i(7943),l=i(4623);const d=500,u=new Set(["session_expired","session_inactive","invalid_credential","max_recovery_exceeded","unexpected_first_frame"]),g={session_expired:"Your session has expired.",session_inactive:"The call has ended.",invalid_credential:"Could not resume the call.",max_recovery_exceeded:"Could not resume the call after several attempts.",unexpected_first_frame:"Could not resume the call."},m={invalid_code:"Could not start the call.",used:"A call is already running in this conversation.",session_ended:"This conversation has ended.",attach_unsupported:"Calls are not available in this conversation.",internal_error:"Could not start the call."},p=t=>t instanceof Error?t.message:"Signaling connection failed",f={...o.n,setRemoteDescription:"Connection setup failed",setLocalDescription:"Connection setup failed",addIceCandidate:"Network configuration failed",createOffer:"Browser audio setup failed"};class R{constructor(t,e){this.wsUrl=t,this.fetchConfig=e,this.log=n()("moveo:webrtc:media"),this.pc=null,this.signaling=null,this.localStream=null,this.remoteStream=null,this.remoteAudio=null,this.remoteAudioMonitor=null,this.audioMonitor=null,this.qualityMonitor=null,this.stallDetector=null,this.pendingCandidates=[],this.state="idle",this.eventListeners=new Map,this.iceRestarting=!1,this.iceRestartAttempts=0,this.iceRestartTimeout=null,this.iceRestartAnswerTimeout=null,this.connectResolve=null,this.connectReject=null,this.resuming=!1,this.reconnectAttempts=0,this.reconnectErrorCode=null,this.attaching=!1,this.attachErrorCode=null,this.ended=!1,this.hangupReason=null,this.brainResponseChunks=new Map,this.interruptedRequests=new Map}get serverHangupReason(){return this.hangupReason}setConfig(t){this.config=t}async connect(t){if(!this.config)throw new Error("Voice config not set; call setConfig() before connect().");this.ended=!1,this.hangupReason=null,this.setState("connecting");try{this.localStream=await navigator.mediaDevices.getUserMedia({audio:{echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0}})}catch(t){const e=t instanceof Error?t.name:"",i=f[e]||"Microphone access failed";throw this.emitError(i),new Error(i)}if(t)this.signaling=await this.openAttachedSignaling(t);else{this.signaling=this.createSignaling();try{await this.signaling.connect()}catch(t){this.failSignaling(p(t))}}this.pc=new RTCPeerConnection({iceServers:this.config.iceServers,iceTransportPolicy:this.config.iceTransportPolicy||"relay"});const e=this.localStream.getAudioTracks()[0];this.pc.addTrack(e,this.localStream),this.pc.ontrack=this.handleRemoteTrack.bind(this),this.pc.onicecandidate=t=>{t.candidate&&this.signaling?.send({type:"ice",candidate:t.candidate.toJSON()})},this.pc.onconnectionstatechange=this.handleConnectionStateChange.bind(this);try{const t=await this.pc.createOffer();if(!t.sdp)throw new Error("createOffer returned no SDP");await this.pc.setLocalDescription(t),this.signaling.send({type:"offer",sdp:t.sdp})}catch(t){this.cleanup();const e=t instanceof Error?t.name:"createOffer",i=f[e]||"Failed to create call offer";throw this.emitError(i),new Error(i)}return this.audioMonitor=new r.h,this.audioMonitor.start(this.localStream,t=>{this.emit("audio-level",t,Number(t))}),new Promise((t,e)=>{this.connectResolve=t,this.connectReject=e})}disconnect(){this.disconnectInternal(!1)}createSignaling(){return new l.g(this.wsUrl,this.config.sessionToken,this.handleSignalingMessage.bind(this),this.handleSignalingClose.bind(this),this.handleSignalingError.bind(this))}async openAttachedSignaling(t){for(let e=1;;e++){this.attachErrorCode=null;let i=null;try{const e=await t.getCode();if(this.ended)throw new Error("Call ended");if(i=this.createSignaling(),this.attaching=!0,await i.connectWithFirstFrame({type:"attach",code:e}),this.ended)throw new Error("Call ended");return i}catch(t){i?.close();const s=this.attachErrorCode;if("internal_error"===s&&e<2)continue;this.failSignaling(s?m[s]??"Could not start the call.":p(t))}finally{this.attaching=!1}}}failSignaling(t){throw this.cleanupLocalStream(),this.emitError(t),new Error(t)}disconnectInternal(t){if(this.ended=!0,!t)try{this.signaling?.send({type:"hangup"})}catch{}try{this.qualityMonitor?.stop(),this.qualityMonitor=null}catch{}try{this.stallDetector?.stop(),this.stallDetector=null}catch{}try{this.audioMonitor?.stop(),this.audioMonitor=null}catch{}try{this.remoteAudioMonitor?.stop(),this.remoteAudioMonitor=null}catch{}try{this.pc?.close(),this.pc=null}catch{}this.cleanupLocalStream();try{this.signaling?.close(),this.signaling=null}catch{}try{this.remoteAudio&&(this.remoteAudio.srcObject=null,this.remoteAudio.remove(),this.remoteAudio=null),this.remoteStream=null}catch{}this.clearIceRestart(),this.iceRestartAttempts=0,this.connectReject&&this.connectReject(new Error("Call disconnected before connection completed")),this.connectResolve=null,this.connectReject=null,this.resuming=!1,this.reconnectAttempts=0,this.reconnectErrorCode=null,this.originalSessionID=void 0,this.originalConnectionID=void 0,this.pendingCandidates=[],this.brainResponseChunks.clear(),this.interruptedRequests.clear(),this.setState("idle")}toggleMute(){const t=this.localStream?.getAudioTracks()[0];if(t){t.enabled=!t.enabled;const e=!t.enabled;return this.signaling?.send({type:"mute",muted:e}),e}return!1}isMuted(){return!(this.localStream?.getAudioTracks()[0]?.enabled??1)}getLocalStream(){return this.localStream}getRemoteStream(){return this.remoteStream}sendDigits(t){for(const e of t)this.signaling?.send({type:"dtmf",digit:e})}on(t,e){const i=this.eventListeners.get(t)??new Set;return i.add(e),this.eventListeners.set(t,i),()=>this.off(t,e)}off(t,e){const i=this.eventListeners.get(t);i&&(i.delete(e),0===i.size&&this.eventListeners.delete(t))}handleRemoteTrack(t){const[e]=t.streams;this.remoteStream=e??null,this.remoteAudio=document.createElement("audio"),this.remoteAudio.srcObject=e,this.remoteAudio.autoplay=!0,document.body.appendChild(this.remoteAudio),this.remoteAudio.play().catch(()=>{}),this.remoteAudioMonitor=new r.h,this.remoteAudioMonitor.start(e,t=>{Number(t)>.02&&this.emit("remote-audio",t,Number(t))})}handleConnectionStateChange(){if(this.pc)switch(this.log("connectionState %s",this.pc.connectionState),this.pc.connectionState){case"connecting":this.setState("connecting");break;case"connected":{const t="reconnecting"===this.state;this.setState("connected"),this.clearIceRestart(),this.iceRestartAttempts=0,t&&this.emit("warning-cleared",void 0,"reconnect"),this.qualityMonitor?.stop(),this.qualityMonitor=new a.M(this.pc,(t,e)=>this.emit("warning",e,t),t=>this.emit("warning-cleared",void 0,t)),this.qualityMonitor.start(),this.stallDetector?.stop(),this.stallDetector=new h.$(this.pc,()=>{this.log("media stall -> ICE restart"),this.triggerIceRestart()},()=>{this.log("media recovered"),this.clearIceRestart(),this.iceRestartAttempts=0,"reconnecting"===this.state&&this.setState("connected"),this.emit("warning-cleared",void 0,"reconnect")}),this.stallDetector.start(),this.connectResolve?.(),this.connectResolve=null,this.connectReject=null;break}case"disconnected":if(this.resuming)break;this.triggerIceRestart();break;case"failed":if(this.resuming){this.connectReject?.(new Error("ICE connection failed")),this.connectResolve=null,this.connectReject=null;break}if(this.connectReject){this.connectReject(new Error("ICE connection failed")),this.connectResolve=null,this.connectReject=null,this.emitError("Connection failed"),this.cleanup();break}this.iceRestarting?this.onRestartAttemptExpired():this.triggerIceRestart();break;case"closed":this.cleanup()}}async triggerIceRestart(){if(this.resuming||!this.pc||!this.signaling)return;if(this.iceRestarting||this.iceRestartAttempts>=2)return;if(!this.signaling.isConnected)return this.isResumeEligible()?void this.beginResume(d):(this.emitError("Connection lost"),void this.cleanup());const t=this.pc,e=this.signaling;this.iceRestarting=!0,this.iceRestartAttempts++,this.log("ICE restart attempt %d/%d",this.iceRestartAttempts,2),this.setState("reconnecting"),this.emit("warning","Reconnecting…","reconnect");try{const i=await t.createOffer({iceRestart:!0});if(!i.sdp)throw new Error("createOffer returned no SDP");await t.setLocalDescription(i),e.send({type:"offer",sdp:i.sdp})}catch{if(this.resuming||!this.iceRestarting||this.pc!==t)return;return this.iceRestarting=!1,this.emitError("Connection lost"),void this.cleanup()}this.iceRestarting&&this.pc===t&&(this.iceRestartTimeout=setTimeout(()=>{this.onRestartAttemptExpired()},1e4),this.iceRestartAnswerTimeout=setTimeout(()=>{this.onIceRestartUnanswered()},3e3))}onIceRestartUnanswered(){this.iceRestartAnswerTimeout=null,this.iceRestarting&&this.isResumeEligible()&&(this.log("ICE restart offer unanswered -> resume"),this.beginResume(d))}onRestartAttemptExpired(){this.log("ICE restart attempt expired (attempts=%d)",this.iceRestartAttempts),this.iceRestartTimeout&&(clearTimeout(this.iceRestartTimeout),this.iceRestartTimeout=null),this.iceRestartAnswerTimeout&&(clearTimeout(this.iceRestartAnswerTimeout),this.iceRestartAnswerTimeout=null),this.iceRestarting=!1,this.iceRestartAttempts<2?this.triggerIceRestart():this.isResumeEligible()?this.beginResume(d):(this.emitError("Connection lost"),this.cleanup())}clearIceRestart(){this.iceRestarting&&this.log("ICE restart cleared"),this.iceRestartTimeout&&(clearTimeout(this.iceRestartTimeout),this.iceRestartTimeout=null),this.iceRestartAnswerTimeout&&(clearTimeout(this.iceRestartAnswerTimeout),this.iceRestartAnswerTimeout=null),this.iceRestarting=!1}handleSignalingMessage(t){switch(t.type){case"answer":this.handleAnswer(t.sdp);break;case"ice":this.addIceCandidate(t.candidate);break;case"transcript":this.emit("transcript",t.text,{text:t.text,request_id:t.request_id,timestamp:t.timestamp});break;case"brain_response":this.handleBrainResponse(t);break;case"interrupt":this.handleInterrupt(t);break;case"hangup":this.hangupReason=t.reason??null,this.disconnectInternal(!0);break;case"session_ready":this.originalSessionID=t.session_id,this.originalConnectionID=t.connection_id;break;case"reconnect_ack":this.originalConnectionID=t.connection_id;break;case"drain":this.handleDrain(t);break;case"error":this.attaching?this.attachErrorCode=t.error.code:this.resuming?(this.reconnectErrorCode=t.error.code,this.connectReject?.(new Error(`reconnect rejected: ${t.error.code}`))):this.emitError(t.error.message)}}handleSignalingClose(t,e){this.resuming?this.connectReject?.(new Error("Signaling closed during resume")):this.isResumeEligible()?this.beginResume(d):"idle"!==this.state&&this.disconnect()}handleSignalingError(t){this.resuming||this.isResumeEligible()||"connected"===this.state&&this.emitError("Signaling error")}isResumeEligible(){return!this.resuming&&("connected"===this.state||"reconnecting"===this.state)&&void 0!==this.originalSessionID&&void 0!==this.originalConnectionID}async beginResume(t=0){this.resuming=!0,this.setState("reconnecting"),this.clearIceRestart(),this.emit("warning","Reconnecting…","reconnect"),t>0&&(await this.sleep(Math.random()*t),!this.resuming)||await this.reconnect()}async handleDrain(t){if(!this.resuming)return this.isResumeEligible()?void await this.beginResume(Math.max(0,t.reconnect_delay_ms||0)):(this.emitError("Could not resume the call."),void this.disconnectInternal(!0))}async reconnect(){try{this.pc?.getSenders().forEach(t=>{t.track&&this.pc?.removeTrack(t)})}catch{}const t=this.localStream?.getAudioTracks()[0];if(!t||"live"!==t.readyState)return this.emitError("Microphone is no longer available."),void this.disconnectInternal(!0);for(this.teardownForReconnect();this.reconnectAttempts<3;){this.reconnectErrorCode=null;try{if(this.config=await this.fetchConfig(),!this.resuming)return;return this.signaling=this.createSignaling(),await this.signaling.connectWithFirstFrame({type:"reconnect",session_id:this.originalSessionID,connection_id:this.originalConnectionID}),await this.buildReconnectPeerConnection(t),this.reconnectAttempts=0,this.resuming=!1,void this.emit("warning-cleared",void 0,"reconnect")}catch(t){const e=this.reconnectErrorCode;if(e&&u.has(e))return this.emitError(g[e]??"Could not resume the call."),void this.disconnectInternal(!0);if(t instanceof c.Xq)return this.emitError(t.message),void this.disconnectInternal(!0);const i=t instanceof c.PA?t.message:"Reconnecting…";if(this.emit("warning",i,"reconnect"),this.teardownForReconnect(),this.reconnectAttempts++,this.reconnectAttempts>=3)break;if(await this.sleep(500*this.reconnectAttempts),!this.resuming)return}}this.emitError("Could not resume the call."),this.disconnectInternal(!0)}buildReconnectPeerConnection(t){return this.pc=new RTCPeerConnection({iceServers:this.config.iceServers,iceTransportPolicy:this.config.iceTransportPolicy||"relay"}),this.pc.addTrack(t,this.localStream),this.pc.ontrack=this.handleRemoteTrack.bind(this),this.pc.onicecandidate=t=>{t.candidate&&this.signaling?.send({type:"ice",candidate:t.candidate.toJSON()})},this.pc.onconnectionstatechange=this.handleConnectionStateChange.bind(this),this.audioMonitor=new r.h,this.audioMonitor.start(this.localStream,t=>{this.emit("audio-level",t,Number(t))}),new Promise((t,e)=>{const i=setTimeout(()=>{this.connectResolve=null,this.connectReject=null,e(new Error("Reconnect media resume timed out"))},15e3);this.connectResolve=()=>{clearTimeout(i),t()},this.connectReject=t=>{clearTimeout(i),e(t)},(async()=>{try{const t=await this.pc.createOffer();if(!t.sdp)throw new Error("createOffer returned no SDP");await this.pc.setLocalDescription(t),this.signaling.send({type:"offer",sdp:t.sdp})}catch(t){const e=t instanceof Error?t.name:"createOffer";this.connectReject?.(new Error(f[e]||"Failed to create call offer"))}})()})}teardownForReconnect(){try{this.qualityMonitor?.stop(),this.qualityMonitor=null}catch{}try{this.stallDetector?.stop(),this.stallDetector=null}catch{}try{this.audioMonitor?.stop(),this.audioMonitor=null}catch{}try{this.remoteAudioMonitor?.stop(),this.remoteAudioMonitor=null}catch{}try{this.pc?.close(),this.pc=null}catch{}try{this.signaling?.close(4001,"Resuming"),this.signaling=null}catch{}try{this.remoteAudio&&(this.remoteAudio.srcObject=null,this.remoteAudio.remove(),this.remoteAudio=null),this.remoteStream=null}catch{}this.clearIceRestart(),this.iceRestartAttempts=0,this.connectResolve=null,this.connectReject=null,this.pendingCandidates=[]}sleep(t){return new Promise(e=>setTimeout(e,t))}async handleAnswer(t){if(this.iceRestartAnswerTimeout&&(clearTimeout(this.iceRestartAnswerTimeout),this.iceRestartAnswerTimeout=null),this.pc)try{await this.pc.setRemoteDescription({type:"answer",sdp:t}),await this.flushPendingCandidates()}catch{this.emitError(f.setRemoteDescription)}}async addIceCandidate(t){if(this.pc?.remoteDescription)try{await this.pc.addIceCandidate(t)}catch{}else this.pendingCandidates.push(t)}async flushPendingCandidates(){for(const t of this.pendingCandidates){if(!this.pc)break;try{await this.pc.addIceCandidate(t)}catch{}}this.pendingCandidates=[]}cleanup(){this.disconnect()}cleanupLocalStream(){try{this.localStream?.getTracks().forEach(t=>t.stop()),this.localStream=null}catch{}}setState(t){this.state=t,this.emit("state-change",t,t)}handleBrainResponse(t){const e=t.request_id;if(this.interruptedRequests.has(e))return;if(t.final){const t=this.assembleBrainResponse(e);return void this.emit("brain-response",t,{text:t,request_id:e,final:!0})}let i=this.brainResponseChunks.get(e);i||(i=new Map,this.brainResponseChunks.set(e,i)),i.set(t.index,t.text);const s=this.assembleBrainResponse(e);this.emit("brain-response",s,{text:s,request_id:e,final:!1})}assembleBrainResponse(t){const e=this.brainResponseChunks.get(t);return e?[...e.entries()].sort((t,e)=>t[0]-e[0]).map(([,t])=>t).join(" "):""}handleInterrupt(t){const e=this.interruptedRequests.get(t.request_id),i=void 0===e?t.heard_index:Math.min(e,t.heard_index);this.interruptedRequests.set(t.request_id,i);const s=this.brainResponseChunks.get(t.request_id),n=s?[...s.entries()].sort((t,e)=>t[0]-e[0]):[],r=t=>n.filter(([e])=>t(e)).map(([,t])=>t).join(" "),o=r(t=>t<=i),a=r(t=>t>i);this.emit("interrupt",o,{request_id:t.request_id,heard_index:i,timestamp:t.timestamp,heardText:o,unheardText:a})}emitError(t){this.emit("error",t),this.setState("error")}emit(t,e,i){const s={state:t,message:e,data:i},n=t=>{const e=this.eventListeners.get(t);e&&e.forEach(t=>{try{t(s)}catch{}})};n(t),n("any")}}}}]);
|
|
2
|
+
"use strict";(this.webpackChunkMoveoAI||=[]).push([[882],{2229(t,e,i){i.d(e,{h:()=>s});class s{constructor(){this.analyserNode=null,this.audioSource=null,this.animationId=null,this.audioContext=null}start(t,e){try{this.audioContext=new AudioContext,this.audioSource=this.audioContext.createMediaStreamSource(t),this.analyserNode=this.audioContext.createAnalyser(),this.analyserNode.fftSize=256,this.analyserNode.smoothingTimeConstant=.3,this.audioSource.connect(this.analyserNode);const i=new Float32Array(this.analyserNode.fftSize),s=()=>{if(!this.analyserNode)return void this.stop();this.analyserNode.getFloatTimeDomainData(i);let t=0;for(const e of i)t+=e*e;const n=Math.sqrt(t/i.length),r=Math.min(1,10*n);e((r>.01?r:0).toFixed(3)),this.animationId=requestAnimationFrame(s)};s()}catch{}}stop(){if(null!==this.animationId&&(cancelAnimationFrame(this.animationId),this.animationId=null),this.audioSource){try{this.audioSource.disconnect()}catch{}this.audioSource=null}this.audioContext&&(this.audioContext.close().catch(()=>{}),this.audioContext=null),this.analyserNode=null}}},6815(t,e,i){i.d(e,["n",0,{NotAllowedError:"Microphone permission denied",NotFoundError:"No microphone found",NotReadableError:"Microphone is in use by another application",OverconstrainedError:"Microphone does not support required settings"}])},3564(t,e,i){i.d(e,{M:()=>r});var s=i(2076);const n={"high-rtt":"High network latency detected","low-mos":"Poor call quality detected","high-jitter":"Network instability detected","high-packet-loss":"Packet loss detected"};class r{constructor(t,e,i,s=5e3){this.pc=t,this.onWarning=e,this.onWarningCleared=i,this.pollIntervalMs=s,this.interval=null,this.prevStats=null,this.activeWarnings=new Set}start(){this.interval=setInterval(()=>{this.pollStats()},this.pollIntervalMs)}stop(){null!==this.interval&&(clearInterval(this.interval),this.interval=null),this.prevStats=null,this.activeWarnings.clear()}async pollStats(){try{const t=await this.pc.getStats();let e=null,i=0,n=0;const r=(0,s._)(t),o=null!=r?.currentRoundTripTime?1e3*r.currentRoundTripTime:null;t.forEach(t=>{if("inbound-rtp"===t.type&&"audio"===t.kind){const s=t;e=s.jitter??null,i=s.packetsReceived??0,n=s.packetsLost??0}});let a=0;if(this.prevStats){const t=i-this.prevStats.packetsReceived,e=n-this.prevStats.packetsLost,s=t+e;a=s>0?e/s:0}if(this.prevStats={packetsReceived:i,packetsLost:n},this.checkWarning("high-rtt",null!==o&&o>400),this.checkWarning("high-jitter",null!==e&&e>.03),this.checkWarning("high-packet-loss",a>.03),null!==o&&null!==e){const t=93.2-1e3*e*2-100*a*40-.1*Math.max(0,o-150),i=1+.035*t+t*(t-60)*(100-t)*7e-6;this.checkWarning("low-mos",i<3)}}catch{}}checkWarning(t,e){e&&!this.activeWarnings.has(t)?(this.activeWarnings.add(t),this.onWarning(t,n[t]||t)):!e&&this.activeWarnings.has(t)&&(this.activeWarnings.delete(t),this.onWarningCleared(t))}}},6845(t,e,i){i.d(e,{PA:()=>s,TM:()=>r,Xq:()=>n});class s extends Error{constructor(t="Voice service is busy, please try again."){super(t),this.name="ServerBusyError"}}class n extends Error{constructor(t){super(t),this.name="ConfigUnavailableError"}}class r extends Error{constructor(t="Too many attempts. Please try again in a moment."){super(t),this.name="RateLimitedError"}}},7943(t,e,i){i.d(e,{$:()=>o});var s=i(7833),n=i.n(s),r=i(2076);class o{constructor(t,e,i,s=1e3,r=3,o=!function(t=navigator.userAgent){return/\b(iPad|iPhone|iPod)\b/.test(t)||/AppleWebKit/.test(t)&&!/Chrome|Chromium|CriOS|Edg|OPR/.test(t)}()){this.pc=t,this.onStall=e,this.onRecover=i,this.pollIntervalMs=s,this.flatPollsThreshold=r,this.escalate=o,this.log=n()("moveo:webrtc:media"),this.interval=null,this.prevCounts=null,this.flatPolls=0,this.stalled=!1,this.cannotAssessLogged=!1}start(){this.log("started (escalate=%s)",this.escalate),this.interval=setInterval(()=>{this.poll()},this.pollIntervalMs)}stop(){null!==this.interval&&(clearInterval(this.interval),this.interval=null),this.prevCounts=null,this.flatPolls=0,this.stalled=!1,this.cannotAssessLogged=!1}async poll(){try{const t=await this.pc.getStats(),e=function(t){const e=new Map;return t.forEach(t=>{if("inbound-rtp"===t.type){const i=t;"audio"===i.kind&&void 0!==i.packetsReceived&&e.set(i.id,i.packetsReceived)}}),e}(t);if(0===e.size)return void(this.cannotAssessLogged||(this.cannotAssessLogged=!0,this.log("cannot assess: no audio inbound-rtp with packetsReceived")));this.cannotAssessLogged=!1;const i=this.prevCounts;if(this.prevCounts=e,null===i)return void this.log("assessing %d audio inbound-rtp report(s)",e.size);let s=!1,n=!1,r=0,o=!1;if(e.forEach((t,e)=>{const a=i.get(e);void 0!==a?(r++,t>a?s=!0:t<a&&(n=!0)):o=!0}),s)return this.stalled&&(this.stalled=!1,this.log("recovered%s (pair %s)",this.escalate?"":" (log-only)",this.describeSelectedPair(t)),this.escalate&&this.onRecover()),void(this.flatPolls=0);if(0===r||n||o)return this.log("inbound-rtp reports reseeded (comparable=%d new=%s)",r,o),void(this.flatPolls=0);this.flatPolls++,this.log("flat poll reports=%d flat=%d",e.size,this.flatPolls),this.flatPolls>=this.flatPollsThreshold&&!this.stalled&&(this.stalled=!0,this.log("stall declared%s after %d flat polls (pair %s)",this.escalate?"":" (log-only, escalation disabled)",this.flatPolls,this.describeSelectedPair(t)),this.escalate&&this.onStall())}catch{}}describeSelectedPair(t){const e=(0,r._)(t);return e?(0,r.t)(t,e):"?"}}},2076(t,e,i){function s(t){let e;const i=[];t.forEach(t=>{if("transport"===t.type){const{selectedCandidatePairId:i}=t;i&&!e&&(e=i)}else"candidate-pair"===t.type&&i.push(t)});const s=e&&i.find(t=>t.id===e)||i.find(t=>!0===t.selected);if(s)return s;let n;for(const t of i){if(!t.nominated)continue;if(void 0===n){n=t;continue}const e=n.lastPacketReceivedTimestamp,i=t.lastPacketReceivedTimestamp;void 0!==i&&(void 0===e||i>e)&&(n=t)}return n}function n(t,e){const i=e=>{if(!e)return"?";const i=t.get(e);if(!i)return"?";const s=`${i.candidateType??"?"}/${i.protocol??"?"}`;return i.relayProtocol?`${s}(${i.relayProtocol})`:s};return`${i(e.localCandidateId)} -> ${i(e.remoteCandidateId)}`}i.d(e,{_:()=>s,t:()=>n})},4623(t,e,i){i.d(e,{g:()=>r});var s=i(7833),n=i.n(s);class r{constructor(t,e,i,s,r){this.url=t,this.sessionToken=e,this.onMessage=i,this.onClose=s,this.onError=r,this.log=n()("moveo:webrtc:media"),this.ws=null}connect(){return new Promise((t,e)=>{this.log("connecting to %s",this.url);const i=`${this.url}?token=${encodeURIComponent(this.sessionToken)}`;this.ws=new WebSocket(i);const s=setTimeout(()=>{this.close(),e(new Error("Signaling connection timed out"))},1e4);this.ws.onopen=()=>{},this.ws.onmessage=e=>{let i;try{i=JSON.parse(e.data)}catch{return}this.log("recv %s",i.type),"welcome"===i.type&&(clearTimeout(s),t()),this.onMessage(i)},this.ws.onerror=t=>{clearTimeout(s),this.log("websocket error"),this.onError(t),e(new Error("Signaling WebSocket error"))},this.ws.onclose=t=>{clearTimeout(s),this.log("closed code=%d reason=%s",t.code,t.reason),e(new Error("Signaling WebSocket closed before connection established")),this.onClose(t.code,t.reason)}})}connectWithFirstFrame(t,e){const i="attach"===t.type?"attach_ack":"reconnect_ack";return new Promise((s,n)=>{this.log("%s to %s",t.type,this.url);const r=`${this.url}?token=${encodeURIComponent(this.sessionToken)}`;this.ws=new WebSocket(r);let o=!1;const a=setTimeout(()=>{o||(o=!0,"reconnect"===t.type?this.close(4001,"Resume timed out"):this.close(),n(new Error(`Signaling ${t.type} timed out`)))},1e4);this.ws.onopen=()=>{this.ws?.send(JSON.stringify(t)),e?.()},this.ws.onmessage=e=>{let r;try{r=JSON.parse(e.data)}catch{return}this.log("recv %s",r.type),this.onMessage(r),o||(r.type===i?(o=!0,clearTimeout(a),s()):"error"===r.type&&(o=!0,clearTimeout(a),n(new Error(`${t.type} rejected: ${r.error.code}`))))},this.ws.onerror=t=>{this.log("websocket error"),this.onError(t),o||(o=!0,clearTimeout(a),n(new Error("Signaling WebSocket error")))},this.ws.onclose=e=>{this.log("closed code=%d reason=%s",e.code,e.reason),o||(o=!0,clearTimeout(a),n(new Error(`Signaling WebSocket closed before ${t.type} established`))),this.onClose(e.code,e.reason)}})}send(t){this.ws&&this.ws.readyState===WebSocket.OPEN&&(this.log("send %s",t.type),this.ws.send(JSON.stringify(t)))}close(t=1e3,e="Client closed"){this.ws&&(this.ws.onopen=null,this.ws.onclose=null,this.ws.onerror=null,this.ws.onmessage=null,this.ws.close(t,e),this.ws=null)}get isConnected(){return this.ws?.readyState===WebSocket.OPEN}}},5882(t,e,i){i.r(e),i.d(e,{WebRTCProvider:()=>w}),i(6752);var s=i(7833),n=i.n(s),r=i(2229),o=i(6815),a=i(3564),c=i(6845),h=i(7943),l=i(4623);const d=500,u=new Set(["session_expired","session_inactive","invalid_credential","max_recovery_exceeded","unexpected_first_frame"]),g={session_expired:"Your session has expired.",session_inactive:"The call has ended.",invalid_credential:"Could not resume the call.",max_recovery_exceeded:"Could not resume the call after several attempts.",unexpected_first_frame:"Could not resume the call."},m={invalid_code:"Could not start the call.",used:"A call is already running in this conversation.",session_ended:"This conversation has ended.",attach_unsupported:"Calls are not available in this conversation.",internal_error:"Could not start the call."},p=t=>t instanceof Error?t.message:"Signaling connection failed",f={...o.n,setRemoteDescription:"Connection setup failed",setLocalDescription:"Connection setup failed",addIceCandidate:"Network configuration failed",createOffer:"Browser audio setup failed"};class w{constructor(t,e){this.wsUrl=t,this.fetchConfig=e,this.log=n()("moveo:webrtc:media"),this.pc=null,this.signaling=null,this.localStream=null,this.remoteStream=null,this.remoteAudio=null,this.remoteAudioMonitor=null,this.audioMonitor=null,this.qualityMonitor=null,this.stallDetector=null,this.pendingCandidates=[],this.state="idle",this.eventListeners=new Map,this.iceRestarting=!1,this.iceRestartAttempts=0,this.iceRestartTimeout=null,this.iceRestartAnswerTimeout=null,this.connectResolve=null,this.connectReject=null,this.resuming=!1,this.reconnectAttempts=0,this.reconnectErrorCode=null,this.attaching=!1,this.attachErrorCode=null,this.ended=!1,this.hangupReason=null,this.attachSent=!1,this.attachSignaling=null,this.brainResponseChunks=new Map,this.interruptedRequests=new Map}get serverHangupReason(){return this.hangupReason}get attached(){return this.attachSent}setConfig(t){this.config=t}async connect(t){try{return await this.runConnect(t)}catch(t){if("connecting"===this.state){const t="Could not start the call.";throw this.cleanup(),this.emitError(t),new Error(t)}throw t}}async runConnect(t){if(!this.config)throw new Error("Voice config not set; call setConfig() before connect().");this.ended=!1,this.hangupReason=null,this.attachSent=!1,this.setState("connecting");try{this.localStream=await navigator.mediaDevices.getUserMedia({audio:{echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0}})}catch(t){const e=t instanceof Error?t.name:"",i=f[e]||"Microphone access failed";throw this.emitError(i),new Error(i)}if(t)this.signaling=await this.openAttachedSignaling(t);else{this.signaling=this.createSignaling();try{await this.signaling.connect()}catch(t){this.failSignaling(p(t))}}this.pc=new RTCPeerConnection({iceServers:this.config.iceServers,iceTransportPolicy:this.config.iceTransportPolicy||"relay"});const e=this.localStream.getAudioTracks()[0];this.pc.addTrack(e,this.localStream),this.pc.ontrack=this.handleRemoteTrack.bind(this),this.pc.onicecandidate=t=>{t.candidate&&this.signaling?.send({type:"ice",candidate:t.candidate.toJSON()})},this.pc.onconnectionstatechange=this.handleConnectionStateChange.bind(this);try{const t=await this.pc.createOffer();if(!t.sdp)throw new Error("createOffer returned no SDP");await this.pc.setLocalDescription(t),this.signaling.send({type:"offer",sdp:t.sdp})}catch(t){this.cleanup();const e=t instanceof Error?t.name:"createOffer",i=f[e]||"Failed to create call offer";throw this.emitError(i),new Error(i)}return this.audioMonitor=new r.h,this.audioMonitor.start(this.localStream,t=>{this.emit("audio-level",t,Number(t))}),new Promise((t,e)=>{this.connectResolve=t,this.connectReject=e})}disconnect(){this.disconnectInternal(!1)}createSignaling(){return new l.g(this.wsUrl,this.config.sessionToken,this.handleSignalingMessage.bind(this),this.handleSignalingClose.bind(this),this.handleSignalingError.bind(this))}async openAttachedSignaling(t){for(let e=1;;e++){this.attachErrorCode=null;let i=null;try{const e=await t.getCode();if(this.ended)throw new Error("Call ended");if(i=this.createSignaling(),this.attachSignaling=i,this.attaching=!0,await i.connectWithFirstFrame({type:"attach",code:e},()=>{this.attachSent=!0}),this.ended)throw new Error("Call ended");return i}catch(t){i?.close();const s=this.attachErrorCode;if(s&&(this.attachSent=!1),"internal_error"===s&&e<2)continue;this.failSignaling(s?m[s]??"Could not start the call.":p(t))}finally{this.attaching=!1,this.attachSignaling=null}}}failSignaling(t){throw this.cleanupLocalStream(),this.emitError(t),new Error(t)}disconnectInternal(t){if(this.ended=!0,this.attachSent||this.attachSignaling?.close(),!t)try{this.signaling?.send({type:"hangup"})}catch{}try{this.qualityMonitor?.stop(),this.qualityMonitor=null}catch{}try{this.stallDetector?.stop(),this.stallDetector=null}catch{}try{this.audioMonitor?.stop(),this.audioMonitor=null}catch{}try{this.remoteAudioMonitor?.stop(),this.remoteAudioMonitor=null}catch{}try{this.pc?.close(),this.pc=null}catch{}this.cleanupLocalStream();try{this.signaling?.close(),this.signaling=null}catch{}try{this.remoteAudio&&(this.remoteAudio.srcObject=null,this.remoteAudio.remove(),this.remoteAudio=null),this.remoteStream=null}catch{}this.clearIceRestart(),this.iceRestartAttempts=0,this.connectReject&&this.connectReject(new Error("Call disconnected before connection completed")),this.connectResolve=null,this.connectReject=null,this.resuming=!1,this.reconnectAttempts=0,this.reconnectErrorCode=null,this.originalSessionID=void 0,this.originalConnectionID=void 0,this.pendingCandidates=[],this.brainResponseChunks.clear(),this.interruptedRequests.clear(),this.setState("idle")}toggleMute(){const t=this.localStream?.getAudioTracks()[0];if(t){t.enabled=!t.enabled;const e=!t.enabled;return this.signaling?.send({type:"mute",muted:e}),e}return!1}isMuted(){return!(this.localStream?.getAudioTracks()[0]?.enabled??1)}getLocalStream(){return this.localStream}getRemoteStream(){return this.remoteStream}sendDigits(t){for(const e of t)this.signaling?.send({type:"dtmf",digit:e})}on(t,e){const i=this.eventListeners.get(t)??new Set;return i.add(e),this.eventListeners.set(t,i),()=>this.off(t,e)}off(t,e){const i=this.eventListeners.get(t);i&&(i.delete(e),0===i.size&&this.eventListeners.delete(t))}handleRemoteTrack(t){const[e]=t.streams;this.remoteStream=e??null,this.remoteAudio=document.createElement("audio"),this.remoteAudio.srcObject=e,this.remoteAudio.autoplay=!0,document.body.appendChild(this.remoteAudio),this.remoteAudio.play().catch(()=>{}),this.remoteAudioMonitor=new r.h,this.remoteAudioMonitor.start(e,t=>{Number(t)>.02&&this.emit("remote-audio",t,Number(t))})}handleConnectionStateChange(){if(this.pc)switch(this.log("connectionState %s",this.pc.connectionState),this.pc.connectionState){case"connecting":this.setState("connecting");break;case"connected":{const t="reconnecting"===this.state;this.setState("connected"),this.clearIceRestart(),this.iceRestartAttempts=0,t&&this.emit("warning-cleared",void 0,"reconnect"),this.qualityMonitor?.stop(),this.qualityMonitor=new a.M(this.pc,(t,e)=>this.emit("warning",e,t),t=>this.emit("warning-cleared",void 0,t)),this.qualityMonitor.start(),this.stallDetector?.stop(),this.stallDetector=new h.$(this.pc,()=>{this.log("media stall -> ICE restart"),this.triggerIceRestart()},()=>{this.log("media recovered"),this.clearIceRestart(),this.iceRestartAttempts=0,"reconnecting"===this.state&&this.setState("connected"),this.emit("warning-cleared",void 0,"reconnect")}),this.stallDetector.start(),this.connectResolve?.(),this.connectResolve=null,this.connectReject=null;break}case"disconnected":if(this.resuming)break;this.triggerIceRestart();break;case"failed":if(this.resuming){this.connectReject?.(new Error("ICE connection failed")),this.connectResolve=null,this.connectReject=null;break}if(this.connectReject){this.connectReject(new Error("ICE connection failed")),this.connectResolve=null,this.connectReject=null,this.emitError("Connection failed"),this.cleanup();break}this.iceRestarting?this.onRestartAttemptExpired():this.triggerIceRestart();break;case"closed":this.cleanup()}}async triggerIceRestart(){if(this.resuming||!this.pc||!this.signaling)return;if(this.iceRestarting||this.iceRestartAttempts>=2)return;if(!this.signaling.isConnected)return this.isResumeEligible()?void this.beginResume(d):(this.emitError("Connection lost"),void this.cleanup());const t=this.pc,e=this.signaling;this.iceRestarting=!0,this.iceRestartAttempts++,this.log("ICE restart attempt %d/%d",this.iceRestartAttempts,2),this.setState("reconnecting"),this.emit("warning","Reconnecting…","reconnect");try{const i=await t.createOffer({iceRestart:!0});if(!i.sdp)throw new Error("createOffer returned no SDP");await t.setLocalDescription(i),e.send({type:"offer",sdp:i.sdp})}catch{if(this.resuming||!this.iceRestarting||this.pc!==t)return;return this.iceRestarting=!1,this.emitError("Connection lost"),void this.cleanup()}this.iceRestarting&&this.pc===t&&(this.iceRestartTimeout=setTimeout(()=>{this.onRestartAttemptExpired()},1e4),this.iceRestartAnswerTimeout=setTimeout(()=>{this.onIceRestartUnanswered()},3e3))}onIceRestartUnanswered(){this.iceRestartAnswerTimeout=null,this.iceRestarting&&this.isResumeEligible()&&(this.log("ICE restart offer unanswered -> resume"),this.beginResume(d))}onRestartAttemptExpired(){this.log("ICE restart attempt expired (attempts=%d)",this.iceRestartAttempts),this.iceRestartTimeout&&(clearTimeout(this.iceRestartTimeout),this.iceRestartTimeout=null),this.iceRestartAnswerTimeout&&(clearTimeout(this.iceRestartAnswerTimeout),this.iceRestartAnswerTimeout=null),this.iceRestarting=!1,this.iceRestartAttempts<2?this.triggerIceRestart():this.isResumeEligible()?this.beginResume(d):(this.emitError("Connection lost"),this.cleanup())}clearIceRestart(){this.iceRestarting&&this.log("ICE restart cleared"),this.iceRestartTimeout&&(clearTimeout(this.iceRestartTimeout),this.iceRestartTimeout=null),this.iceRestartAnswerTimeout&&(clearTimeout(this.iceRestartAnswerTimeout),this.iceRestartAnswerTimeout=null),this.iceRestarting=!1}handleSignalingMessage(t){switch(t.type){case"answer":this.handleAnswer(t.sdp);break;case"ice":this.addIceCandidate(t.candidate);break;case"transcript":this.emit("transcript",t.text,{text:t.text,request_id:t.request_id,timestamp:t.timestamp});break;case"brain_response":this.handleBrainResponse(t);break;case"interrupt":this.handleInterrupt(t);break;case"hangup":this.hangupReason=t.reason??"",this.disconnectInternal(!0);break;case"session_ready":this.originalSessionID=t.session_id,this.originalConnectionID=t.connection_id;break;case"reconnect_ack":this.originalConnectionID=t.connection_id;break;case"drain":this.handleDrain(t);break;case"error":this.attaching?this.attachErrorCode=t.error.code:this.resuming?(this.reconnectErrorCode=t.error.code,this.connectReject?.(new Error(`reconnect rejected: ${t.error.code}`))):this.emitError(t.error.message)}}handleSignalingClose(t,e){this.resuming?this.connectReject?.(new Error("Signaling closed during resume")):this.isResumeEligible()?this.beginResume(d):"idle"!==this.state&&this.disconnect()}handleSignalingError(t){this.resuming||this.isResumeEligible()||"connected"===this.state&&this.emitError("Signaling error")}isResumeEligible(){return!this.resuming&&("connected"===this.state||"reconnecting"===this.state)&&void 0!==this.originalSessionID&&void 0!==this.originalConnectionID}async beginResume(t=0){this.resuming=!0,this.setState("reconnecting"),this.clearIceRestart(),this.emit("warning","Reconnecting…","reconnect"),t>0&&(await this.sleep(Math.random()*t),!this.resuming)||await this.reconnect()}async handleDrain(t){if(!this.resuming)return this.isResumeEligible()?void await this.beginResume(Math.max(0,t.reconnect_delay_ms||0)):(this.emitError("Could not resume the call."),void this.disconnectInternal(!0))}async reconnect(){try{this.pc?.getSenders().forEach(t=>{t.track&&this.pc?.removeTrack(t)})}catch{}const t=this.localStream?.getAudioTracks()[0];if(!t||"live"!==t.readyState)return this.emitError("Microphone is no longer available."),void this.disconnectInternal(!0);for(this.teardownForReconnect();this.reconnectAttempts<3;){this.reconnectErrorCode=null;try{if(this.config=await this.fetchConfig(),!this.resuming)return;return this.signaling=this.createSignaling(),await this.signaling.connectWithFirstFrame({type:"reconnect",session_id:this.originalSessionID,connection_id:this.originalConnectionID}),await this.buildReconnectPeerConnection(t),this.reconnectAttempts=0,this.resuming=!1,void this.emit("warning-cleared",void 0,"reconnect")}catch(t){const e=this.reconnectErrorCode;if(e&&u.has(e))return this.emitError(g[e]??"Could not resume the call."),void this.disconnectInternal(!0);if(t instanceof c.Xq)return this.emitError(t.message),void this.disconnectInternal(!0);const i=t instanceof c.PA?t.message:"Reconnecting…";if(this.emit("warning",i,"reconnect"),this.teardownForReconnect(),this.reconnectAttempts++,this.reconnectAttempts>=3)break;if(await this.sleep(500*this.reconnectAttempts),!this.resuming)return}}this.emitError("Could not resume the call."),this.disconnectInternal(!0)}buildReconnectPeerConnection(t){return this.pc=new RTCPeerConnection({iceServers:this.config.iceServers,iceTransportPolicy:this.config.iceTransportPolicy||"relay"}),this.pc.addTrack(t,this.localStream),this.pc.ontrack=this.handleRemoteTrack.bind(this),this.pc.onicecandidate=t=>{t.candidate&&this.signaling?.send({type:"ice",candidate:t.candidate.toJSON()})},this.pc.onconnectionstatechange=this.handleConnectionStateChange.bind(this),this.audioMonitor=new r.h,this.audioMonitor.start(this.localStream,t=>{this.emit("audio-level",t,Number(t))}),new Promise((t,e)=>{const i=setTimeout(()=>{this.connectResolve=null,this.connectReject=null,e(new Error("Reconnect media resume timed out"))},15e3);this.connectResolve=()=>{clearTimeout(i),t()},this.connectReject=t=>{clearTimeout(i),e(t)},(async()=>{try{const t=await this.pc.createOffer();if(!t.sdp)throw new Error("createOffer returned no SDP");await this.pc.setLocalDescription(t),this.signaling.send({type:"offer",sdp:t.sdp})}catch(t){const e=t instanceof Error?t.name:"createOffer";this.connectReject?.(new Error(f[e]||"Failed to create call offer"))}})()})}teardownForReconnect(){try{this.qualityMonitor?.stop(),this.qualityMonitor=null}catch{}try{this.stallDetector?.stop(),this.stallDetector=null}catch{}try{this.audioMonitor?.stop(),this.audioMonitor=null}catch{}try{this.remoteAudioMonitor?.stop(),this.remoteAudioMonitor=null}catch{}try{this.pc?.close(),this.pc=null}catch{}try{this.signaling?.close(4001,"Resuming"),this.signaling=null}catch{}try{this.remoteAudio&&(this.remoteAudio.srcObject=null,this.remoteAudio.remove(),this.remoteAudio=null),this.remoteStream=null}catch{}this.clearIceRestart(),this.iceRestartAttempts=0,this.connectResolve=null,this.connectReject=null,this.pendingCandidates=[]}sleep(t){return new Promise(e=>setTimeout(e,t))}async handleAnswer(t){if(this.iceRestartAnswerTimeout&&(clearTimeout(this.iceRestartAnswerTimeout),this.iceRestartAnswerTimeout=null),this.pc)try{await this.pc.setRemoteDescription({type:"answer",sdp:t}),await this.flushPendingCandidates()}catch{this.emitError(f.setRemoteDescription)}}async addIceCandidate(t){if(this.pc?.remoteDescription)try{await this.pc.addIceCandidate(t)}catch{}else this.pendingCandidates.push(t)}async flushPendingCandidates(){for(const t of this.pendingCandidates){if(!this.pc)break;try{await this.pc.addIceCandidate(t)}catch{}}this.pendingCandidates=[]}cleanup(){this.disconnect()}cleanupLocalStream(){try{this.localStream?.getTracks().forEach(t=>t.stop()),this.localStream=null}catch{}}setState(t){this.state=t,this.emit("state-change",t,t)}handleBrainResponse(t){const e=t.request_id;if(this.interruptedRequests.has(e))return;if(t.final){const t=this.assembleBrainResponse(e);return void this.emit("brain-response",t,{text:t,request_id:e,final:!0})}let i=this.brainResponseChunks.get(e);i||(i=new Map,this.brainResponseChunks.set(e,i)),i.set(t.index,t.text);const s=this.assembleBrainResponse(e);this.emit("brain-response",s,{text:s,request_id:e,final:!1})}assembleBrainResponse(t){const e=this.brainResponseChunks.get(t);return e?[...e.entries()].sort((t,e)=>t[0]-e[0]).map(([,t])=>t).join(" "):""}handleInterrupt(t){const e=this.interruptedRequests.get(t.request_id),i=void 0===e?t.heard_index:Math.min(e,t.heard_index);this.interruptedRequests.set(t.request_id,i);const s=this.brainResponseChunks.get(t.request_id),n=s?[...s.entries()].sort((t,e)=>t[0]-e[0]):[],r=t=>n.filter(([e])=>t(e)).map(([,t])=>t).join(" "),o=r(t=>t<=i),a=r(t=>t>i);this.emit("interrupt",o,{request_id:t.request_id,heard_index:i,timestamp:t.timestamp,heardText:o,unheardText:a})}emitError(t){this.emit("error",t),this.setState("error")}emit(t,e,i){const s={state:t,message:e,data:i},n=t=>{const e=this.eventListeners.get(t);e&&e.forEach(t=>{try{t(s)}catch{}})};n(t),n("any")}}}}]);
|
package/dist/continue/index.html
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
#continue-chat {
|
|
17
|
+
position: relative;
|
|
17
18
|
box-sizing: border-box;
|
|
18
19
|
width: min(560px, 100%);
|
|
19
20
|
height: min(760px, calc(100dvh - 48px));
|
|
@@ -23,6 +24,41 @@
|
|
|
23
24
|
box-shadow: 0 2px 12px rgb(0 0 0 / 12%);
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
#continue-loading {
|
|
28
|
+
position: absolute;
|
|
29
|
+
inset: 0;
|
|
30
|
+
display: flex;
|
|
31
|
+
flex-direction: column;
|
|
32
|
+
gap: 16px;
|
|
33
|
+
align-items: center;
|
|
34
|
+
justify-content: center;
|
|
35
|
+
padding: 24px;
|
|
36
|
+
background: #fff;
|
|
37
|
+
font-size: 14px;
|
|
38
|
+
text-align: center;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* The same spinner as the widget's own loading screen. */
|
|
42
|
+
#continue-loading span {
|
|
43
|
+
width: 16px;
|
|
44
|
+
height: 16px;
|
|
45
|
+
box-sizing: border-box;
|
|
46
|
+
border: 3px solid rgb(0 0 0 / 30%);
|
|
47
|
+
border-top-color: #1b66d6;
|
|
48
|
+
border-radius: 50%;
|
|
49
|
+
animation: continue-spin 1s ease-in-out infinite;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#continue-loading p {
|
|
53
|
+
margin: 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@keyframes continue-spin {
|
|
57
|
+
to {
|
|
58
|
+
transform: rotate(360deg);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
26
62
|
#continue-used {
|
|
27
63
|
box-sizing: border-box;
|
|
28
64
|
max-width: 420px;
|
|
@@ -49,7 +85,7 @@
|
|
|
49
85
|
border-radius: 0;
|
|
50
86
|
box-shadow: none;
|
|
51
87
|
}
|
|
52
|
-
}</style></head><body><div id="continue-chat" hidden></div><div id="continue-used" hidden><h1>This link was already used</h1><p>Go back to the app where you started, or open the conversation where you already continued it.</p></div><script>(function () {
|
|
88
|
+
}</style></head><body><div id="continue-chat" hidden><div id="continue-loading"><span></span><p>Loading...</p></div></div><div id="continue-used" hidden><h1>This link was already used</h1><p>Go back to the app where you started, or open the conversation where you already continued it.</p></div><script>(function () {
|
|
53
89
|
const params = new URLSearchParams(window.location.search);
|
|
54
90
|
|
|
55
91
|
// `t` packs the integration id (16 bytes), the code (16 bytes) and the channels host.
|
|
@@ -103,7 +139,7 @@
|
|
|
103
139
|
};
|
|
104
140
|
|
|
105
141
|
// The widget's own language order: the stored choice, then the browser's.
|
|
106
|
-
const strings = {"ar":{"page_title":"تابع محادثتك","used_title":"تم استخدام هذا الرابط بالفعل","used_subtitle":"ارجع إلى التطبيق الذي بدأت منه، أو افتح المحادثة حيث تابعتها بالفعل."},"bg":{"page_title":"Продължете разговора си","used_title":"Тази връзка вече е използвана","used_subtitle":"Върнете се в приложението, където започнахте, или отворете разговора там, където вече сте го продължили."},"cs":{"page_title":"Pokračujte v konverzaci","used_title":"Tento odkaz už byl použit","used_subtitle":"Vraťte se do aplikace, kde jste začali, nebo otevřete konverzaci tam, kde jste v ní už pokračovali."},"da":{"page_title":"Fortsæt din samtale","used_title":"Dette link er allerede brugt","used_subtitle":"Gå tilbage til appen, hvor du startede, eller åbn samtalen, hvor du allerede har fortsat den."},"de":{"page_title":"Setzen Sie Ihr Gespräch fort","used_title":"Dieser Link wurde bereits verwendet","used_subtitle":"Kehren Sie zur App zurück, in der Sie begonnen haben, oder öffnen Sie das Gespräch dort, wo Sie es bereits fortgesetzt haben."},"el":{"page_title":"Συνεχίστε τη συνομιλία σας","used_title":"Αυτός ο σύνδεσμος έχει ήδη χρησιμοποιηθεί","used_subtitle":"Επιστρέψτε στην εφαρμογή όπου ξεκινήσατε ή ανοίξτε τη συνομιλία εκεί όπου τη συνεχίσατε ήδη."},"en":{"page_title":"Continue your conversation","used_title":"This link was already used","used_subtitle":"Go back to the app where you started, or open the conversation where you already continued it."},"es":{"page_title":"Continúe su conversación","used_title":"Este enlace ya se usó","used_subtitle":"Vuelva a la app donde empezó o abra la conversación donde ya la continuó."},"fr":{"page_title":"Continuez votre conversation","used_title":"Ce lien a déjà été utilisé","used_subtitle":"Revenez à l'application où vous avez commencé, ou ouvrez la conversation là où vous l'avez déjà poursuivie."},"id":{"page_title":"Lanjutkan percakapan Anda","used_title":"Tautan ini sudah digunakan","used_subtitle":"Kembali ke aplikasi tempat Anda memulai, atau buka percakapan tempat Anda sudah melanjutkannya."},"it":{"page_title":"Continua la conversazione","used_title":"Questo link è già stato usato","used_subtitle":"Torna all'app in cui hai iniziato, oppure apri la conversazione dove l'hai già continuata."},"ja":{"page_title":"会話を続ける","used_title":"このリンクはすでに使用されています","used_subtitle":"開始したアプリに戻るか、すでに続きを進めている会話を開いてください。"},"me":{"page_title":"Nastavite razgovor","used_title":"Ovaj link je već iskorišćen","used_subtitle":"Vratite se u aplikaciju u kojoj ste počeli ili otvorite razgovor tamo gdje ste ga već nastavili."},"nl-be":{"page_title":"Zet uw gesprek voort","used_title":"Deze link is al gebruikt","used_subtitle":"Ga terug naar de app waar u begon, of open het gesprek waar u het al hebt voortgezet."},"nl":{"page_title":"Zet je gesprek voort","used_title":"Deze link is al gebruikt","used_subtitle":"Ga terug naar de app waar je begon, of open het gesprek waar je het al hebt voortgezet."},"pl":{"page_title":"Kontynuuj rozmowę","used_title":"Ten link został już użyty","used_subtitle":"Wróć do aplikacji, w której rozpoczęła się rozmowa, lub otwórz ją tam, gdzie jest już kontynuowana."},"pt-br":{"page_title":"Continue sua conversa","used_title":"Este link já foi usado","used_subtitle":"Volte ao app onde você começou ou abra a conversa onde você já a continuou."},"pt":{"page_title":"Continue a sua conversa","used_title":"Esta ligação já foi utilizada","used_subtitle":"Volte à aplicação onde começou ou abra a conversa onde já a continuou."},"ro":{"page_title":"Continuă conversația","used_title":"Acest link a fost deja folosit","used_subtitle":"Revino la aplicația în care ai început sau deschide conversația acolo unde ai continuat-o deja."},"ru":{"page_title":"Продолжите разговор","used_title":"Эта ссылка уже использована","used_subtitle":"Вернитесь в приложение, где вы начали, или откройте разговор там, где вы уже его продолжили."},"sq":{"page_title":"Vazhdo bisedën","used_title":"Kjo lidhje është përdorur tashmë","used_subtitle":"Kthehu te aplikacioni ku fillove ose hap bisedën aty ku e ke vazhduar tashmë."},"sr":{"page_title":"Наставите разговор","used_title":"Ова веза је већ искоришћена","used_subtitle":"Вратите се у апликацију у којој сте почели или отворите разговор тамо где сте га већ наставили."},"sv":{"page_title":"Fortsätt konversationen","used_title":"Den här länken har redan använts","used_subtitle":"Gå tillbaka till appen där du började, eller öppna konversationen där du redan har fortsatt den."},"tl":{"page_title":"Ituloy ang pakikipag-usap mo","used_title":"Nagamit na ang link na ito","used_subtitle":"Bumalik sa app kung saan ka nagsimula, o buksan ang usapan kung saan mo na ito itinuloy."},"tr":{"page_title":"Görüşmenize devam edin","used_title":"Bu bağlantı zaten kullanıldı","used_subtitle":"Başladığınız uygulamaya geri dönün veya görüşmeyi zaten devam ettiğiniz yerde açın."},"zh":{"page_title":"继续您的对话","used_title":"此链接已被使用","used_subtitle":"请返回您开始对话的应用,或打开您已继续的对话。"}};
|
|
142
|
+
const strings = {"ar":{"page_title":"تابع محادثتك","loading":"جارٍ التحميل...","used_title":"تم استخدام هذا الرابط بالفعل","used_subtitle":"ارجع إلى التطبيق الذي بدأت منه، أو افتح المحادثة حيث تابعتها بالفعل."},"bg":{"page_title":"Продължете разговора си","loading":"Зареждане...","used_title":"Тази връзка вече е използвана","used_subtitle":"Върнете се в приложението, където започнахте, или отворете разговора там, където вече сте го продължили."},"cs":{"page_title":"Pokračujte v konverzaci","loading":"Načítání...","used_title":"Tento odkaz už byl použit","used_subtitle":"Vraťte se do aplikace, kde jste začali, nebo otevřete konverzaci tam, kde jste v ní už pokračovali."},"da":{"page_title":"Fortsæt din samtale","loading":"Indlæser...","used_title":"Dette link er allerede brugt","used_subtitle":"Gå tilbage til appen, hvor du startede, eller åbn samtalen, hvor du allerede har fortsat den."},"de":{"page_title":"Setzen Sie Ihr Gespräch fort","loading":"Wird geladen...","used_title":"Dieser Link wurde bereits verwendet","used_subtitle":"Kehren Sie zur App zurück, in der Sie begonnen haben, oder öffnen Sie das Gespräch dort, wo Sie es bereits fortgesetzt haben."},"el":{"page_title":"Συνεχίστε τη συνομιλία σας","loading":"Φόρτωση...","used_title":"Αυτός ο σύνδεσμος έχει ήδη χρησιμοποιηθεί","used_subtitle":"Επιστρέψτε στην εφαρμογή όπου ξεκινήσατε ή ανοίξτε τη συνομιλία εκεί όπου τη συνεχίσατε ήδη."},"en":{"page_title":"Continue your conversation","loading":"Loading...","used_title":"This link was already used","used_subtitle":"Go back to the app where you started, or open the conversation where you already continued it."},"es":{"page_title":"Continúe su conversación","loading":"Cargando...","used_title":"Este enlace ya se usó","used_subtitle":"Vuelva a la app donde empezó o abra la conversación donde ya la continuó."},"fr":{"page_title":"Continuez votre conversation","loading":"Chargement...","used_title":"Ce lien a déjà été utilisé","used_subtitle":"Revenez à l'application où vous avez commencé, ou ouvrez la conversation là où vous l'avez déjà poursuivie."},"id":{"page_title":"Lanjutkan percakapan Anda","loading":"Memuat...","used_title":"Tautan ini sudah digunakan","used_subtitle":"Kembali ke aplikasi tempat Anda memulai, atau buka percakapan tempat Anda sudah melanjutkannya."},"it":{"page_title":"Continua la conversazione","loading":"Caricamento...","used_title":"Questo link è già stato usato","used_subtitle":"Torna all'app in cui hai iniziato, oppure apri la conversazione dove l'hai già continuata."},"ja":{"page_title":"会話を続ける","loading":"読み込み中...","used_title":"このリンクはすでに使用されています","used_subtitle":"開始したアプリに戻るか、すでに続きを進めている会話を開いてください。"},"me":{"page_title":"Nastavite razgovor","loading":"Učitavam...","used_title":"Ovaj link je već iskorišćen","used_subtitle":"Vratite se u aplikaciju u kojoj ste počeli ili otvorite razgovor tamo gdje ste ga već nastavili."},"nl-be":{"page_title":"Zet uw gesprek voort","loading":"Bezig met laden...","used_title":"Deze link is al gebruikt","used_subtitle":"Ga terug naar de app waar u begon, of open het gesprek waar u het al hebt voortgezet."},"nl":{"page_title":"Zet je gesprek voort","loading":"Bezig met laden...","used_title":"Deze link is al gebruikt","used_subtitle":"Ga terug naar de app waar je begon, of open het gesprek waar je het al hebt voortgezet."},"pl":{"page_title":"Kontynuuj rozmowę","loading":"Ładowanie...","used_title":"Ten link został już użyty","used_subtitle":"Wróć do aplikacji, w której rozpoczęła się rozmowa, lub otwórz ją tam, gdzie jest już kontynuowana."},"pt-br":{"page_title":"Continue sua conversa","loading":"Carregando...","used_title":"Este link já foi usado","used_subtitle":"Volte ao app onde você começou ou abra a conversa onde você já a continuou."},"pt":{"page_title":"Continue a sua conversa","loading":"A carregar...","used_title":"Esta ligação já foi utilizada","used_subtitle":"Volte à aplicação onde começou ou abra a conversa onde já a continuou."},"ro":{"page_title":"Continuă conversația","loading":"Se încarcă...","used_title":"Acest link a fost deja folosit","used_subtitle":"Revino la aplicația în care ai început sau deschide conversația acolo unde ai continuat-o deja."},"ru":{"page_title":"Продолжите разговор","loading":"Загрузка...","used_title":"Эта ссылка уже использована","used_subtitle":"Вернитесь в приложение, где вы начали, или откройте разговор там, где вы уже его продолжили."},"sq":{"page_title":"Vazhdo bisedën","loading":"Duke u ngarkuar...","used_title":"Kjo lidhje është përdorur tashmë","used_subtitle":"Kthehu te aplikacioni ku fillove ose hap bisedën aty ku e ke vazhduar tashmë."},"sr":{"page_title":"Наставите разговор","loading":"Учитавам...","used_title":"Ова веза је већ искоришћена","used_subtitle":"Вратите се у апликацију у којој сте почели или отворите разговор тамо где сте га већ наставили."},"sv":{"page_title":"Fortsätt konversationen","loading":"Laddar...","used_title":"Den här länken har redan använts","used_subtitle":"Gå tillbaka till appen där du började, eller öppna konversationen där du redan har fortsatt den."},"tl":{"page_title":"Ituloy ang pakikipag-usap mo","loading":"Naglo-load...","used_title":"Nagamit na ang link na ito","used_subtitle":"Bumalik sa app kung saan ka nagsimula, o buksan ang usapan kung saan mo na ito itinuloy."},"tr":{"page_title":"Görüşmenize devam edin","loading":"Yükleniyor...","used_title":"Bu bağlantı zaten kullanıldı","used_subtitle":"Başladığınız uygulamaya geri dönün veya görüşmeyi zaten devam ettiğiniz yerde açın."},"zh":{"page_title":"继续您的对话","loading":"加载中...","used_title":"此链接已被使用","used_subtitle":"请返回您开始对话的应用,或打开您已继续的对话。"}};
|
|
107
143
|
const supported = (tag) => {
|
|
108
144
|
const lower = (tag || '').toLowerCase();
|
|
109
145
|
const base = lower.split('-')[0];
|
|
@@ -119,6 +155,8 @@
|
|
|
119
155
|
document.documentElement.lang = language === 'me' ? 'sr-ME' : language;
|
|
120
156
|
document.documentElement.dir = language === 'ar' ? 'rtl' : 'ltr';
|
|
121
157
|
document.title = strings[language].page_title;
|
|
158
|
+
document.querySelector('#continue-loading p').textContent =
|
|
159
|
+
strings[language].loading;
|
|
122
160
|
document.querySelector('#continue-used h1').textContent =
|
|
123
161
|
strings[language].used_title;
|
|
124
162
|
document.querySelector('#continue-used p').textContent =
|
|
@@ -157,7 +195,7 @@
|
|
|
157
195
|
const element = document.getElementById('continue-chat');
|
|
158
196
|
element.hidden = false;
|
|
159
197
|
|
|
160
|
-
const webpackFiles = `../iframe.min.js?
|
|
198
|
+
const webpackFiles = `../iframe.min.js?41a8de788d7a8653b886,../web-client.min.js?41a8de788d7a8653b886,../preview.css.min.js?41a8de788d7a8653b886,../audio-client.min.js?41a8de788d7a8653b886,../webrtc-client.min.js?41a8de788d7a8653b886,../telnyx-client.min.js?41a8de788d7a8653b886`.split(',');
|
|
161
199
|
const script = document.createElement('script');
|
|
162
200
|
script.src = webpackFiles.find((file) => file.includes('web-client.min.js'));
|
|
163
201
|
script.onload = function () {
|
|
@@ -168,9 +206,12 @@
|
|
|
168
206
|
widget_position: 'embed',
|
|
169
207
|
sticky_session: true,
|
|
170
208
|
continue_code: storedCode || '',
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
|
|
209
|
+
})
|
|
210
|
+
// The widget reports loaded only once it can show translated text.
|
|
211
|
+
.then(() => document.getElementById('continue-loading').remove())
|
|
212
|
+
.catch((error) => {
|
|
213
|
+
console.error(error);
|
|
214
|
+
});
|
|
174
215
|
};
|
|
175
216
|
document.head.appendChild(script);
|
|
176
217
|
})();</script></body></html>
|