ysyt-agent-sdk 1.0.23 → 1.0.25

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.
@@ -15,6 +15,7 @@ export declare class SIPClient {
15
15
  private reconnectTimer;
16
16
  private isOffline;
17
17
  private autoAnswerTimer;
18
+ private isManuallyStopped;
18
19
  constructor(config: {
19
20
  webSocket: string;
20
21
  server: string;
@@ -114,7 +114,7 @@ const ai=e=>(t,s)=>{void 0!==s?s.addInitializer(()=>{customElements.define(e,t)}
114
114
  border: 1px solid #595959;
115
115
  color: #595959;
116
116
  }
117
- `}connectedCallback(){super.connectedCallback(),setTimeout(()=>{this.remove()},this.duration+300)}render(){return Jt`<div>${this.content}</div>`}};oi([di({type:String})],ui.prototype,"type",void 0),oi([di({type:String})],ui.prototype,"content",void 0),oi([di({type:Number})],ui.prototype,"duration",void 0),ui=oi([ai("my-message")],ui);const pi=(e,t,s=3e3)=>{let i=document.querySelector("#message-container");i||(i=document.createElement("div"),i.id="message-container",Object.assign(i.style,{position:"fixed",top:"14px",left:"50%",transform:"translateX(-50%)",zIndex:"9999"}),document.body.appendChild(i));const r=document.createElement("my-message");r.setAttribute("type",e),r.setAttribute("content",t),r.setAttribute("duration",String(s)),r.style.setProperty("--fade-delay",s/1e3+"s"),i.appendChild(r)},fi=(e,t=3e3)=>pi("success",e,t),mi=(e,t=3e3)=>pi("error",e,t),vi=(e,t=3e3)=>pi("warning",e,t),wi="/v1/aicc/bmserver",yi="/v1/aicc/ccs";let bi="",Ti="",Si="";async function Ci(e,t){const s=function(){const e=ni(Ti,Si);return ti.create({prefixUrl:`${bi}/api`,timeout:!1,headers:{Authorization:e},hooks:{afterResponse:[async(e,t,s)=>{const i=await s.clone().json();if(0!==i.code)throw mi(i.msg),new Error(JSON.stringify(i)||"请求失败");return s.json=async()=>i,s}]}})}(),i=e.replace(/^\/+/,"");return s.post(i,{json:t}).json()}const Ei=new class extends Qe{updateRttObject(e){this.setState({rttObject:e})}}({rttObject:{rtt:null,jitter:null,packetsLost:null,packetsReceived:null,packetsSent:null,sendBitrate:null,recvBitrate:null,codec:null}});class Ai{constructor(e,t){this.config=e,this.eventCallback=t,this.userAgent=null,this.registerer=null,this.activeSession=null,this.callTimeoutTimer=null,this.incomingInvitation=null,this.reconnectAttempts=0,this.maxReconnectAttempts=5,this.reconnectTimer=null,this.isOffline=!1,this.autoAnswerTimer=null,this.handleMessage=e=>{},this.handleNetworkInfoChange=e=>{if(e.rttObject?.rtt){0===(e.rttObject?.rtt||0)?this.isOffline||(this.isOffline=!0,this.handleOffline()):this.isOffline&&(this.isOffline=!1,this.handleOnline())}},this.handleOffline=()=>{console.warn("[SIPClient] 网络断开,准备销毁 SIP 客户端",(new Date).toLocaleString()),this.destroy()},this.handleOnline=()=>{console.warn("[SIPClient] 网络恢复,准备重新启动 SIP 客户端",(new Date).toLocaleString()),this.reconnect()},Ei.subscribe(this.handleNetworkInfoChange)}async start(){const e=Xe.makeURI(`sip:${this.config.user}@${this.config.server}`);if(!e)throw new Error("无效的SIP配置");const t={uri:e,authorizationUsername:this.config.user,authorizationPassword:this.config.password,transportOptions:{server:this.config.webSocket,connectionTimeout:30,keepAliveInterval:0},sessionDescriptionHandlerFactoryOptions:{alwaysAcquireMediaFirst:!0,peerConnectionConfiguration:{iceServers:[]},iceGatheringTimeout:400},logBuiltinEnabled:!1};this.userAgent=new Xe(t),this.setupEventListeners(),await this.userAgent.start(),await this.register()}setupEventListeners(){this.userAgent&&(this.userAgent.transport.stateChange.addListener(e=>{let t;switch(e){case ee.Connecting:t=st.WEB_RTC_CONNECTING;break;case ee.Connected:t=st.WEB_RTC_CONNECTED,this.reconnectAttempts=0,_s.isRtcReconnecting=!1,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null);break;case ee.Disconnecting:t=st.WEB_RTC_DISCONNECTING;break;case ee.Disconnected:{t=st.WEB_RTC_DISCONNECTED;const e=Rs.get("answerDevice");if(!_s.isRtcReconnecting&&this.reconnectAttempts<this.maxReconnectAttempts&&1===e){this.reconnectAttempts++;const e=1e3*this.reconnectAttempts;console.warn(`SIP WebSocket 断开,第 ${this.reconnectAttempts} 次尝试重连,${e}ms 后重试...`),this.reconnectTimer=setTimeout(()=>{this.reconnect()},e)}else this.reconnectAttempts>=this.maxReconnectAttempts&&console.error("SIP 重连失败:已达到最大重试次数");break}default:t="unknown"}this.eventCallback?.({type:t})}),this.userAgent.transport.stateChange.addListener(e=>{}),this.userAgent.delegate={onInvite:e=>{this.handleIncomingCall(e)}})}async register(){if(this.userAgent){this.registerer=new ne(this.userAgent),this.registerer?.stateChange.addListener(e=>{let t;switch(e){case X.Initial:t="initial";break;case X.Registered:t=st.WEB_RTC_REGISTERED;break;case X.Unregistered:t=st.WEB_RTC_UNREGISTERED;break;case X.Terminated:t=st.WEB_RTC_TERMINATED;break;default:t="unknown"}this.eventCallback?.({type:t})});try{await this.registerer.register()}catch(e){this.eventCallback?.({type:st.WEB_RTC_REGISTER_FAILED,data:e})}}}attachRemoteAudio(e){const t=e.sessionDescriptionHandler;if(t){const e=t.peerConnection;if(!e)return;const s=new MediaStream;e.getReceivers().forEach(e=>{e.track&&"audio"===e.track.kind&&s.addTrack(e.track)});let i=document.getElementById("sip-remote-audio");i||(i=document.createElement("audio"),i.id="sip-remote-audio",i.autoplay=!0,i.style.display="none",document.body.appendChild(i)),i.srcObject=s,i.play().catch(e=>{console.error("音频播放失败,需要用户交互触发",e)})}}handleSessionState(e,t,s){e.stateChange.addListener(t=>{switch(t){case K.Established:clearTimeout(this.callTimeoutTimer),this.activeSession=e,this.attachRemoteAudio(e);break;case K.Terminating:break;case K.Terminated:this.activeSession=null,clearTimeout(this.callTimeoutTimer);case K.Establishing:}})}handleIncomingCall(e){this.incomingInvitation=e,this.handleSessionState(e,!1);const{soft_device_auto_answer:t,auto_answer_time:s}=_s.agentInfo,i=Rs.get("direction"),r=1===t;if(i===tt.OUTGOING)this.answerCall();else if(r&&i===tt.INCOMING){const e=Number(s)||0;e>0?this.autoAnswerTimer=setTimeout(()=>{this.answerCall(),this.autoAnswerTimer=null},e):this.answerCall()}}async makeCall(e){if(Rs.getState().isCalling)throw new Error("正在呼叫中,请勿重复点击");if(!this.userAgent)throw Rs.updateIsCalling(!1),Rs.updateDirection(null),new Error("SIP客户端未初始化");Rs.updateIsCalling(!0),Rs.updateDirection(tt.OUTGOING);const t=Xe.makeURI(`sip:${e}@${this.config.server}`);if(!t)throw new Error("无效的被叫号码");const s=new Z(this.userAgent,t);this.handleSessionState(s,!0,e);try{this.activeSession=s,await s.invite(),this.config.callTimeout&&(this.callTimeoutTimer=setTimeout(()=>{s.state!==K.Established&&(this.hangup(),this.eventCallback?.({type:"error",data:{message:"呼叫超时未接通"}}))},this.config.callTimeout))}catch(e){this.eventCallback?.({type:"error",data:{message:"呼叫失败",detail:e}}),Rs.updateIsCalling(!1),Rs.updateDirection(null)}}async answerCall(){if(!this.incomingInvitation)throw new Error("无来电可接听");this.autoAnswerTimer&&(clearTimeout(this.autoAnswerTimer),this.autoAnswerTimer=null);try{console.log((new Date).toLocaleString()),await this.incomingInvitation.accept(),console.log((new Date).toLocaleString()),this.activeSession=this.incomingInvitation,this.incomingInvitation=null}catch(e){this.eventCallback?.({type:st.WEB_RTC_ANSWER_FAILED,data:{detail:e}})}}async rejectInCall(){if(this.incomingInvitation)try{await this.incomingInvitation.reject(),this.incomingInvitation=null,Rs.updateDirection(null),Rs.updateIsCalling(!1),clearTimeout(this.callTimeoutTimer)}catch(e){this.eventCallback?.({type:"error",data:{message:"拒接失败",detail:e}})}}sendDTMF(e){if(!this.activeSession)throw new Error("当前没有活跃的通话");const t=this.activeSession.sessionDescriptionHandler;t&&"function"==typeof t.sendDtmf?(t.sendDtmf(e),this.eventCallback?.({type:st.WEB_RTC_SEND_DTMF,data:{tone:e}})):console.warn("DTMF发送不支持或未初始化")}async hangup(){if(this.activeSession){clearTimeout(this.callTimeoutTimer);const e=this.activeSession;if(!e)return;const t=e.state;try{t===K.Established?await e.bye():t===K.Establishing&&(e instanceof Z?await e.cancel():e instanceof J&&await e.reject()),clearTimeout(this.callTimeoutTimer)}catch(e){console.error("挂断失败",e)}finally{this.activeSession=null}}}async rejectOutCall(){this.activeSession instanceof J&&(await this.activeSession.reject(),this.activeSession=null)}async destroy(){if(clearTimeout(this.callTimeoutTimer),this.activeSession&&await this.hangup(),this.registerer){try{await this.registerer.unregister()}catch(e){console.warn("注销失败",e)}this.registerer=null}this.userAgent&&(await this.userAgent.stop(),this.userAgent=null)}async reconnect(){if(!_s.isRtcReconnecting){_s.isRtcReconnecting=!0;try{await this.destroy(),await this.start(),console.log("重连成功"),this.isOffline=!1}catch(e){console.error("重连失败",e)}finally{_s.isRtcReconnecting=!1}}}async getNetworkStats(){if(this.activeSession){const e=this.activeSession.sessionDescriptionHandler?.peerConnection;if(!e)return null;const t=await e.getStats(),s={};return t.forEach(e=>{"candidate-pair"===e.type&&"succeeded"===e.state&&null!=e.currentRoundTripTime&&(s.rtt=+(1e3*e.currentRoundTripTime).toFixed(2)),"inbound-rtp"===e.type&&"audio"===e.kind&&(s.jitter=+(1e3*e.jitter).toFixed(2),s.packetsLost=e.packetsLost,s.packetsReceived=e.packetsReceived,e.bytesReceived&&e.timestamp&&(s.recvBitrate=+(e.bytesReceived/1024).toFixed(2))),"outbound-rtp"===e.type&&"audio"===e.kind&&(s.packetsSent=e.packetsSent,e.bytesSent&&e.timestamp&&(s.sendBitrate=+(e.bytesSent/1024).toFixed(2))),"codec"===e.type&&e.mimeType&&(s.codec=e.mimeType)}),{...s,rtt:s?.rtt||40}}try{const e=Date.now();await Ci(`${wi}/config/ping`,{});const t=Date.now();return{rtt:t-e||1}}catch(e){console.log(e)}}}let _i=class extends fs{static{this.styles=pt`
117
+ `}connectedCallback(){super.connectedCallback(),setTimeout(()=>{this.remove()},this.duration+300)}render(){return Jt`<div>${this.content}</div>`}};oi([di({type:String})],ui.prototype,"type",void 0),oi([di({type:String})],ui.prototype,"content",void 0),oi([di({type:Number})],ui.prototype,"duration",void 0),ui=oi([ai("my-message")],ui);const pi=(e,t,s=3e3)=>{let i=document.querySelector("#message-container");i||(i=document.createElement("div"),i.id="message-container",Object.assign(i.style,{position:"fixed",top:"14px",left:"50%",transform:"translateX(-50%)",zIndex:"9999"}),document.body.appendChild(i));const r=document.createElement("my-message");r.setAttribute("type",e),r.setAttribute("content",t),r.setAttribute("duration",String(s)),r.style.setProperty("--fade-delay",s/1e3+"s"),i.appendChild(r)},fi=(e,t=3e3)=>pi("success",e,t),mi=(e,t=3e3)=>pi("error",e,t),vi=(e,t=3e3)=>pi("warning",e,t),wi="/v1/aicc/bmserver",yi="/v1/aicc/ccs";let bi="",Ti="",Si="";async function Ci(e,t){const s=function(){const e=ni(Ti,Si);return ti.create({prefixUrl:`${bi}/api`,timeout:!1,headers:{Authorization:e},hooks:{afterResponse:[async(e,t,s)=>{const i=await s.clone().json();if(0!==i.code)throw mi(i.msg),new Error(JSON.stringify(i)||"请求失败");return s.json=async()=>i,s}]}})}(),i=e.replace(/^\/+/,"");return s.post(i,{json:t}).json()}const Ei=new class extends Qe{updateRttObject(e){this.setState({rttObject:e})}}({rttObject:{rtt:null,jitter:null,packetsLost:null,packetsReceived:null,packetsSent:null,sendBitrate:null,recvBitrate:null,codec:null}});class Ai{constructor(e,t){this.config=e,this.eventCallback=t,this.userAgent=null,this.registerer=null,this.activeSession=null,this.callTimeoutTimer=null,this.incomingInvitation=null,this.reconnectAttempts=0,this.maxReconnectAttempts=5,this.reconnectTimer=null,this.isOffline=!1,this.autoAnswerTimer=null,this.isManuallyStopped=!1,this.handleMessage=e=>{},this.handleNetworkInfoChange=e=>{if(e.rttObject?.rtt){0===(e.rttObject?.rtt||0)?this.isOffline||(this.isOffline=!0,this.handleOffline()):this.isOffline&&(this.isOffline=!1,this.handleOnline())}},this.handleOffline=()=>{console.warn("[SIPClient] 网络断开,准备销毁 SIP 客户端",(new Date).toLocaleString()),this.destroy()},this.handleOnline=()=>{console.warn("[SIPClient] 网络恢复,准备重新启动 SIP 客户端",(new Date).toLocaleString()),this.reconnect()},Ei.subscribe(this.handleNetworkInfoChange)}async start(){const e=Xe.makeURI(`sip:${this.config.user}@${this.config.server}`);if(!e)throw new Error("无效的SIP配置");const t={uri:e,authorizationUsername:this.config.user,authorizationPassword:this.config.password,transportOptions:{server:this.config.webSocket,connectionTimeout:30,keepAliveInterval:0},sessionDescriptionHandlerFactoryOptions:{alwaysAcquireMediaFirst:!0,peerConnectionConfiguration:{iceServers:[]},iceGatheringTimeout:400},logBuiltinEnabled:!1};this.userAgent=new Xe(t),this.setupEventListeners(),await this.userAgent.start(),await this.register()}setupEventListeners(){this.userAgent&&(this.userAgent.transport.stateChange.addListener(e=>{let t;switch(e){case ee.Connecting:t=st.WEB_RTC_CONNECTING;break;case ee.Connected:t=st.WEB_RTC_CONNECTED,this.reconnectAttempts=0,_s.isRtcReconnecting=!1,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null);break;case ee.Disconnecting:t=st.WEB_RTC_DISCONNECTING;break;case ee.Disconnected:{if(this.isManuallyStopped)return void console.warn("[SIP] 手动断开,不触发重连");t=st.WEB_RTC_DISCONNECTED;const e=Rs.get("answerDevice");if(!_s.isRtcReconnecting&&this.reconnectAttempts<this.maxReconnectAttempts&&1===e){this.reconnectAttempts++;const e=1e3*this.reconnectAttempts;console.warn(`SIP WebSocket 断开,第 ${this.reconnectAttempts} 次尝试重连,${e}ms 后重试...`),this.reconnectTimer=setTimeout(()=>{this.reconnect()},e)}else this.reconnectAttempts>=this.maxReconnectAttempts&&console.error("SIP 重连失败:已达到最大重试次数");break}default:t="unknown"}this.eventCallback?.({type:t})}),this.userAgent.transport.stateChange.addListener(e=>{}),this.userAgent.delegate={onInvite:e=>{this.handleIncomingCall(e)}})}async register(){if(this.userAgent){this.registerer=new ne(this.userAgent),this.registerer?.stateChange.addListener(e=>{let t;switch(e){case X.Initial:t="initial";break;case X.Registered:t=st.WEB_RTC_REGISTERED;break;case X.Unregistered:t=st.WEB_RTC_UNREGISTERED;break;case X.Terminated:t=st.WEB_RTC_TERMINATED;break;default:t="unknown"}this.eventCallback?.({type:t})});try{await this.registerer.register()}catch(e){this.eventCallback?.({type:st.WEB_RTC_REGISTER_FAILED,data:e})}}}attachRemoteAudio(e){const t=e.sessionDescriptionHandler;if(t){const e=t.peerConnection;if(!e)return;const s=new MediaStream;e.getReceivers().forEach(e=>{e.track&&"audio"===e.track.kind&&s.addTrack(e.track)});let i=document.getElementById("sip-remote-audio");i||(i=document.createElement("audio"),i.id="sip-remote-audio",i.autoplay=!0,i.style.display="none",document.body.appendChild(i)),i.srcObject=s,i.play().catch(e=>{console.error("音频播放失败,需要用户交互触发",e)})}}handleSessionState(e,t,s){e.stateChange.addListener(t=>{switch(t){case K.Established:clearTimeout(this.callTimeoutTimer),this.activeSession=e,this.attachRemoteAudio(e);break;case K.Terminating:break;case K.Terminated:this.activeSession=null,clearTimeout(this.callTimeoutTimer);case K.Establishing:}})}handleIncomingCall(e){this.incomingInvitation=e,this.handleSessionState(e,!1);const{soft_device_auto_answer:t,auto_answer_time:s}=_s.agentInfo,i=Rs.get("direction"),r=1===t;if(i===tt.OUTGOING)this.answerCall();else if(r&&i===tt.INCOMING){const e=Number(s)||0;e>0?this.autoAnswerTimer=setTimeout(()=>{this.answerCall(),this.autoAnswerTimer=null},e):this.answerCall()}}async makeCall(e){if(Rs.getState().isCalling)throw new Error("正在呼叫中,请勿重复点击");if(!this.userAgent)throw Rs.updateIsCalling(!1),Rs.updateDirection(null),new Error("SIP客户端未初始化");Rs.updateIsCalling(!0),Rs.updateDirection(tt.OUTGOING);const t=Xe.makeURI(`sip:${e}@${this.config.server}`);if(!t)throw new Error("无效的被叫号码");const s=new Z(this.userAgent,t);this.handleSessionState(s,!0,e);try{this.activeSession=s,await s.invite(),this.config.callTimeout&&(this.callTimeoutTimer=setTimeout(()=>{s.state!==K.Established&&(this.hangup(),this.eventCallback?.({type:"error",data:{message:"呼叫超时未接通"}}))},this.config.callTimeout))}catch(e){this.eventCallback?.({type:"error",data:{message:"呼叫失败",detail:e}}),Rs.updateIsCalling(!1),Rs.updateDirection(null)}}async answerCall(){if(!this.incomingInvitation)throw new Error("无来电可接听");this.autoAnswerTimer&&(clearTimeout(this.autoAnswerTimer),this.autoAnswerTimer=null);try{console.log((new Date).toLocaleString()),await this.incomingInvitation.accept(),console.log((new Date).toLocaleString()),this.activeSession=this.incomingInvitation,this.incomingInvitation=null}catch(e){this.eventCallback?.({type:st.WEB_RTC_ANSWER_FAILED,data:{detail:e}})}}async rejectInCall(){if(this.incomingInvitation)try{await this.incomingInvitation.reject(),this.incomingInvitation=null,Rs.updateDirection(null),Rs.updateIsCalling(!1),clearTimeout(this.callTimeoutTimer)}catch(e){this.eventCallback?.({type:"error",data:{message:"拒接失败",detail:e}})}}sendDTMF(e){if(!this.activeSession)throw new Error("当前没有活跃的通话");const t=this.activeSession.sessionDescriptionHandler;t&&"function"==typeof t.sendDtmf?(t.sendDtmf(e),this.eventCallback?.({type:st.WEB_RTC_SEND_DTMF,data:{tone:e}})):console.warn("DTMF发送不支持或未初始化")}async hangup(){if(this.activeSession){clearTimeout(this.callTimeoutTimer);const e=this.activeSession;if(!e)return;const t=e.state;try{t===K.Established?await e.bye():t===K.Establishing&&(e instanceof Z?await e.cancel():e instanceof J&&await e.reject()),clearTimeout(this.callTimeoutTimer)}catch(e){console.error("挂断失败",e)}finally{this.activeSession=null}}}async rejectOutCall(){this.activeSession instanceof J&&(await this.activeSession.reject(),this.activeSession=null)}async destroy(){if(this.isManuallyStopped=!0,clearTimeout(this.callTimeoutTimer),this.activeSession&&await this.hangup(),this.registerer){try{await(this.userAgent?.stop()),await this.registerer.unregister()}catch(e){console.warn("注销失败",e)}this.registerer=null}this.userAgent&&(await this.userAgent.stop(),this.userAgent=null)}async reconnect(){if(!_s.isRtcReconnecting){_s.isRtcReconnecting=!0;try{await this.destroy(),await this.start(),console.log("重连成功"),this.isOffline=!1}catch(e){console.error("重连失败",e)}finally{_s.isRtcReconnecting=!1}}}async getNetworkStats(){if(this.activeSession){const e=this.activeSession.sessionDescriptionHandler?.peerConnection;if(!e)return null;const t=await e.getStats(),s={};return t.forEach(e=>{"candidate-pair"===e.type&&"succeeded"===e.state&&null!=e.currentRoundTripTime&&(s.rtt=+(1e3*e.currentRoundTripTime).toFixed(2)),"inbound-rtp"===e.type&&"audio"===e.kind&&(s.jitter=+(1e3*e.jitter).toFixed(2),s.packetsLost=e.packetsLost,s.packetsReceived=e.packetsReceived,e.bytesReceived&&e.timestamp&&(s.recvBitrate=+(e.bytesReceived/1024).toFixed(2))),"outbound-rtp"===e.type&&"audio"===e.kind&&(s.packetsSent=e.packetsSent,e.bytesSent&&e.timestamp&&(s.sendBitrate=+(e.bytesSent/1024).toFixed(2))),"codec"===e.type&&e.mimeType&&(s.codec=e.mimeType)}),{...s,rtt:s?.rtt||40}}try{const e=Date.now();await Ci(`${wi}/config/ping`,{});const t=Date.now();return{rtt:t-e||1}}catch(e){console.log(e)}}}let _i=class extends fs{static{this.styles=pt`
118
118
  .timer-text {
119
119
  width: 70px;
120
120
  font-size: 14px;
@@ -808,4 +808,4 @@ const ai=e=>(t,s)=>{void 0!==s?s.addInitializer(()=>{customElements.define(e,t)}
808
808
  `:""}
809
809
  </div>
810
810
  </my-popover>
811
- `;us(i,this.rttHTML)}}const Oi=new class{constructor(){this.events={}}on(e,t){this.events[e]=this.events[e]||[],this.events[e].push(t)}off(e,t){this.events[e]=(this.events[e]||[]).filter(e=>e!==t)}emit(e,...t){(this.events[e]||[]).forEach(e=>e(...t))}};class qi{static requestPermission(){"Notification"in window&&"default"===Notification.permission&&Notification.requestPermission()}static async requestMediaPermissions(){try{const e=await navigator.mediaDevices.enumerateDevices();if(e.some(e=>"audioinput"===e.kind&&""!==e.label))return console.log("已授权麦克风权限,无需再次获取"),null;const t=await navigator.mediaDevices.getUserMedia({audio:!0});return console.log("获取音频权限成功"),t}catch(e){return console.warn("获取音频权限失败:",e),null}}static show(e,t,s){if("Notification"in window&&"granted"===Notification.permission){const i=new Notification(e,{body:t,tag:"xy-global-notification",renotify:!0});i.onclick=()=>{window.focus(),i.close(),s?.()},setTimeout(()=>i.close(),5e3)}}static async checkMediaPermissions(){try{return(await navigator.mediaDevices.enumerateDevices()).some(e=>"audioinput"===e.kind&&""!==e.label)}catch(e){return console.warn("无法检查设备权限:",e),!1}}}let Li=null;class Ui{constructor(e){this.url=e,this.ws=null,this.heartbeatWorker=null,this.reconnectDelay=3e3,this.manualClose=!1,this.apiClient=new Hi,this.initWebSocket()}static getInstance(e){if(!Li){if(!e)throw new Error("WebSocket 尚未初始化");Li=new Ui(e)}return Li}stateIdleChange(){const{state:e}=_s.stateObject;e!==et.IDLE&&this.apiClient.changeState({data:{state:et.IDLE,state_name:"空闲"}})}putAgentState(){_s.sessionId="";const{post_call_process_time:e}=_s.agentInfo,t=e||0;_s.autoStateTimer&&(clearTimeout(_s.autoStateTimer),_s.autoStateTimer=null),t>0?_s.autoStateTimer=setTimeout(()=>{_s.autoStateTimer=null,this.stateIdleChange()},t):this.stateIdleChange()}initWebSocket(){this.ws=new WebSocket(this.url),this.ws.onopen=()=>{this.sendMessage({type:0}),this.startHeartbeat()},this.ws.onmessage=e=>{const t=JSON.parse(e.data),{type:s,data:i,code:r,msg:n}=t;if(0!==s&&console.warn(s),s===rt.OUT_CALL&&0!==r&&(ct.emit(it.OUT_FAILED,n),this.stateIdleChange()),s===rt.AGENT_STATE){const{state:e,state_name:t}=i;_s.stateObject={state:e,state_name:t},"振铃中"===t&&Rs.updateDisplayText(ot.AGENT_RINGING),ct.emit(it.AGENT_STATE,{state:e,state_name:t})}s===rt.OUT_CALL_INCOMING_CALL&&(ct.emit(it.OUT_INCOMING_CALL,i),_s.sessionId=i.session_id,Rs.updateActionConfigs(bs)),s===rt.OUT_CALL_END&&(1===i.device_type?(this.putAgentState(),Rs.reset(),ct.emit(it.OUT_HANGUP,i)):2===i.device_type&&(Rs.get("direction")===tt.CONSULT?(Rs.reset(),ct.emit(it.OUR_SIDE_CONSULT_HANGUP,i),this.putAgentState()):Rs.get("isCalling")&&(Rs.updateDisplayText(ot.CALLING),ct.emit(it.OTHER_SIDE_CONSULT_HANGUP,i),Rs.updateActionConfigs(Ts)))),s===rt.OUT_CALL_RINGING&&(ct.emit(it.OUT_RINGING,i),Rs.updateDisplayText(ot.RINGING)),s===rt.OUT_CALL_ANSWER&&(ct.emit(it.OUT_OFF_ANSWER,i),Rs.updateOutCallIsAnswer(!0),Rs.updateDisplayText(ot.CALLING),Rs.updateActionConfigs(Ts)),s===rt.CONSULT_RINGING&&(ct.emit(it.CONSULT_RINGING,i),Rs.updateDisplayText(ot.AGENT_RINGING),Rs.updateActionConfigs([])),s===rt.CONSULT_ANSWER&&(ct.emit(it.CONSULT_OFF_HOOK,i),Rs.updateConsultIsAnswer(!0),Rs.updateDisplayText(ot.CONSULTING),Rs.updateActionConfigs(Ss)),s===rt.CONSULT_FAIL&&(ct.emit(it.CONSULT_FAILED),Rs.updateDisplayText(ot.CALLING),Rs.updateActionConfigs(Ts)),s===rt.CONSULT_CALL_IN&&(ct.emit(it.CONSULT_INCOMING,i),Rs.updateIsCalling(!0),Rs.updateDirection(tt.CONSULT),Rs.updateDisplayText(ot.CONSULT_CALL_IN),Rs.updateActionConfigs(Es),_s.enableBrowserAlert&&qi.show("📞 咨询来电",`坐席 [${i.agent_name}] 正在咨询...`,()=>{ct.emit(nt.NOTIFICATION_CLICK,{agent_mane:i.agent_name})})),s===rt.CONSULT_CALL_IN_SUCCESS&&(_s.sessionId=i.session_id,ct.emit(it.CONSULT_CALL_IN_SUCCESS,i),Rs.updateConsultIsAnswer(!0),Rs.updateDisplayText(ot.CALLING),Rs.updateActionConfigs(Cs)),s===rt.MUTE&&(0===r?(Rs.updateIsMuted(!0),ct.emit(it.MUTE_SUCCESS)):ct.emit(it.MUTE_FAILED,n)),s===rt.UNMUTE&&(0===r?(Rs.updateIsMuted(!1),ct.emit(it.UNMUTE_SUCCESS)):ct.emit(it.UNMUTE_FAILED,n)),s===rt.CONSULT_TRANSFER&&(0===r?ct.emit(it.CONSULT_TRANSFER_SUCCESS):ct.emit(it.CONSULT_TRANSFER_FAILED,n)),s===rt.CONSULT_TRANSFER_FAILED&&ct.emit(it.CONSULT_TRANSFER_FAILED,i.status),s===rt.CONSULT_TRANSFER_OFF_HOOK&&(Rs.updateDirection(tt.INCOMING),Rs.updateDisplayText(ot.CALLING),ct.emit(it.CONSULT_TRANSFER_OFF_HOOK),Rs.updateActionConfigs(As),Rs.updateIncomingIsAnswer(!0),Rs.updateConsultIsAnswer(!1)),s===rt.INCOMING_CALL&&(_s.sessionId=i.session_id,Rs.updateIsCalling(!0),Rs.updateDirection(tt.INCOMING),Rs.updateDisplayText(ot.INCOMING_CALL),ct.emit(it.INCOMING_CALL,{customer_phone:i.customer_phone}),Rs.updateActionConfigs(Es),_s.enableBrowserAlert&&qi.show("📞 客户来电",`客户 [${i.customer_phone}] 正在呼入...`,()=>{ct.emit(nt.NOTIFICATION_CLICK,{customer_phone:i.customer_phone})})),s===rt.INCOMING_CALL_OFF_HOOK&&(Rs.updateIncomingIsAnswer(!0),ct.emit(it.INCOMING_CALL_OFF_HOOK),Rs.updateActionConfigs(As),Rs.updateDisplayText(ot.CALLING)),s===rt.INCOMING_CALL_END&&1===i.device_type&&(ct.emit(it.INCOMING_CALL_END),Rs.reset(),Rs.updateActionConfigs(ws),this.putAgentState()),0!==s&&Oi.emit("ws-message",t)},this.ws.onclose=()=>{this.stopHeartbeat(),this.manualClose||setTimeout(()=>this.initWebSocket(),this.reconnectDelay)},this.ws.onerror=()=>{this.ws?.close()}}startHeartbeat(){const e=new Blob(["\n let timer;\n self.onmessage = function (e) {\n if (e.data === 'start') {\n timer = setInterval(() => {\n self.postMessage('heartbeat');\n }, 14000);\n } else if (e.data === 'stop') {\n clearInterval(timer);\n }\n };\n "],{type:"application/javascript"}),t=URL.createObjectURL(e);this.heartbeatWorker=new Worker(t),this.heartbeatWorker.onmessage=()=>{this.sendMessage({type:0})},this.heartbeatWorker.postMessage("start")}stopHeartbeat(){this.heartbeatWorker&&(this.heartbeatWorker.postMessage("stop"),this.heartbeatWorker.terminate(),this.heartbeatWorker=null)}sendMessage(e){this.ws?.readyState===WebSocket.OPEN?this.ws.send(JSON.stringify(e)):console.warn("WebSocket 未连接,消息未发送")}close(){this.manualClose=!0,this.stopHeartbeat(),this.ws?.close()}static __internalClose(){Li&&(Li.close(),Li=null)}static __internalSend(e){if(!Li)throw new Error("WebSocket 尚未初始化");Li.sendMessage(e)}}module.exports=class{#e=null;#t=new Hi;#s=null;#i=!1;#r;constructor(){clearInterval(this.#s),this.#s=setInterval(async()=>{const e=await this.#n();Ei.updateRttObject(e)},2e3),qi.requestMediaPermissions(),window.addEventListener("beforeunload",()=>{this.destroy()}),this.#r=Rs.subscribeKey("answerDevice",e=>{1===e&&this.initWebRtc()})}async initWebRtc(){const e=_s.agentInfo.sip_account_list.find(e=>1===e.device_type);if(e){const t=e.sip_register_addr;this.#e=new Ai({server:t.split(":")[0],user:e.sip_account,password:e.sip_password,webSocket:`wss://${t}`,callTimeout:3e4},this.#o),await this.#e.start()}}async init({actionNodeParams:e,url:t,appKey:s,appSecret:i,agentNo:r,enableBrowserAlert:n}){try{const{viewHtmlElement:o,rttHTML:a}=e;bi=t||"https://aicc-api.yescloudy.com",Ti=s,Si=i;const c=ni(s,i);await this.#t._agentLogin(r);const{agent_no:h,current_answer_device:d,init_state:l}=_s.agentInfo;l===et.IDLE?(Rs.updateActionConfigs(ws),_s.stateObject={state:l,state_name:"空闲"}):l===et.BUSY&&(Rs.updateActionConfigs(ys),_s.stateObject={state:l,state_name:"忙碌"}),Ui.getInstance(`ws://112.90.183.54:8719/api/v1/ws/call/${h}?Authorization=${c}`),Rs.updateAnswerDevice(d),1===d&&await this.initWebRtc(),e&&o&&(this.#i=!0,new Pi({container:o,rttHTML:a,statusParams:{state:et.IDLE,statusName:"空闲"}},this)),_s.enableBrowserAlert=n,n&&qi.requestPermission()}catch(e){this.#i&&mi("初始化失败"),console.log(e)}}#o=e=>{const{type:t,data:s}=e;ct.emit(t,s)};#a=e=>{};on(e,t){return ct.on(e,t),this}off(e,t){ct.off(e,t)}offAll(){ct.clearAllListeners()}#c(){if(!this.#e)throw new Error("请先初始化SDK");const e=Rs.get("isCalling"),t=_s.sessionId;if(!e)throw new Error("当前没有通话");if(!t)throw new Error("sessionId不存在")}#h(){const e=Rs.get("isHold"),{sessionId:t}=_s;if(t){if(e)throw new Error("当前已处于保持状态");Ui.__internalSend({type:rt.CALL_HOLD,data:{session_id:t}}),Rs.updateDisplayText(ot.HOLD),Rs.updateIsHold(!0)}}#d(){const{isHold:e}=Rs.getState(),{sessionId:t}=_s;if(t){if(!e)throw new Error("当前未处于保持状态");Ui.__internalSend({type:rt.CALL_UNHOLD,data:{session_id:t}}),Rs.updateDisplayText(""),Rs.updateIsHold(!1)}}#l(e){if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:t}=Rs.getState();if(!t)throw new Error("当前没有通话");this.#e.sendDTMF(e)}#g(){if(1!==Rs.get("answerDevice"))throw new Error("当前设备不支持此功能");if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:e,direction:t,outCallIsAnswer:s}=Rs.getState();if(!e)throw new Error("当前没有通话");s||(t===tt.INCOMING?this.#u():t===tt.OUTGOING&&this.#p())}async#f(e){if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:t,outCallIsAnswer:s,consultIsAnswer:i,incomingIsAnswer:r}=Rs.getState(),n=_s.sessionId;if(i)throw new Error("当前正在咨询");if(!(t||s||r))throw new Error("当前没有通话");if(!n)throw new Error("sessionId不存在");Ui.__internalSend({type:rt.CONSULT_REQUEST,data:{session_id:n,consultation_agent_no:e}})}async#m(){if(1!==Rs.get("answerDevice"))throw new Error("当前设备不支持此功能");if(!this.#e)throw new Error("请先初始化SDK");await this.#e.answerCall()}async#u(){if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:e}=Rs.getState();if(!e)throw new Error("当前没有通话");await this.#e.rejectInCall()}async#v(e){if(!this.#e)throw new Error("请先初始化SDK");if(Rs.getState().isCalling)throw new Error("正在呼叫中,请勿重复点击");Rs.updateIsCalling(!0),Rs.updateDirection(tt.OUTGOING),Ui.__internalSend({type:rt.OUT_CALL,data:{customer_phone:e}})}async#p(){const{isCalling:e}=Rs.getState();if(!this.#e)throw new Error("请先初始化SDK");if(!e)throw new Error("当前没有通话");await this.#e.rejectOutCall()}async#w(){if(!this.#e)throw new Error("请先初始化SDK");if(!Rs.get("isCalling"))throw new Error("当前没有通话");await this.#e.hangup()}#y(){this.#c();const e=Rs.get("actionConfigs"),t=Rs.get("isMuted"),s=_s.sessionId;if(t)throw new Error("当前未处于静音状态");if(!e.includes("mute"))throw new Error("当前状态请勿调用静音");Ui.__internalSend({type:rt.MUTE,data:{session_id:s}})}#b(){this.#c();const e=Rs.get("actionConfigs"),t=Rs.get("isMuted"),s=_s.sessionId;if(!t)throw new Error("当前未处于未静音状态");if(!e.includes("unmute"))throw new Error("当前状态请勿调用静音");Ui.__internalSend({type:rt.UNMUTE,data:{session_id:s}})}#T(){this.#c();const e=Rs.get("consultIsAnswer"),t=_s.sessionId;if(!Rs.get("actionConfigs").includes("consult_transfer"))throw new Error("当前状态请勿调用咨询转移");if(!e)throw new Error("当前未处于咨询中");Ui.__internalSend({type:rt.CONSULT_TRANSFER,data:{session_id:t}})}async destroyRtc(){await(this.#e?.destroy())}destroy(){this.destroyRtc(),clearInterval(this.#s),Oi.off("ws-message",this.#a),Ui.__internalClose(),this.offAll(),this.#r?.()}async#n(){return this.#e?this.#e.getNetworkStats():{}}get call_api(){return this.#i?{answerCall:this.#m.bind(this),makeCall:this.#v.bind(this),cancelCall:this.#g.bind(this),hangup:this.#w.bind(this),sendDTMF:this.#l.bind(this),holdCall:this.#h.bind(this),unholdCall:this.#d.bind(this),consultCall:async e=>{try{await this.#f(e)}catch(e){throw console.warn(e),e}},consultTransfer:this.#T.bind(this),mute:this.#y.bind(this),unmute:this.#b.bind(this)}:{}}get agent_api(){return this.#i?{changeDevice:this.#t.changeDevice.bind(this.#t),changeState:this.#t.changeState.bind(this.#t),getIdleAgentList:e=>this.#t.getIdleAgentList(e),getAgentState:e=>this.#t.getAgentState(e)}:{}}};
811
+ `;us(i,this.rttHTML)}}const Oi=new class{constructor(){this.events={}}on(e,t){this.events[e]=this.events[e]||[],this.events[e].push(t)}off(e,t){this.events[e]=(this.events[e]||[]).filter(e=>e!==t)}emit(e,...t){(this.events[e]||[]).forEach(e=>e(...t))}};class qi{static requestPermission(){"Notification"in window&&"default"===Notification.permission&&Notification.requestPermission()}static async requestMediaPermissions(){try{const e=await navigator.mediaDevices.enumerateDevices();if(e.some(e=>"audioinput"===e.kind&&""!==e.label))return console.log("已授权麦克风权限,无需再次获取"),null;const t=await navigator.mediaDevices.getUserMedia({audio:!0});return console.log("获取音频权限成功"),t}catch(e){return console.warn("获取音频权限失败:",e),null}}static show(e,t,s){if("Notification"in window&&"granted"===Notification.permission){const i=new Notification(e,{body:t,tag:"xy-global-notification",renotify:!0});i.onclick=()=>{window.focus(),i.close(),s?.()},setTimeout(()=>i.close(),5e3)}}static async checkMediaPermissions(){try{return(await navigator.mediaDevices.enumerateDevices()).some(e=>"audioinput"===e.kind&&""!==e.label)}catch(e){return console.warn("无法检查设备权限:",e),!1}}}let Li=null;class Ui{constructor(e){this.url=e,this.ws=null,this.heartbeatWorker=null,this.reconnectDelay=3e3,this.manualClose=!1,this.apiClient=new Hi,this.initWebSocket()}static getInstance(e){if(!Li){if(!e)throw new Error("WebSocket 尚未初始化");Li=new Ui(e)}return Li}stateIdleChange(){const{state:e}=_s.stateObject;e!==et.IDLE&&this.apiClient.changeState({data:{state:et.IDLE,state_name:"空闲"}})}putAgentState(){_s.sessionId="";const{post_call_process_time:e}=_s.agentInfo,t=e||0;_s.autoStateTimer&&(clearTimeout(_s.autoStateTimer),_s.autoStateTimer=null),t>0?_s.autoStateTimer=setTimeout(()=>{_s.autoStateTimer=null,this.stateIdleChange()},t):this.stateIdleChange()}initWebSocket(){this.ws=new WebSocket(this.url),this.ws.onopen=()=>{this.sendMessage({type:0}),this.startHeartbeat()},this.ws.onmessage=e=>{const t=JSON.parse(e.data),{type:s,data:i,code:r,msg:n}=t;if(0!==s&&console.warn(s),s===rt.OUT_CALL&&0!==r&&(ct.emit(it.OUT_FAILED,n),this.stateIdleChange()),s===rt.AGENT_STATE){const{state:e,state_name:t}=i;_s.stateObject={state:e,state_name:t},"振铃中"===t&&Rs.updateDisplayText(ot.AGENT_RINGING),ct.emit(it.AGENT_STATE,{state:e,state_name:t})}s===rt.OUT_CALL_INCOMING_CALL&&(ct.emit(it.OUT_INCOMING_CALL,i),_s.sessionId=i.session_id,Rs.updateActionConfigs(bs)),s===rt.OUT_CALL_END&&(1===i.device_type?(this.putAgentState(),Rs.reset(),ct.emit(it.OUT_HANGUP,i)):2===i.device_type&&(Rs.get("direction")===tt.CONSULT?(Rs.reset(),ct.emit(it.OUR_SIDE_CONSULT_HANGUP,i),this.putAgentState()):Rs.get("isCalling")&&(Rs.updateDisplayText(ot.CALLING),ct.emit(it.OTHER_SIDE_CONSULT_HANGUP,i),Rs.updateActionConfigs(Ts)))),s===rt.OUT_CALL_RINGING&&(ct.emit(it.OUT_RINGING,i),Rs.updateDisplayText(ot.RINGING)),s===rt.OUT_CALL_ANSWER&&(ct.emit(it.OUT_OFF_ANSWER,i),Rs.updateOutCallIsAnswer(!0),Rs.updateDisplayText(ot.CALLING),Rs.updateActionConfigs(Ts)),s===rt.CONSULT_RINGING&&(ct.emit(it.CONSULT_RINGING,i),Rs.updateDisplayText(ot.AGENT_RINGING),Rs.updateActionConfigs([])),s===rt.CONSULT_ANSWER&&(ct.emit(it.CONSULT_OFF_HOOK,i),Rs.updateConsultIsAnswer(!0),Rs.updateDisplayText(ot.CONSULTING),Rs.updateActionConfigs(Ss)),s===rt.CONSULT_FAIL&&(ct.emit(it.CONSULT_FAILED),Rs.updateDisplayText(ot.CALLING),Rs.updateActionConfigs(Ts)),s===rt.CONSULT_CALL_IN&&(ct.emit(it.CONSULT_INCOMING,i),Rs.updateIsCalling(!0),Rs.updateDirection(tt.CONSULT),Rs.updateDisplayText(ot.CONSULT_CALL_IN),Rs.updateActionConfigs(Es),_s.enableBrowserAlert&&qi.show("📞 咨询来电",`坐席 [${i.agent_name}] 正在咨询...`,()=>{ct.emit(nt.NOTIFICATION_CLICK,{agent_mane:i.agent_name})})),s===rt.CONSULT_CALL_IN_SUCCESS&&(_s.sessionId=i.session_id,ct.emit(it.CONSULT_CALL_IN_SUCCESS,i),Rs.updateConsultIsAnswer(!0),Rs.updateDisplayText(ot.CALLING),Rs.updateActionConfigs(Cs)),s===rt.MUTE&&(0===r?(Rs.updateIsMuted(!0),ct.emit(it.MUTE_SUCCESS)):ct.emit(it.MUTE_FAILED,n)),s===rt.UNMUTE&&(0===r?(Rs.updateIsMuted(!1),ct.emit(it.UNMUTE_SUCCESS)):ct.emit(it.UNMUTE_FAILED,n)),s===rt.CONSULT_TRANSFER&&(0===r?ct.emit(it.CONSULT_TRANSFER_SUCCESS):ct.emit(it.CONSULT_TRANSFER_FAILED,n)),s===rt.CONSULT_TRANSFER_FAILED&&ct.emit(it.CONSULT_TRANSFER_FAILED,i.status),s===rt.CONSULT_TRANSFER_OFF_HOOK&&(Rs.updateDirection(tt.INCOMING),Rs.updateDisplayText(ot.CALLING),ct.emit(it.CONSULT_TRANSFER_OFF_HOOK),Rs.updateActionConfigs(As),Rs.updateIncomingIsAnswer(!0),Rs.updateConsultIsAnswer(!1)),s===rt.INCOMING_CALL&&(_s.sessionId=i.session_id,Rs.updateIsCalling(!0),Rs.updateDirection(tt.INCOMING),Rs.updateDisplayText(ot.INCOMING_CALL),ct.emit(it.INCOMING_CALL,{customer_phone:i.customer_phone}),Rs.updateActionConfigs(Es),_s.enableBrowserAlert&&qi.show("📞 客户来电",`客户 [${i.customer_phone}] 正在呼入...`,()=>{ct.emit(nt.NOTIFICATION_CLICK,{customer_phone:i.customer_phone})})),s===rt.INCOMING_CALL_OFF_HOOK&&(Rs.updateIncomingIsAnswer(!0),ct.emit(it.INCOMING_CALL_OFF_HOOK),Rs.updateActionConfigs(As),Rs.updateDisplayText(ot.CALLING)),s===rt.INCOMING_CALL_END&&1===i.device_type&&(ct.emit(it.INCOMING_CALL_END),Rs.reset(),Rs.updateActionConfigs(ws),this.putAgentState()),0!==s&&Oi.emit("ws-message",t)},this.ws.onclose=()=>{this.stopHeartbeat(),this.manualClose||setTimeout(()=>this.initWebSocket(),this.reconnectDelay)},this.ws.onerror=()=>{this.ws?.close()}}startHeartbeat(){const e=new Blob(["\n let timer;\n self.onmessage = function (e) {\n if (e.data === 'start') {\n timer = setInterval(() => {\n self.postMessage('heartbeat');\n }, 14000);\n } else if (e.data === 'stop') {\n clearInterval(timer);\n }\n };\n "],{type:"application/javascript"}),t=URL.createObjectURL(e);this.heartbeatWorker=new Worker(t),this.heartbeatWorker.onmessage=()=>{this.sendMessage({type:0})},this.heartbeatWorker.postMessage("start")}stopHeartbeat(){this.heartbeatWorker&&(this.heartbeatWorker.postMessage("stop"),this.heartbeatWorker.terminate(),this.heartbeatWorker=null)}sendMessage(e){this.ws?.readyState===WebSocket.OPEN?this.ws.send(JSON.stringify(e)):console.warn("WebSocket 未连接,消息未发送")}close(){this.manualClose=!0,this.stopHeartbeat(),this.ws?.close()}static __internalClose(){Li&&(Li.close(),Li=null)}static __internalSend(e){if(!Li)throw new Error("WebSocket 尚未初始化");Li.sendMessage(e)}}module.exports=class{#e=null;#t=new Hi;#s=null;#i=!1;#r;constructor(){clearInterval(this.#s),this.#s=setInterval(async()=>{const e=await this.#n();Ei.updateRttObject(e)},2e3),qi.requestMediaPermissions(),window.addEventListener("beforeunload",()=>{this.destroy()}),this.#r=Rs.subscribeKey("answerDevice",e=>{1===e&&this.initWebRtc()})}async initWebRtc(){const e=_s.agentInfo.sip_account_list.find(e=>1===e.device_type);if(e){const t=e.sip_register_addr;this.#e=new Ai({server:t.split(":")[0],user:e.sip_account,password:e.sip_password,webSocket:`wss://${t}`,callTimeout:3e4},this.#o),await this.#e.start()}}async init({actionNodeParams:e,url:t,appKey:s,appSecret:i,agentNo:r,enableBrowserAlert:n}){try{const{viewHtmlElement:o,rttHTML:a}=e;bi=t||"https://aicc-api.yescloudy.com",Ti=s,Si=i;const c=ni(s,i);await this.#t._agentLogin(r);const{agent_no:h,current_answer_device:d,init_state:l}=_s.agentInfo;l===et.IDLE?(Rs.updateActionConfigs(ws),_s.stateObject={state:l,state_name:"空闲"}):l===et.BUSY&&(Rs.updateActionConfigs(ys),_s.stateObject={state:l,state_name:"忙碌"}),Ui.getInstance(`ws://112.90.183.54:8719/api/v1/ws/call/${h}?Authorization=${c}`),Rs.updateAnswerDevice(d),1===d&&await this.initWebRtc(),e&&o&&(this.#i=!0,new Pi({container:o,rttHTML:a,statusParams:{state:et.IDLE,statusName:"空闲"}},this)),_s.enableBrowserAlert=n,n&&qi.requestPermission()}catch(e){this.#i&&mi("初始化失败"),console.log(e)}}#o=e=>{const{type:t,data:s}=e;ct.emit(t,s)};#a=e=>{};on(e,t){return ct.on(e,t),this}off(e,t){ct.off(e,t)}offAll(){ct.clearAllListeners()}#c(){if(!this.#e)throw new Error("请先初始化SDK");const e=Rs.get("isCalling"),t=_s.sessionId;if(!e)throw new Error("当前没有通话");if(!t)throw new Error("sessionId不存在")}#h(){const e=Rs.get("isHold"),{sessionId:t}=_s;if(t){if(e)throw new Error("当前已处于保持状态");Ui.__internalSend({type:rt.CALL_HOLD,data:{session_id:t}}),Rs.updateDisplayText(ot.HOLD),Rs.updateIsHold(!0)}}#d(){const{isHold:e}=Rs.getState(),{sessionId:t}=_s;if(t){if(!e)throw new Error("当前未处于保持状态");Ui.__internalSend({type:rt.CALL_UNHOLD,data:{session_id:t}}),Rs.updateDisplayText(""),Rs.updateIsHold(!1)}}#l(e){if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:t}=Rs.getState();if(!t)throw new Error("当前没有通话");this.#e.sendDTMF(e)}#g(){if(1!==Rs.get("answerDevice"))throw new Error("当前设备不支持此功能");if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:e,direction:t,outCallIsAnswer:s}=Rs.getState();if(!e)throw new Error("当前没有通话");s||(t===tt.INCOMING?this.#u():t===tt.OUTGOING&&this.#p())}async#f(e){if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:t,outCallIsAnswer:s,consultIsAnswer:i,incomingIsAnswer:r}=Rs.getState(),n=_s.sessionId;if(i)throw new Error("当前正在咨询");if(!(t||s||r))throw new Error("当前没有通话");if(!n)throw new Error("sessionId不存在");Ui.__internalSend({type:rt.CONSULT_REQUEST,data:{session_id:n,consultation_agent_no:e}})}async#m(){if(1!==Rs.get("answerDevice"))throw new Error("当前设备不支持此功能");if(!this.#e)throw new Error("请先初始化SDK");await this.#e.answerCall()}async#u(){if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:e}=Rs.getState();if(!e)throw new Error("当前没有通话");await this.#e.rejectInCall()}async#v(e){if(!this.#e)throw new Error("请先初始化SDK");if(Rs.getState().isCalling)throw new Error("正在呼叫中,请勿重复点击");Rs.updateIsCalling(!0),Rs.updateDirection(tt.OUTGOING),Ui.__internalSend({type:rt.OUT_CALL,data:{customer_phone:e}})}async#p(){const{isCalling:e}=Rs.getState();if(!this.#e)throw new Error("请先初始化SDK");if(!e)throw new Error("当前没有通话");await this.#e.rejectOutCall()}async#w(){if(!this.#e)throw new Error("请先初始化SDK");if(!Rs.get("isCalling"))throw new Error("当前没有通话");await this.#e.hangup()}#y(){this.#c();const e=Rs.get("actionConfigs"),t=Rs.get("isMuted"),s=_s.sessionId;if(t)throw new Error("当前未处于静音状态");if(!e.includes("mute"))throw new Error("当前状态请勿调用静音");Ui.__internalSend({type:rt.MUTE,data:{session_id:s}})}#b(){this.#c();const e=Rs.get("actionConfigs"),t=Rs.get("isMuted"),s=_s.sessionId;if(!t)throw new Error("当前未处于未静音状态");if(!e.includes("unmute"))throw new Error("当前状态请勿调用静音");Ui.__internalSend({type:rt.UNMUTE,data:{session_id:s}})}#T(){this.#c();const e=Rs.get("consultIsAnswer"),t=_s.sessionId;if(!Rs.get("actionConfigs").includes("consult_transfer"))throw new Error("当前状态请勿调用咨询转移");if(!e)throw new Error("当前未处于咨询中");Ui.__internalSend({type:rt.CONSULT_TRANSFER,data:{session_id:t}})}async destroyRtc(){await(this.#e?.destroy())}destroy(){this.destroyRtc(),clearInterval(this.#s),Oi.off("ws-message",this.#a),Ui.__internalClose(),this.offAll(),this.#r?.(),this.#e=null,this.#t=null,this.#i=!1,console.log(1111)}async#n(){return this.#e?this.#e.getNetworkStats():{}}get call_api(){return this.#i?{answerCall:this.#m.bind(this),makeCall:this.#v.bind(this),cancelCall:this.#g.bind(this),hangup:this.#w.bind(this),sendDTMF:this.#l.bind(this),holdCall:this.#h.bind(this),unholdCall:this.#d.bind(this),consultCall:async e=>{try{await this.#f(e)}catch(e){throw console.warn(e),e}},consultTransfer:this.#T.bind(this),mute:this.#y.bind(this),unmute:this.#b.bind(this)}:{}}get agent_api(){return this.#i?{changeDevice:this.#t.changeDevice.bind(this.#t),changeState:this.#t.changeState.bind(this.#t),getIdleAgentList:e=>this.#t.getIdleAgentList(e),getAgentState:e=>this.#t.getAgentState(e)}:{}}};
@@ -114,7 +114,7 @@ const ci=e=>(t,s)=>{void 0!==s?s.addInitializer(()=>{customElements.define(e,t)}
114
114
  border: 1px solid #595959;
115
115
  color: #595959;
116
116
  }
117
- `}connectedCallback(){super.connectedCallback(),setTimeout(()=>{this.remove()},this.duration+300)}render(){return Jt`<div>${this.content}</div>`}};ai([li({type:String})],pi.prototype,"type",void 0),ai([li({type:String})],pi.prototype,"content",void 0),ai([li({type:Number})],pi.prototype,"duration",void 0),pi=ai([ci("my-message")],pi);const fi=(e,t,s=3e3)=>{let i=document.querySelector("#message-container");i||(i=document.createElement("div"),i.id="message-container",Object.assign(i.style,{position:"fixed",top:"14px",left:"50%",transform:"translateX(-50%)",zIndex:"9999"}),document.body.appendChild(i));const r=document.createElement("my-message");r.setAttribute("type",e),r.setAttribute("content",t),r.setAttribute("duration",String(s)),r.style.setProperty("--fade-delay",s/1e3+"s"),i.appendChild(r)},mi=(e,t=3e3)=>fi("success",e,t),vi=(e,t=3e3)=>fi("error",e,t),wi=(e,t=3e3)=>fi("warning",e,t),yi="/v1/aicc/bmserver",bi="/v1/aicc/ccs";let Ti="",Si="",Ci="";async function Ei(e,t){const s=function(){const e=oi(Si,Ci);return si.create({prefixUrl:`${Ti}/api`,timeout:!1,headers:{Authorization:e},hooks:{afterResponse:[async(e,t,s)=>{const i=await s.clone().json();if(0!==i.code)throw vi(i.msg),new Error(JSON.stringify(i)||"请求失败");return s.json=async()=>i,s}]}})}(),i=e.replace(/^\/+/,"");return s.post(i,{json:t}).json()}const Ai=new class extends Qe{updateRttObject(e){this.setState({rttObject:e})}}({rttObject:{rtt:null,jitter:null,packetsLost:null,packetsReceived:null,packetsSent:null,sendBitrate:null,recvBitrate:null,codec:null}});class _i{constructor(e,t){this.config=e,this.eventCallback=t,this.userAgent=null,this.registerer=null,this.activeSession=null,this.callTimeoutTimer=null,this.incomingInvitation=null,this.reconnectAttempts=0,this.maxReconnectAttempts=5,this.reconnectTimer=null,this.isOffline=!1,this.autoAnswerTimer=null,this.handleMessage=e=>{},this.handleNetworkInfoChange=e=>{if(e.rttObject?.rtt){0===(e.rttObject?.rtt||0)?this.isOffline||(this.isOffline=!0,this.handleOffline()):this.isOffline&&(this.isOffline=!1,this.handleOnline())}},this.handleOffline=()=>{console.warn("[SIPClient] 网络断开,准备销毁 SIP 客户端",(new Date).toLocaleString()),this.destroy()},this.handleOnline=()=>{console.warn("[SIPClient] 网络恢复,准备重新启动 SIP 客户端",(new Date).toLocaleString()),this.reconnect()},Ai.subscribe(this.handleNetworkInfoChange)}async start(){const e=Xe.makeURI(`sip:${this.config.user}@${this.config.server}`);if(!e)throw new Error("无效的SIP配置");const t={uri:e,authorizationUsername:this.config.user,authorizationPassword:this.config.password,transportOptions:{server:this.config.webSocket,connectionTimeout:30,keepAliveInterval:0},sessionDescriptionHandlerFactoryOptions:{alwaysAcquireMediaFirst:!0,peerConnectionConfiguration:{iceServers:[]},iceGatheringTimeout:400},logBuiltinEnabled:!1};this.userAgent=new Xe(t),this.setupEventListeners(),await this.userAgent.start(),await this.register()}setupEventListeners(){this.userAgent&&(this.userAgent.transport.stateChange.addListener(e=>{let t;switch(e){case ee.Connecting:t=st.WEB_RTC_CONNECTING;break;case ee.Connected:t=st.WEB_RTC_CONNECTED,this.reconnectAttempts=0,_s.isRtcReconnecting=!1,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null);break;case ee.Disconnecting:t=st.WEB_RTC_DISCONNECTING;break;case ee.Disconnected:{t=st.WEB_RTC_DISCONNECTED;const e=Rs.get("answerDevice");if(!_s.isRtcReconnecting&&this.reconnectAttempts<this.maxReconnectAttempts&&1===e){this.reconnectAttempts++;const e=1e3*this.reconnectAttempts;console.warn(`SIP WebSocket 断开,第 ${this.reconnectAttempts} 次尝试重连,${e}ms 后重试...`),this.reconnectTimer=setTimeout(()=>{this.reconnect()},e)}else this.reconnectAttempts>=this.maxReconnectAttempts&&console.error("SIP 重连失败:已达到最大重试次数");break}default:t="unknown"}this.eventCallback?.({type:t})}),this.userAgent.transport.stateChange.addListener(e=>{}),this.userAgent.delegate={onInvite:e=>{this.handleIncomingCall(e)}})}async register(){if(this.userAgent){this.registerer=new ne(this.userAgent),this.registerer?.stateChange.addListener(e=>{let t;switch(e){case X.Initial:t="initial";break;case X.Registered:t=st.WEB_RTC_REGISTERED;break;case X.Unregistered:t=st.WEB_RTC_UNREGISTERED;break;case X.Terminated:t=st.WEB_RTC_TERMINATED;break;default:t="unknown"}this.eventCallback?.({type:t})});try{await this.registerer.register()}catch(e){this.eventCallback?.({type:st.WEB_RTC_REGISTER_FAILED,data:e})}}}attachRemoteAudio(e){const t=e.sessionDescriptionHandler;if(t){const e=t.peerConnection;if(!e)return;const s=new MediaStream;e.getReceivers().forEach(e=>{e.track&&"audio"===e.track.kind&&s.addTrack(e.track)});let i=document.getElementById("sip-remote-audio");i||(i=document.createElement("audio"),i.id="sip-remote-audio",i.autoplay=!0,i.style.display="none",document.body.appendChild(i)),i.srcObject=s,i.play().catch(e=>{console.error("音频播放失败,需要用户交互触发",e)})}}handleSessionState(e,t,s){e.stateChange.addListener(t=>{switch(t){case K.Established:clearTimeout(this.callTimeoutTimer),this.activeSession=e,this.attachRemoteAudio(e);break;case K.Terminating:break;case K.Terminated:this.activeSession=null,clearTimeout(this.callTimeoutTimer);case K.Establishing:}})}handleIncomingCall(e){this.incomingInvitation=e,this.handleSessionState(e,!1);const{soft_device_auto_answer:t,auto_answer_time:s}=_s.agentInfo,i=Rs.get("direction"),r=1===t;if(i===tt.OUTGOING)this.answerCall();else if(r&&i===tt.INCOMING){const e=Number(s)||0;e>0?this.autoAnswerTimer=setTimeout(()=>{this.answerCall(),this.autoAnswerTimer=null},e):this.answerCall()}}async makeCall(e){if(Rs.getState().isCalling)throw new Error("正在呼叫中,请勿重复点击");if(!this.userAgent)throw Rs.updateIsCalling(!1),Rs.updateDirection(null),new Error("SIP客户端未初始化");Rs.updateIsCalling(!0),Rs.updateDirection(tt.OUTGOING);const t=Xe.makeURI(`sip:${e}@${this.config.server}`);if(!t)throw new Error("无效的被叫号码");const s=new Z(this.userAgent,t);this.handleSessionState(s,!0,e);try{this.activeSession=s,await s.invite(),this.config.callTimeout&&(this.callTimeoutTimer=setTimeout(()=>{s.state!==K.Established&&(this.hangup(),this.eventCallback?.({type:"error",data:{message:"呼叫超时未接通"}}))},this.config.callTimeout))}catch(e){this.eventCallback?.({type:"error",data:{message:"呼叫失败",detail:e}}),Rs.updateIsCalling(!1),Rs.updateDirection(null)}}async answerCall(){if(!this.incomingInvitation)throw new Error("无来电可接听");this.autoAnswerTimer&&(clearTimeout(this.autoAnswerTimer),this.autoAnswerTimer=null);try{console.log((new Date).toLocaleString()),await this.incomingInvitation.accept(),console.log((new Date).toLocaleString()),this.activeSession=this.incomingInvitation,this.incomingInvitation=null}catch(e){this.eventCallback?.({type:st.WEB_RTC_ANSWER_FAILED,data:{detail:e}})}}async rejectInCall(){if(this.incomingInvitation)try{await this.incomingInvitation.reject(),this.incomingInvitation=null,Rs.updateDirection(null),Rs.updateIsCalling(!1),clearTimeout(this.callTimeoutTimer)}catch(e){this.eventCallback?.({type:"error",data:{message:"拒接失败",detail:e}})}}sendDTMF(e){if(!this.activeSession)throw new Error("当前没有活跃的通话");const t=this.activeSession.sessionDescriptionHandler;t&&"function"==typeof t.sendDtmf?(t.sendDtmf(e),this.eventCallback?.({type:st.WEB_RTC_SEND_DTMF,data:{tone:e}})):console.warn("DTMF发送不支持或未初始化")}async hangup(){if(this.activeSession){clearTimeout(this.callTimeoutTimer);const e=this.activeSession;if(!e)return;const t=e.state;try{t===K.Established?await e.bye():t===K.Establishing&&(e instanceof Z?await e.cancel():e instanceof J&&await e.reject()),clearTimeout(this.callTimeoutTimer)}catch(e){console.error("挂断失败",e)}finally{this.activeSession=null}}}async rejectOutCall(){this.activeSession instanceof J&&(await this.activeSession.reject(),this.activeSession=null)}async destroy(){if(clearTimeout(this.callTimeoutTimer),this.activeSession&&await this.hangup(),this.registerer){try{await this.registerer.unregister()}catch(e){console.warn("注销失败",e)}this.registerer=null}this.userAgent&&(await this.userAgent.stop(),this.userAgent=null)}async reconnect(){if(!_s.isRtcReconnecting){_s.isRtcReconnecting=!0;try{await this.destroy(),await this.start(),console.log("重连成功"),this.isOffline=!1}catch(e){console.error("重连失败",e)}finally{_s.isRtcReconnecting=!1}}}async getNetworkStats(){if(this.activeSession){const e=this.activeSession.sessionDescriptionHandler?.peerConnection;if(!e)return null;const t=await e.getStats(),s={};return t.forEach(e=>{"candidate-pair"===e.type&&"succeeded"===e.state&&null!=e.currentRoundTripTime&&(s.rtt=+(1e3*e.currentRoundTripTime).toFixed(2)),"inbound-rtp"===e.type&&"audio"===e.kind&&(s.jitter=+(1e3*e.jitter).toFixed(2),s.packetsLost=e.packetsLost,s.packetsReceived=e.packetsReceived,e.bytesReceived&&e.timestamp&&(s.recvBitrate=+(e.bytesReceived/1024).toFixed(2))),"outbound-rtp"===e.type&&"audio"===e.kind&&(s.packetsSent=e.packetsSent,e.bytesSent&&e.timestamp&&(s.sendBitrate=+(e.bytesSent/1024).toFixed(2))),"codec"===e.type&&e.mimeType&&(s.codec=e.mimeType)}),{...s,rtt:s?.rtt||40}}try{const e=Date.now();await Ei(`${yi}/config/ping`,{});const t=Date.now();return{rtt:t-e||1}}catch(e){console.log(e)}}}let Ii=class extends fs{static{this.styles=pt`
117
+ `}connectedCallback(){super.connectedCallback(),setTimeout(()=>{this.remove()},this.duration+300)}render(){return Jt`<div>${this.content}</div>`}};ai([li({type:String})],pi.prototype,"type",void 0),ai([li({type:String})],pi.prototype,"content",void 0),ai([li({type:Number})],pi.prototype,"duration",void 0),pi=ai([ci("my-message")],pi);const fi=(e,t,s=3e3)=>{let i=document.querySelector("#message-container");i||(i=document.createElement("div"),i.id="message-container",Object.assign(i.style,{position:"fixed",top:"14px",left:"50%",transform:"translateX(-50%)",zIndex:"9999"}),document.body.appendChild(i));const r=document.createElement("my-message");r.setAttribute("type",e),r.setAttribute("content",t),r.setAttribute("duration",String(s)),r.style.setProperty("--fade-delay",s/1e3+"s"),i.appendChild(r)},mi=(e,t=3e3)=>fi("success",e,t),vi=(e,t=3e3)=>fi("error",e,t),wi=(e,t=3e3)=>fi("warning",e,t),yi="/v1/aicc/bmserver",bi="/v1/aicc/ccs";let Ti="",Si="",Ci="";async function Ei(e,t){const s=function(){const e=oi(Si,Ci);return si.create({prefixUrl:`${Ti}/api`,timeout:!1,headers:{Authorization:e},hooks:{afterResponse:[async(e,t,s)=>{const i=await s.clone().json();if(0!==i.code)throw vi(i.msg),new Error(JSON.stringify(i)||"请求失败");return s.json=async()=>i,s}]}})}(),i=e.replace(/^\/+/,"");return s.post(i,{json:t}).json()}const Ai=new class extends Qe{updateRttObject(e){this.setState({rttObject:e})}}({rttObject:{rtt:null,jitter:null,packetsLost:null,packetsReceived:null,packetsSent:null,sendBitrate:null,recvBitrate:null,codec:null}});class _i{constructor(e,t){this.config=e,this.eventCallback=t,this.userAgent=null,this.registerer=null,this.activeSession=null,this.callTimeoutTimer=null,this.incomingInvitation=null,this.reconnectAttempts=0,this.maxReconnectAttempts=5,this.reconnectTimer=null,this.isOffline=!1,this.autoAnswerTimer=null,this.isManuallyStopped=!1,this.handleMessage=e=>{},this.handleNetworkInfoChange=e=>{if(e.rttObject?.rtt){0===(e.rttObject?.rtt||0)?this.isOffline||(this.isOffline=!0,this.handleOffline()):this.isOffline&&(this.isOffline=!1,this.handleOnline())}},this.handleOffline=()=>{console.warn("[SIPClient] 网络断开,准备销毁 SIP 客户端",(new Date).toLocaleString()),this.destroy()},this.handleOnline=()=>{console.warn("[SIPClient] 网络恢复,准备重新启动 SIP 客户端",(new Date).toLocaleString()),this.reconnect()},Ai.subscribe(this.handleNetworkInfoChange)}async start(){const e=Xe.makeURI(`sip:${this.config.user}@${this.config.server}`);if(!e)throw new Error("无效的SIP配置");const t={uri:e,authorizationUsername:this.config.user,authorizationPassword:this.config.password,transportOptions:{server:this.config.webSocket,connectionTimeout:30,keepAliveInterval:0},sessionDescriptionHandlerFactoryOptions:{alwaysAcquireMediaFirst:!0,peerConnectionConfiguration:{iceServers:[]},iceGatheringTimeout:400},logBuiltinEnabled:!1};this.userAgent=new Xe(t),this.setupEventListeners(),await this.userAgent.start(),await this.register()}setupEventListeners(){this.userAgent&&(this.userAgent.transport.stateChange.addListener(e=>{let t;switch(e){case ee.Connecting:t=st.WEB_RTC_CONNECTING;break;case ee.Connected:t=st.WEB_RTC_CONNECTED,this.reconnectAttempts=0,_s.isRtcReconnecting=!1,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null);break;case ee.Disconnecting:t=st.WEB_RTC_DISCONNECTING;break;case ee.Disconnected:{if(this.isManuallyStopped)return void console.warn("[SIP] 手动断开,不触发重连");t=st.WEB_RTC_DISCONNECTED;const e=Rs.get("answerDevice");if(!_s.isRtcReconnecting&&this.reconnectAttempts<this.maxReconnectAttempts&&1===e){this.reconnectAttempts++;const e=1e3*this.reconnectAttempts;console.warn(`SIP WebSocket 断开,第 ${this.reconnectAttempts} 次尝试重连,${e}ms 后重试...`),this.reconnectTimer=setTimeout(()=>{this.reconnect()},e)}else this.reconnectAttempts>=this.maxReconnectAttempts&&console.error("SIP 重连失败:已达到最大重试次数");break}default:t="unknown"}this.eventCallback?.({type:t})}),this.userAgent.transport.stateChange.addListener(e=>{}),this.userAgent.delegate={onInvite:e=>{this.handleIncomingCall(e)}})}async register(){if(this.userAgent){this.registerer=new ne(this.userAgent),this.registerer?.stateChange.addListener(e=>{let t;switch(e){case X.Initial:t="initial";break;case X.Registered:t=st.WEB_RTC_REGISTERED;break;case X.Unregistered:t=st.WEB_RTC_UNREGISTERED;break;case X.Terminated:t=st.WEB_RTC_TERMINATED;break;default:t="unknown"}this.eventCallback?.({type:t})});try{await this.registerer.register()}catch(e){this.eventCallback?.({type:st.WEB_RTC_REGISTER_FAILED,data:e})}}}attachRemoteAudio(e){const t=e.sessionDescriptionHandler;if(t){const e=t.peerConnection;if(!e)return;const s=new MediaStream;e.getReceivers().forEach(e=>{e.track&&"audio"===e.track.kind&&s.addTrack(e.track)});let i=document.getElementById("sip-remote-audio");i||(i=document.createElement("audio"),i.id="sip-remote-audio",i.autoplay=!0,i.style.display="none",document.body.appendChild(i)),i.srcObject=s,i.play().catch(e=>{console.error("音频播放失败,需要用户交互触发",e)})}}handleSessionState(e,t,s){e.stateChange.addListener(t=>{switch(t){case K.Established:clearTimeout(this.callTimeoutTimer),this.activeSession=e,this.attachRemoteAudio(e);break;case K.Terminating:break;case K.Terminated:this.activeSession=null,clearTimeout(this.callTimeoutTimer);case K.Establishing:}})}handleIncomingCall(e){this.incomingInvitation=e,this.handleSessionState(e,!1);const{soft_device_auto_answer:t,auto_answer_time:s}=_s.agentInfo,i=Rs.get("direction"),r=1===t;if(i===tt.OUTGOING)this.answerCall();else if(r&&i===tt.INCOMING){const e=Number(s)||0;e>0?this.autoAnswerTimer=setTimeout(()=>{this.answerCall(),this.autoAnswerTimer=null},e):this.answerCall()}}async makeCall(e){if(Rs.getState().isCalling)throw new Error("正在呼叫中,请勿重复点击");if(!this.userAgent)throw Rs.updateIsCalling(!1),Rs.updateDirection(null),new Error("SIP客户端未初始化");Rs.updateIsCalling(!0),Rs.updateDirection(tt.OUTGOING);const t=Xe.makeURI(`sip:${e}@${this.config.server}`);if(!t)throw new Error("无效的被叫号码");const s=new Z(this.userAgent,t);this.handleSessionState(s,!0,e);try{this.activeSession=s,await s.invite(),this.config.callTimeout&&(this.callTimeoutTimer=setTimeout(()=>{s.state!==K.Established&&(this.hangup(),this.eventCallback?.({type:"error",data:{message:"呼叫超时未接通"}}))},this.config.callTimeout))}catch(e){this.eventCallback?.({type:"error",data:{message:"呼叫失败",detail:e}}),Rs.updateIsCalling(!1),Rs.updateDirection(null)}}async answerCall(){if(!this.incomingInvitation)throw new Error("无来电可接听");this.autoAnswerTimer&&(clearTimeout(this.autoAnswerTimer),this.autoAnswerTimer=null);try{console.log((new Date).toLocaleString()),await this.incomingInvitation.accept(),console.log((new Date).toLocaleString()),this.activeSession=this.incomingInvitation,this.incomingInvitation=null}catch(e){this.eventCallback?.({type:st.WEB_RTC_ANSWER_FAILED,data:{detail:e}})}}async rejectInCall(){if(this.incomingInvitation)try{await this.incomingInvitation.reject(),this.incomingInvitation=null,Rs.updateDirection(null),Rs.updateIsCalling(!1),clearTimeout(this.callTimeoutTimer)}catch(e){this.eventCallback?.({type:"error",data:{message:"拒接失败",detail:e}})}}sendDTMF(e){if(!this.activeSession)throw new Error("当前没有活跃的通话");const t=this.activeSession.sessionDescriptionHandler;t&&"function"==typeof t.sendDtmf?(t.sendDtmf(e),this.eventCallback?.({type:st.WEB_RTC_SEND_DTMF,data:{tone:e}})):console.warn("DTMF发送不支持或未初始化")}async hangup(){if(this.activeSession){clearTimeout(this.callTimeoutTimer);const e=this.activeSession;if(!e)return;const t=e.state;try{t===K.Established?await e.bye():t===K.Establishing&&(e instanceof Z?await e.cancel():e instanceof J&&await e.reject()),clearTimeout(this.callTimeoutTimer)}catch(e){console.error("挂断失败",e)}finally{this.activeSession=null}}}async rejectOutCall(){this.activeSession instanceof J&&(await this.activeSession.reject(),this.activeSession=null)}async destroy(){if(this.isManuallyStopped=!0,clearTimeout(this.callTimeoutTimer),this.activeSession&&await this.hangup(),this.registerer){try{await(this.userAgent?.stop()),await this.registerer.unregister()}catch(e){console.warn("注销失败",e)}this.registerer=null}this.userAgent&&(await this.userAgent.stop(),this.userAgent=null)}async reconnect(){if(!_s.isRtcReconnecting){_s.isRtcReconnecting=!0;try{await this.destroy(),await this.start(),console.log("重连成功"),this.isOffline=!1}catch(e){console.error("重连失败",e)}finally{_s.isRtcReconnecting=!1}}}async getNetworkStats(){if(this.activeSession){const e=this.activeSession.sessionDescriptionHandler?.peerConnection;if(!e)return null;const t=await e.getStats(),s={};return t.forEach(e=>{"candidate-pair"===e.type&&"succeeded"===e.state&&null!=e.currentRoundTripTime&&(s.rtt=+(1e3*e.currentRoundTripTime).toFixed(2)),"inbound-rtp"===e.type&&"audio"===e.kind&&(s.jitter=+(1e3*e.jitter).toFixed(2),s.packetsLost=e.packetsLost,s.packetsReceived=e.packetsReceived,e.bytesReceived&&e.timestamp&&(s.recvBitrate=+(e.bytesReceived/1024).toFixed(2))),"outbound-rtp"===e.type&&"audio"===e.kind&&(s.packetsSent=e.packetsSent,e.bytesSent&&e.timestamp&&(s.sendBitrate=+(e.bytesSent/1024).toFixed(2))),"codec"===e.type&&e.mimeType&&(s.codec=e.mimeType)}),{...s,rtt:s?.rtt||40}}try{const e=Date.now();await Ei(`${yi}/config/ping`,{});const t=Date.now();return{rtt:t-e||1}}catch(e){console.log(e)}}}let Ii=class extends fs{static{this.styles=pt`
118
118
  .timer-text {
119
119
  width: 70px;
120
120
  font-size: 14px;
@@ -808,4 +808,4 @@ const ci=e=>(t,s)=>{void 0!==s?s.addInitializer(()=>{customElements.define(e,t)}
808
808
  `:""}
809
809
  </div>
810
810
  </my-popover>
811
- `;us(i,this.rttHTML)}}const qi=new class{constructor(){this.events={}}on(e,t){this.events[e]=this.events[e]||[],this.events[e].push(t)}off(e,t){this.events[e]=(this.events[e]||[]).filter(e=>e!==t)}emit(e,...t){(this.events[e]||[]).forEach(e=>e(...t))}};class Li{static requestPermission(){"Notification"in window&&"default"===Notification.permission&&Notification.requestPermission()}static async requestMediaPermissions(){try{const e=await navigator.mediaDevices.enumerateDevices();if(e.some(e=>"audioinput"===e.kind&&""!==e.label))return console.log("已授权麦克风权限,无需再次获取"),null;const t=await navigator.mediaDevices.getUserMedia({audio:!0});return console.log("获取音频权限成功"),t}catch(e){return console.warn("获取音频权限失败:",e),null}}static show(e,t,s){if("Notification"in window&&"granted"===Notification.permission){const i=new Notification(e,{body:t,tag:"xy-global-notification",renotify:!0});i.onclick=()=>{window.focus(),i.close(),s?.()},setTimeout(()=>i.close(),5e3)}}static async checkMediaPermissions(){try{return(await navigator.mediaDevices.enumerateDevices()).some(e=>"audioinput"===e.kind&&""!==e.label)}catch(e){return console.warn("无法检查设备权限:",e),!1}}}let Ui=null;class Mi{constructor(e){this.url=e,this.ws=null,this.heartbeatWorker=null,this.reconnectDelay=3e3,this.manualClose=!1,this.apiClient=new Pi,this.initWebSocket()}static getInstance(e){if(!Ui){if(!e)throw new Error("WebSocket 尚未初始化");Ui=new Mi(e)}return Ui}stateIdleChange(){const{state:e}=_s.stateObject;e!==et.IDLE&&this.apiClient.changeState({data:{state:et.IDLE,state_name:"空闲"}})}putAgentState(){_s.sessionId="";const{post_call_process_time:e}=_s.agentInfo,t=e||0;_s.autoStateTimer&&(clearTimeout(_s.autoStateTimer),_s.autoStateTimer=null),t>0?_s.autoStateTimer=setTimeout(()=>{_s.autoStateTimer=null,this.stateIdleChange()},t):this.stateIdleChange()}initWebSocket(){this.ws=new WebSocket(this.url),this.ws.onopen=()=>{this.sendMessage({type:0}),this.startHeartbeat()},this.ws.onmessage=e=>{const t=JSON.parse(e.data),{type:s,data:i,code:r,msg:n}=t;if(0!==s&&console.warn(s),s===rt.OUT_CALL&&0!==r&&(ct.emit(it.OUT_FAILED,n),this.stateIdleChange()),s===rt.AGENT_STATE){const{state:e,state_name:t}=i;_s.stateObject={state:e,state_name:t},"振铃中"===t&&Rs.updateDisplayText(ot.AGENT_RINGING),ct.emit(it.AGENT_STATE,{state:e,state_name:t})}s===rt.OUT_CALL_INCOMING_CALL&&(ct.emit(it.OUT_INCOMING_CALL,i),_s.sessionId=i.session_id,Rs.updateActionConfigs(bs)),s===rt.OUT_CALL_END&&(1===i.device_type?(this.putAgentState(),Rs.reset(),ct.emit(it.OUT_HANGUP,i)):2===i.device_type&&(Rs.get("direction")===tt.CONSULT?(Rs.reset(),ct.emit(it.OUR_SIDE_CONSULT_HANGUP,i),this.putAgentState()):Rs.get("isCalling")&&(Rs.updateDisplayText(ot.CALLING),ct.emit(it.OTHER_SIDE_CONSULT_HANGUP,i),Rs.updateActionConfigs(Ts)))),s===rt.OUT_CALL_RINGING&&(ct.emit(it.OUT_RINGING,i),Rs.updateDisplayText(ot.RINGING)),s===rt.OUT_CALL_ANSWER&&(ct.emit(it.OUT_OFF_ANSWER,i),Rs.updateOutCallIsAnswer(!0),Rs.updateDisplayText(ot.CALLING),Rs.updateActionConfigs(Ts)),s===rt.CONSULT_RINGING&&(ct.emit(it.CONSULT_RINGING,i),Rs.updateDisplayText(ot.AGENT_RINGING),Rs.updateActionConfigs([])),s===rt.CONSULT_ANSWER&&(ct.emit(it.CONSULT_OFF_HOOK,i),Rs.updateConsultIsAnswer(!0),Rs.updateDisplayText(ot.CONSULTING),Rs.updateActionConfigs(Ss)),s===rt.CONSULT_FAIL&&(ct.emit(it.CONSULT_FAILED),Rs.updateDisplayText(ot.CALLING),Rs.updateActionConfigs(Ts)),s===rt.CONSULT_CALL_IN&&(ct.emit(it.CONSULT_INCOMING,i),Rs.updateIsCalling(!0),Rs.updateDirection(tt.CONSULT),Rs.updateDisplayText(ot.CONSULT_CALL_IN),Rs.updateActionConfigs(Es),_s.enableBrowserAlert&&Li.show("📞 咨询来电",`坐席 [${i.agent_name}] 正在咨询...`,()=>{ct.emit(nt.NOTIFICATION_CLICK,{agent_mane:i.agent_name})})),s===rt.CONSULT_CALL_IN_SUCCESS&&(_s.sessionId=i.session_id,ct.emit(it.CONSULT_CALL_IN_SUCCESS,i),Rs.updateConsultIsAnswer(!0),Rs.updateDisplayText(ot.CALLING),Rs.updateActionConfigs(Cs)),s===rt.MUTE&&(0===r?(Rs.updateIsMuted(!0),ct.emit(it.MUTE_SUCCESS)):ct.emit(it.MUTE_FAILED,n)),s===rt.UNMUTE&&(0===r?(Rs.updateIsMuted(!1),ct.emit(it.UNMUTE_SUCCESS)):ct.emit(it.UNMUTE_FAILED,n)),s===rt.CONSULT_TRANSFER&&(0===r?ct.emit(it.CONSULT_TRANSFER_SUCCESS):ct.emit(it.CONSULT_TRANSFER_FAILED,n)),s===rt.CONSULT_TRANSFER_FAILED&&ct.emit(it.CONSULT_TRANSFER_FAILED,i.status),s===rt.CONSULT_TRANSFER_OFF_HOOK&&(Rs.updateDirection(tt.INCOMING),Rs.updateDisplayText(ot.CALLING),ct.emit(it.CONSULT_TRANSFER_OFF_HOOK),Rs.updateActionConfigs(As),Rs.updateIncomingIsAnswer(!0),Rs.updateConsultIsAnswer(!1)),s===rt.INCOMING_CALL&&(_s.sessionId=i.session_id,Rs.updateIsCalling(!0),Rs.updateDirection(tt.INCOMING),Rs.updateDisplayText(ot.INCOMING_CALL),ct.emit(it.INCOMING_CALL,{customer_phone:i.customer_phone}),Rs.updateActionConfigs(Es),_s.enableBrowserAlert&&Li.show("📞 客户来电",`客户 [${i.customer_phone}] 正在呼入...`,()=>{ct.emit(nt.NOTIFICATION_CLICK,{customer_phone:i.customer_phone})})),s===rt.INCOMING_CALL_OFF_HOOK&&(Rs.updateIncomingIsAnswer(!0),ct.emit(it.INCOMING_CALL_OFF_HOOK),Rs.updateActionConfigs(As),Rs.updateDisplayText(ot.CALLING)),s===rt.INCOMING_CALL_END&&1===i.device_type&&(ct.emit(it.INCOMING_CALL_END),Rs.reset(),Rs.updateActionConfigs(ws),this.putAgentState()),0!==s&&qi.emit("ws-message",t)},this.ws.onclose=()=>{this.stopHeartbeat(),this.manualClose||setTimeout(()=>this.initWebSocket(),this.reconnectDelay)},this.ws.onerror=()=>{this.ws?.close()}}startHeartbeat(){const e=new Blob(["\n let timer;\n self.onmessage = function (e) {\n if (e.data === 'start') {\n timer = setInterval(() => {\n self.postMessage('heartbeat');\n }, 14000);\n } else if (e.data === 'stop') {\n clearInterval(timer);\n }\n };\n "],{type:"application/javascript"}),t=URL.createObjectURL(e);this.heartbeatWorker=new Worker(t),this.heartbeatWorker.onmessage=()=>{this.sendMessage({type:0})},this.heartbeatWorker.postMessage("start")}stopHeartbeat(){this.heartbeatWorker&&(this.heartbeatWorker.postMessage("stop"),this.heartbeatWorker.terminate(),this.heartbeatWorker=null)}sendMessage(e){this.ws?.readyState===WebSocket.OPEN?this.ws.send(JSON.stringify(e)):console.warn("WebSocket 未连接,消息未发送")}close(){this.manualClose=!0,this.stopHeartbeat(),this.ws?.close()}static __internalClose(){Ui&&(Ui.close(),Ui=null)}static __internalSend(e){if(!Ui)throw new Error("WebSocket 尚未初始化");Ui.sendMessage(e)}}class Fi{#e=null;#t=new Pi;#s=null;#i=!1;#r;constructor(){clearInterval(this.#s),this.#s=setInterval(async()=>{const e=await this.#n();Ai.updateRttObject(e)},2e3),Li.requestMediaPermissions(),window.addEventListener("beforeunload",()=>{this.destroy()}),this.#r=Rs.subscribeKey("answerDevice",e=>{1===e&&this.initWebRtc()})}async initWebRtc(){const e=_s.agentInfo.sip_account_list.find(e=>1===e.device_type);if(e){const t=e.sip_register_addr;this.#e=new _i({server:t.split(":")[0],user:e.sip_account,password:e.sip_password,webSocket:`wss://${t}`,callTimeout:3e4},this.#o),await this.#e.start()}}async init({actionNodeParams:e,url:t,appKey:s,appSecret:i,agentNo:r,enableBrowserAlert:n}){try{const{viewHtmlElement:o,rttHTML:a}=e;Ti=t||"https://aicc-api.yescloudy.com",Si=s,Ci=i;const c=oi(s,i);await this.#t._agentLogin(r);const{agent_no:h,current_answer_device:d,init_state:l}=_s.agentInfo;l===et.IDLE?(Rs.updateActionConfigs(ws),_s.stateObject={state:l,state_name:"空闲"}):l===et.BUSY&&(Rs.updateActionConfigs(ys),_s.stateObject={state:l,state_name:"忙碌"}),Mi.getInstance(`ws://112.90.183.54:8719/api/v1/ws/call/${h}?Authorization=${c}`),Rs.updateAnswerDevice(d),1===d&&await this.initWebRtc(),e&&o&&(this.#i=!0,new Oi({container:o,rttHTML:a,statusParams:{state:et.IDLE,statusName:"空闲"}},this)),_s.enableBrowserAlert=n,n&&Li.requestPermission()}catch(e){this.#i&&vi("初始化失败"),console.log(e)}}#o=e=>{const{type:t,data:s}=e;ct.emit(t,s)};#a=e=>{};on(e,t){return ct.on(e,t),this}off(e,t){ct.off(e,t)}offAll(){ct.clearAllListeners()}#c(){if(!this.#e)throw new Error("请先初始化SDK");const e=Rs.get("isCalling"),t=_s.sessionId;if(!e)throw new Error("当前没有通话");if(!t)throw new Error("sessionId不存在")}#h(){const e=Rs.get("isHold"),{sessionId:t}=_s;if(t){if(e)throw new Error("当前已处于保持状态");Mi.__internalSend({type:rt.CALL_HOLD,data:{session_id:t}}),Rs.updateDisplayText(ot.HOLD),Rs.updateIsHold(!0)}}#d(){const{isHold:e}=Rs.getState(),{sessionId:t}=_s;if(t){if(!e)throw new Error("当前未处于保持状态");Mi.__internalSend({type:rt.CALL_UNHOLD,data:{session_id:t}}),Rs.updateDisplayText(""),Rs.updateIsHold(!1)}}#l(e){if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:t}=Rs.getState();if(!t)throw new Error("当前没有通话");this.#e.sendDTMF(e)}#g(){if(1!==Rs.get("answerDevice"))throw new Error("当前设备不支持此功能");if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:e,direction:t,outCallIsAnswer:s}=Rs.getState();if(!e)throw new Error("当前没有通话");s||(t===tt.INCOMING?this.#u():t===tt.OUTGOING&&this.#p())}async#f(e){if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:t,outCallIsAnswer:s,consultIsAnswer:i,incomingIsAnswer:r}=Rs.getState(),n=_s.sessionId;if(i)throw new Error("当前正在咨询");if(!(t||s||r))throw new Error("当前没有通话");if(!n)throw new Error("sessionId不存在");Mi.__internalSend({type:rt.CONSULT_REQUEST,data:{session_id:n,consultation_agent_no:e}})}async#m(){if(1!==Rs.get("answerDevice"))throw new Error("当前设备不支持此功能");if(!this.#e)throw new Error("请先初始化SDK");await this.#e.answerCall()}async#u(){if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:e}=Rs.getState();if(!e)throw new Error("当前没有通话");await this.#e.rejectInCall()}async#v(e){if(!this.#e)throw new Error("请先初始化SDK");if(Rs.getState().isCalling)throw new Error("正在呼叫中,请勿重复点击");Rs.updateIsCalling(!0),Rs.updateDirection(tt.OUTGOING),Mi.__internalSend({type:rt.OUT_CALL,data:{customer_phone:e}})}async#p(){const{isCalling:e}=Rs.getState();if(!this.#e)throw new Error("请先初始化SDK");if(!e)throw new Error("当前没有通话");await this.#e.rejectOutCall()}async#w(){if(!this.#e)throw new Error("请先初始化SDK");if(!Rs.get("isCalling"))throw new Error("当前没有通话");await this.#e.hangup()}#y(){this.#c();const e=Rs.get("actionConfigs"),t=Rs.get("isMuted"),s=_s.sessionId;if(t)throw new Error("当前未处于静音状态");if(!e.includes("mute"))throw new Error("当前状态请勿调用静音");Mi.__internalSend({type:rt.MUTE,data:{session_id:s}})}#b(){this.#c();const e=Rs.get("actionConfigs"),t=Rs.get("isMuted"),s=_s.sessionId;if(!t)throw new Error("当前未处于未静音状态");if(!e.includes("unmute"))throw new Error("当前状态请勿调用静音");Mi.__internalSend({type:rt.UNMUTE,data:{session_id:s}})}#T(){this.#c();const e=Rs.get("consultIsAnswer"),t=_s.sessionId;if(!Rs.get("actionConfigs").includes("consult_transfer"))throw new Error("当前状态请勿调用咨询转移");if(!e)throw new Error("当前未处于咨询中");Mi.__internalSend({type:rt.CONSULT_TRANSFER,data:{session_id:t}})}async destroyRtc(){await(this.#e?.destroy())}destroy(){this.destroyRtc(),clearInterval(this.#s),qi.off("ws-message",this.#a),Mi.__internalClose(),this.offAll(),this.#r?.()}async#n(){return this.#e?this.#e.getNetworkStats():{}}get call_api(){return this.#i?{answerCall:this.#m.bind(this),makeCall:this.#v.bind(this),cancelCall:this.#g.bind(this),hangup:this.#w.bind(this),sendDTMF:this.#l.bind(this),holdCall:this.#h.bind(this),unholdCall:this.#d.bind(this),consultCall:async e=>{try{await this.#f(e)}catch(e){throw console.warn(e),e}},consultTransfer:this.#T.bind(this),mute:this.#y.bind(this),unmute:this.#b.bind(this)}:{}}get agent_api(){return this.#i?{changeDevice:this.#t.changeDevice.bind(this.#t),changeState:this.#t.changeState.bind(this.#t),getIdleAgentList:e=>this.#t.getIdleAgentList(e),getAgentState:e=>this.#t.getAgentState(e)}:{}}}export{Fi as default};
811
+ `;us(i,this.rttHTML)}}const qi=new class{constructor(){this.events={}}on(e,t){this.events[e]=this.events[e]||[],this.events[e].push(t)}off(e,t){this.events[e]=(this.events[e]||[]).filter(e=>e!==t)}emit(e,...t){(this.events[e]||[]).forEach(e=>e(...t))}};class Li{static requestPermission(){"Notification"in window&&"default"===Notification.permission&&Notification.requestPermission()}static async requestMediaPermissions(){try{const e=await navigator.mediaDevices.enumerateDevices();if(e.some(e=>"audioinput"===e.kind&&""!==e.label))return console.log("已授权麦克风权限,无需再次获取"),null;const t=await navigator.mediaDevices.getUserMedia({audio:!0});return console.log("获取音频权限成功"),t}catch(e){return console.warn("获取音频权限失败:",e),null}}static show(e,t,s){if("Notification"in window&&"granted"===Notification.permission){const i=new Notification(e,{body:t,tag:"xy-global-notification",renotify:!0});i.onclick=()=>{window.focus(),i.close(),s?.()},setTimeout(()=>i.close(),5e3)}}static async checkMediaPermissions(){try{return(await navigator.mediaDevices.enumerateDevices()).some(e=>"audioinput"===e.kind&&""!==e.label)}catch(e){return console.warn("无法检查设备权限:",e),!1}}}let Ui=null;class Mi{constructor(e){this.url=e,this.ws=null,this.heartbeatWorker=null,this.reconnectDelay=3e3,this.manualClose=!1,this.apiClient=new Pi,this.initWebSocket()}static getInstance(e){if(!Ui){if(!e)throw new Error("WebSocket 尚未初始化");Ui=new Mi(e)}return Ui}stateIdleChange(){const{state:e}=_s.stateObject;e!==et.IDLE&&this.apiClient.changeState({data:{state:et.IDLE,state_name:"空闲"}})}putAgentState(){_s.sessionId="";const{post_call_process_time:e}=_s.agentInfo,t=e||0;_s.autoStateTimer&&(clearTimeout(_s.autoStateTimer),_s.autoStateTimer=null),t>0?_s.autoStateTimer=setTimeout(()=>{_s.autoStateTimer=null,this.stateIdleChange()},t):this.stateIdleChange()}initWebSocket(){this.ws=new WebSocket(this.url),this.ws.onopen=()=>{this.sendMessage({type:0}),this.startHeartbeat()},this.ws.onmessage=e=>{const t=JSON.parse(e.data),{type:s,data:i,code:r,msg:n}=t;if(0!==s&&console.warn(s),s===rt.OUT_CALL&&0!==r&&(ct.emit(it.OUT_FAILED,n),this.stateIdleChange()),s===rt.AGENT_STATE){const{state:e,state_name:t}=i;_s.stateObject={state:e,state_name:t},"振铃中"===t&&Rs.updateDisplayText(ot.AGENT_RINGING),ct.emit(it.AGENT_STATE,{state:e,state_name:t})}s===rt.OUT_CALL_INCOMING_CALL&&(ct.emit(it.OUT_INCOMING_CALL,i),_s.sessionId=i.session_id,Rs.updateActionConfigs(bs)),s===rt.OUT_CALL_END&&(1===i.device_type?(this.putAgentState(),Rs.reset(),ct.emit(it.OUT_HANGUP,i)):2===i.device_type&&(Rs.get("direction")===tt.CONSULT?(Rs.reset(),ct.emit(it.OUR_SIDE_CONSULT_HANGUP,i),this.putAgentState()):Rs.get("isCalling")&&(Rs.updateDisplayText(ot.CALLING),ct.emit(it.OTHER_SIDE_CONSULT_HANGUP,i),Rs.updateActionConfigs(Ts)))),s===rt.OUT_CALL_RINGING&&(ct.emit(it.OUT_RINGING,i),Rs.updateDisplayText(ot.RINGING)),s===rt.OUT_CALL_ANSWER&&(ct.emit(it.OUT_OFF_ANSWER,i),Rs.updateOutCallIsAnswer(!0),Rs.updateDisplayText(ot.CALLING),Rs.updateActionConfigs(Ts)),s===rt.CONSULT_RINGING&&(ct.emit(it.CONSULT_RINGING,i),Rs.updateDisplayText(ot.AGENT_RINGING),Rs.updateActionConfigs([])),s===rt.CONSULT_ANSWER&&(ct.emit(it.CONSULT_OFF_HOOK,i),Rs.updateConsultIsAnswer(!0),Rs.updateDisplayText(ot.CONSULTING),Rs.updateActionConfigs(Ss)),s===rt.CONSULT_FAIL&&(ct.emit(it.CONSULT_FAILED),Rs.updateDisplayText(ot.CALLING),Rs.updateActionConfigs(Ts)),s===rt.CONSULT_CALL_IN&&(ct.emit(it.CONSULT_INCOMING,i),Rs.updateIsCalling(!0),Rs.updateDirection(tt.CONSULT),Rs.updateDisplayText(ot.CONSULT_CALL_IN),Rs.updateActionConfigs(Es),_s.enableBrowserAlert&&Li.show("📞 咨询来电",`坐席 [${i.agent_name}] 正在咨询...`,()=>{ct.emit(nt.NOTIFICATION_CLICK,{agent_mane:i.agent_name})})),s===rt.CONSULT_CALL_IN_SUCCESS&&(_s.sessionId=i.session_id,ct.emit(it.CONSULT_CALL_IN_SUCCESS,i),Rs.updateConsultIsAnswer(!0),Rs.updateDisplayText(ot.CALLING),Rs.updateActionConfigs(Cs)),s===rt.MUTE&&(0===r?(Rs.updateIsMuted(!0),ct.emit(it.MUTE_SUCCESS)):ct.emit(it.MUTE_FAILED,n)),s===rt.UNMUTE&&(0===r?(Rs.updateIsMuted(!1),ct.emit(it.UNMUTE_SUCCESS)):ct.emit(it.UNMUTE_FAILED,n)),s===rt.CONSULT_TRANSFER&&(0===r?ct.emit(it.CONSULT_TRANSFER_SUCCESS):ct.emit(it.CONSULT_TRANSFER_FAILED,n)),s===rt.CONSULT_TRANSFER_FAILED&&ct.emit(it.CONSULT_TRANSFER_FAILED,i.status),s===rt.CONSULT_TRANSFER_OFF_HOOK&&(Rs.updateDirection(tt.INCOMING),Rs.updateDisplayText(ot.CALLING),ct.emit(it.CONSULT_TRANSFER_OFF_HOOK),Rs.updateActionConfigs(As),Rs.updateIncomingIsAnswer(!0),Rs.updateConsultIsAnswer(!1)),s===rt.INCOMING_CALL&&(_s.sessionId=i.session_id,Rs.updateIsCalling(!0),Rs.updateDirection(tt.INCOMING),Rs.updateDisplayText(ot.INCOMING_CALL),ct.emit(it.INCOMING_CALL,{customer_phone:i.customer_phone}),Rs.updateActionConfigs(Es),_s.enableBrowserAlert&&Li.show("📞 客户来电",`客户 [${i.customer_phone}] 正在呼入...`,()=>{ct.emit(nt.NOTIFICATION_CLICK,{customer_phone:i.customer_phone})})),s===rt.INCOMING_CALL_OFF_HOOK&&(Rs.updateIncomingIsAnswer(!0),ct.emit(it.INCOMING_CALL_OFF_HOOK),Rs.updateActionConfigs(As),Rs.updateDisplayText(ot.CALLING)),s===rt.INCOMING_CALL_END&&1===i.device_type&&(ct.emit(it.INCOMING_CALL_END),Rs.reset(),Rs.updateActionConfigs(ws),this.putAgentState()),0!==s&&qi.emit("ws-message",t)},this.ws.onclose=()=>{this.stopHeartbeat(),this.manualClose||setTimeout(()=>this.initWebSocket(),this.reconnectDelay)},this.ws.onerror=()=>{this.ws?.close()}}startHeartbeat(){const e=new Blob(["\n let timer;\n self.onmessage = function (e) {\n if (e.data === 'start') {\n timer = setInterval(() => {\n self.postMessage('heartbeat');\n }, 14000);\n } else if (e.data === 'stop') {\n clearInterval(timer);\n }\n };\n "],{type:"application/javascript"}),t=URL.createObjectURL(e);this.heartbeatWorker=new Worker(t),this.heartbeatWorker.onmessage=()=>{this.sendMessage({type:0})},this.heartbeatWorker.postMessage("start")}stopHeartbeat(){this.heartbeatWorker&&(this.heartbeatWorker.postMessage("stop"),this.heartbeatWorker.terminate(),this.heartbeatWorker=null)}sendMessage(e){this.ws?.readyState===WebSocket.OPEN?this.ws.send(JSON.stringify(e)):console.warn("WebSocket 未连接,消息未发送")}close(){this.manualClose=!0,this.stopHeartbeat(),this.ws?.close()}static __internalClose(){Ui&&(Ui.close(),Ui=null)}static __internalSend(e){if(!Ui)throw new Error("WebSocket 尚未初始化");Ui.sendMessage(e)}}class Fi{#e=null;#t=new Pi;#s=null;#i=!1;#r;constructor(){clearInterval(this.#s),this.#s=setInterval(async()=>{const e=await this.#n();Ai.updateRttObject(e)},2e3),Li.requestMediaPermissions(),window.addEventListener("beforeunload",()=>{this.destroy()}),this.#r=Rs.subscribeKey("answerDevice",e=>{1===e&&this.initWebRtc()})}async initWebRtc(){const e=_s.agentInfo.sip_account_list.find(e=>1===e.device_type);if(e){const t=e.sip_register_addr;this.#e=new _i({server:t.split(":")[0],user:e.sip_account,password:e.sip_password,webSocket:`wss://${t}`,callTimeout:3e4},this.#o),await this.#e.start()}}async init({actionNodeParams:e,url:t,appKey:s,appSecret:i,agentNo:r,enableBrowserAlert:n}){try{const{viewHtmlElement:o,rttHTML:a}=e;Ti=t||"https://aicc-api.yescloudy.com",Si=s,Ci=i;const c=oi(s,i);await this.#t._agentLogin(r);const{agent_no:h,current_answer_device:d,init_state:l}=_s.agentInfo;l===et.IDLE?(Rs.updateActionConfigs(ws),_s.stateObject={state:l,state_name:"空闲"}):l===et.BUSY&&(Rs.updateActionConfigs(ys),_s.stateObject={state:l,state_name:"忙碌"}),Mi.getInstance(`ws://112.90.183.54:8719/api/v1/ws/call/${h}?Authorization=${c}`),Rs.updateAnswerDevice(d),1===d&&await this.initWebRtc(),e&&o&&(this.#i=!0,new Oi({container:o,rttHTML:a,statusParams:{state:et.IDLE,statusName:"空闲"}},this)),_s.enableBrowserAlert=n,n&&Li.requestPermission()}catch(e){this.#i&&vi("初始化失败"),console.log(e)}}#o=e=>{const{type:t,data:s}=e;ct.emit(t,s)};#a=e=>{};on(e,t){return ct.on(e,t),this}off(e,t){ct.off(e,t)}offAll(){ct.clearAllListeners()}#c(){if(!this.#e)throw new Error("请先初始化SDK");const e=Rs.get("isCalling"),t=_s.sessionId;if(!e)throw new Error("当前没有通话");if(!t)throw new Error("sessionId不存在")}#h(){const e=Rs.get("isHold"),{sessionId:t}=_s;if(t){if(e)throw new Error("当前已处于保持状态");Mi.__internalSend({type:rt.CALL_HOLD,data:{session_id:t}}),Rs.updateDisplayText(ot.HOLD),Rs.updateIsHold(!0)}}#d(){const{isHold:e}=Rs.getState(),{sessionId:t}=_s;if(t){if(!e)throw new Error("当前未处于保持状态");Mi.__internalSend({type:rt.CALL_UNHOLD,data:{session_id:t}}),Rs.updateDisplayText(""),Rs.updateIsHold(!1)}}#l(e){if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:t}=Rs.getState();if(!t)throw new Error("当前没有通话");this.#e.sendDTMF(e)}#g(){if(1!==Rs.get("answerDevice"))throw new Error("当前设备不支持此功能");if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:e,direction:t,outCallIsAnswer:s}=Rs.getState();if(!e)throw new Error("当前没有通话");s||(t===tt.INCOMING?this.#u():t===tt.OUTGOING&&this.#p())}async#f(e){if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:t,outCallIsAnswer:s,consultIsAnswer:i,incomingIsAnswer:r}=Rs.getState(),n=_s.sessionId;if(i)throw new Error("当前正在咨询");if(!(t||s||r))throw new Error("当前没有通话");if(!n)throw new Error("sessionId不存在");Mi.__internalSend({type:rt.CONSULT_REQUEST,data:{session_id:n,consultation_agent_no:e}})}async#m(){if(1!==Rs.get("answerDevice"))throw new Error("当前设备不支持此功能");if(!this.#e)throw new Error("请先初始化SDK");await this.#e.answerCall()}async#u(){if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:e}=Rs.getState();if(!e)throw new Error("当前没有通话");await this.#e.rejectInCall()}async#v(e){if(!this.#e)throw new Error("请先初始化SDK");if(Rs.getState().isCalling)throw new Error("正在呼叫中,请勿重复点击");Rs.updateIsCalling(!0),Rs.updateDirection(tt.OUTGOING),Mi.__internalSend({type:rt.OUT_CALL,data:{customer_phone:e}})}async#p(){const{isCalling:e}=Rs.getState();if(!this.#e)throw new Error("请先初始化SDK");if(!e)throw new Error("当前没有通话");await this.#e.rejectOutCall()}async#w(){if(!this.#e)throw new Error("请先初始化SDK");if(!Rs.get("isCalling"))throw new Error("当前没有通话");await this.#e.hangup()}#y(){this.#c();const e=Rs.get("actionConfigs"),t=Rs.get("isMuted"),s=_s.sessionId;if(t)throw new Error("当前未处于静音状态");if(!e.includes("mute"))throw new Error("当前状态请勿调用静音");Mi.__internalSend({type:rt.MUTE,data:{session_id:s}})}#b(){this.#c();const e=Rs.get("actionConfigs"),t=Rs.get("isMuted"),s=_s.sessionId;if(!t)throw new Error("当前未处于未静音状态");if(!e.includes("unmute"))throw new Error("当前状态请勿调用静音");Mi.__internalSend({type:rt.UNMUTE,data:{session_id:s}})}#T(){this.#c();const e=Rs.get("consultIsAnswer"),t=_s.sessionId;if(!Rs.get("actionConfigs").includes("consult_transfer"))throw new Error("当前状态请勿调用咨询转移");if(!e)throw new Error("当前未处于咨询中");Mi.__internalSend({type:rt.CONSULT_TRANSFER,data:{session_id:t}})}async destroyRtc(){await(this.#e?.destroy())}destroy(){this.destroyRtc(),clearInterval(this.#s),qi.off("ws-message",this.#a),Mi.__internalClose(),this.offAll(),this.#r?.(),this.#e=null,this.#t=null,this.#i=!1,console.log(1111)}async#n(){return this.#e?this.#e.getNetworkStats():{}}get call_api(){return this.#i?{answerCall:this.#m.bind(this),makeCall:this.#v.bind(this),cancelCall:this.#g.bind(this),hangup:this.#w.bind(this),sendDTMF:this.#l.bind(this),holdCall:this.#h.bind(this),unholdCall:this.#d.bind(this),consultCall:async e=>{try{await this.#f(e)}catch(e){throw console.warn(e),e}},consultTransfer:this.#T.bind(this),mute:this.#y.bind(this),unmute:this.#b.bind(this)}:{}}get agent_api(){return this.#i?{changeDevice:this.#t.changeDevice.bind(this.#t),changeState:this.#t.changeState.bind(this.#t),getIdleAgentList:e=>this.#t.getIdleAgentList(e),getAgentState:e=>this.#t.getAgentState(e)}:{}}}export{Fi as default};
@@ -114,7 +114,7 @@ const ai=e=>(t,s)=>{void 0!==s?s.addInitializer(()=>{customElements.define(e,t)}
114
114
  border: 1px solid #595959;
115
115
  color: #595959;
116
116
  }
117
- `}connectedCallback(){super.connectedCallback(),setTimeout(()=>{this.remove()},this.duration+300)}render(){return Jt`<div>${this.content}</div>`}};oi([di({type:String})],gi.prototype,"type",void 0),oi([di({type:String})],gi.prototype,"content",void 0),oi([di({type:Number})],gi.prototype,"duration",void 0),gi=oi([ai("my-message")],gi);const pi=(e,t,s=3e3)=>{let i=document.querySelector("#message-container");i||(i=document.createElement("div"),i.id="message-container",Object.assign(i.style,{position:"fixed",top:"14px",left:"50%",transform:"translateX(-50%)",zIndex:"9999"}),document.body.appendChild(i));const r=document.createElement("my-message");r.setAttribute("type",e),r.setAttribute("content",t),r.setAttribute("duration",String(s)),r.style.setProperty("--fade-delay",s/1e3+"s"),i.appendChild(r)},fi=(e,t=3e3)=>pi("success",e,t),mi=(e,t=3e3)=>pi("error",e,t),vi=(e,t=3e3)=>pi("warning",e,t),wi="/v1/aicc/bmserver",yi="/v1/aicc/ccs";let bi="",Ti="",Si="";async function Ci(e,t){const s=function(){const e=ni(Ti,Si);return ti.create({prefixUrl:`${bi}/api`,timeout:!1,headers:{Authorization:e},hooks:{afterResponse:[async(e,t,s)=>{const i=await s.clone().json();if(0!==i.code)throw mi(i.msg),new Error(JSON.stringify(i)||"请求失败");return s.json=async()=>i,s}]}})}(),i=e.replace(/^\/+/,"");return s.post(i,{json:t}).json()}const Ei=new class extends Qe{updateRttObject(e){this.setState({rttObject:e})}}({rttObject:{rtt:null,jitter:null,packetsLost:null,packetsReceived:null,packetsSent:null,sendBitrate:null,recvBitrate:null,codec:null}});class Ai{constructor(e,t){this.config=e,this.eventCallback=t,this.userAgent=null,this.registerer=null,this.activeSession=null,this.callTimeoutTimer=null,this.incomingInvitation=null,this.reconnectAttempts=0,this.maxReconnectAttempts=5,this.reconnectTimer=null,this.isOffline=!1,this.autoAnswerTimer=null,this.handleMessage=e=>{},this.handleNetworkInfoChange=e=>{if(e.rttObject?.rtt){0===(e.rttObject?.rtt||0)?this.isOffline||(this.isOffline=!0,this.handleOffline()):this.isOffline&&(this.isOffline=!1,this.handleOnline())}},this.handleOffline=()=>{console.warn("[SIPClient] 网络断开,准备销毁 SIP 客户端",(new Date).toLocaleString()),this.destroy()},this.handleOnline=()=>{console.warn("[SIPClient] 网络恢复,准备重新启动 SIP 客户端",(new Date).toLocaleString()),this.reconnect()},Ei.subscribe(this.handleNetworkInfoChange)}async start(){const e=Xe.makeURI(`sip:${this.config.user}@${this.config.server}`);if(!e)throw new Error("无效的SIP配置");const t={uri:e,authorizationUsername:this.config.user,authorizationPassword:this.config.password,transportOptions:{server:this.config.webSocket,connectionTimeout:30,keepAliveInterval:0},sessionDescriptionHandlerFactoryOptions:{alwaysAcquireMediaFirst:!0,peerConnectionConfiguration:{iceServers:[]},iceGatheringTimeout:400},logBuiltinEnabled:!1};this.userAgent=new Xe(t),this.setupEventListeners(),await this.userAgent.start(),await this.register()}setupEventListeners(){this.userAgent&&(this.userAgent.transport.stateChange.addListener(e=>{let t;switch(e){case ee.Connecting:t=st.WEB_RTC_CONNECTING;break;case ee.Connected:t=st.WEB_RTC_CONNECTED,this.reconnectAttempts=0,_s.isRtcReconnecting=!1,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null);break;case ee.Disconnecting:t=st.WEB_RTC_DISCONNECTING;break;case ee.Disconnected:{t=st.WEB_RTC_DISCONNECTED;const e=Rs.get("answerDevice");if(!_s.isRtcReconnecting&&this.reconnectAttempts<this.maxReconnectAttempts&&1===e){this.reconnectAttempts++;const e=1e3*this.reconnectAttempts;console.warn(`SIP WebSocket 断开,第 ${this.reconnectAttempts} 次尝试重连,${e}ms 后重试...`),this.reconnectTimer=setTimeout(()=>{this.reconnect()},e)}else this.reconnectAttempts>=this.maxReconnectAttempts&&console.error("SIP 重连失败:已达到最大重试次数");break}default:t="unknown"}this.eventCallback?.({type:t})}),this.userAgent.transport.stateChange.addListener(e=>{}),this.userAgent.delegate={onInvite:e=>{this.handleIncomingCall(e)}})}async register(){if(this.userAgent){this.registerer=new ne(this.userAgent),this.registerer?.stateChange.addListener(e=>{let t;switch(e){case X.Initial:t="initial";break;case X.Registered:t=st.WEB_RTC_REGISTERED;break;case X.Unregistered:t=st.WEB_RTC_UNREGISTERED;break;case X.Terminated:t=st.WEB_RTC_TERMINATED;break;default:t="unknown"}this.eventCallback?.({type:t})});try{await this.registerer.register()}catch(e){this.eventCallback?.({type:st.WEB_RTC_REGISTER_FAILED,data:e})}}}attachRemoteAudio(e){const t=e.sessionDescriptionHandler;if(t){const e=t.peerConnection;if(!e)return;const s=new MediaStream;e.getReceivers().forEach(e=>{e.track&&"audio"===e.track.kind&&s.addTrack(e.track)});let i=document.getElementById("sip-remote-audio");i||(i=document.createElement("audio"),i.id="sip-remote-audio",i.autoplay=!0,i.style.display="none",document.body.appendChild(i)),i.srcObject=s,i.play().catch(e=>{console.error("音频播放失败,需要用户交互触发",e)})}}handleSessionState(e,t,s){e.stateChange.addListener(t=>{switch(t){case K.Established:clearTimeout(this.callTimeoutTimer),this.activeSession=e,this.attachRemoteAudio(e);break;case K.Terminating:break;case K.Terminated:this.activeSession=null,clearTimeout(this.callTimeoutTimer);case K.Establishing:}})}handleIncomingCall(e){this.incomingInvitation=e,this.handleSessionState(e,!1);const{soft_device_auto_answer:t,auto_answer_time:s}=_s.agentInfo,i=Rs.get("direction"),r=1===t;if(i===tt.OUTGOING)this.answerCall();else if(r&&i===tt.INCOMING){const e=Number(s)||0;e>0?this.autoAnswerTimer=setTimeout(()=>{this.answerCall(),this.autoAnswerTimer=null},e):this.answerCall()}}async makeCall(e){if(Rs.getState().isCalling)throw new Error("正在呼叫中,请勿重复点击");if(!this.userAgent)throw Rs.updateIsCalling(!1),Rs.updateDirection(null),new Error("SIP客户端未初始化");Rs.updateIsCalling(!0),Rs.updateDirection(tt.OUTGOING);const t=Xe.makeURI(`sip:${e}@${this.config.server}`);if(!t)throw new Error("无效的被叫号码");const s=new Z(this.userAgent,t);this.handleSessionState(s,!0,e);try{this.activeSession=s,await s.invite(),this.config.callTimeout&&(this.callTimeoutTimer=setTimeout(()=>{s.state!==K.Established&&(this.hangup(),this.eventCallback?.({type:"error",data:{message:"呼叫超时未接通"}}))},this.config.callTimeout))}catch(e){this.eventCallback?.({type:"error",data:{message:"呼叫失败",detail:e}}),Rs.updateIsCalling(!1),Rs.updateDirection(null)}}async answerCall(){if(!this.incomingInvitation)throw new Error("无来电可接听");this.autoAnswerTimer&&(clearTimeout(this.autoAnswerTimer),this.autoAnswerTimer=null);try{console.log((new Date).toLocaleString()),await this.incomingInvitation.accept(),console.log((new Date).toLocaleString()),this.activeSession=this.incomingInvitation,this.incomingInvitation=null}catch(e){this.eventCallback?.({type:st.WEB_RTC_ANSWER_FAILED,data:{detail:e}})}}async rejectInCall(){if(this.incomingInvitation)try{await this.incomingInvitation.reject(),this.incomingInvitation=null,Rs.updateDirection(null),Rs.updateIsCalling(!1),clearTimeout(this.callTimeoutTimer)}catch(e){this.eventCallback?.({type:"error",data:{message:"拒接失败",detail:e}})}}sendDTMF(e){if(!this.activeSession)throw new Error("当前没有活跃的通话");const t=this.activeSession.sessionDescriptionHandler;t&&"function"==typeof t.sendDtmf?(t.sendDtmf(e),this.eventCallback?.({type:st.WEB_RTC_SEND_DTMF,data:{tone:e}})):console.warn("DTMF发送不支持或未初始化")}async hangup(){if(this.activeSession){clearTimeout(this.callTimeoutTimer);const e=this.activeSession;if(!e)return;const t=e.state;try{t===K.Established?await e.bye():t===K.Establishing&&(e instanceof Z?await e.cancel():e instanceof J&&await e.reject()),clearTimeout(this.callTimeoutTimer)}catch(e){console.error("挂断失败",e)}finally{this.activeSession=null}}}async rejectOutCall(){this.activeSession instanceof J&&(await this.activeSession.reject(),this.activeSession=null)}async destroy(){if(clearTimeout(this.callTimeoutTimer),this.activeSession&&await this.hangup(),this.registerer){try{await this.registerer.unregister()}catch(e){console.warn("注销失败",e)}this.registerer=null}this.userAgent&&(await this.userAgent.stop(),this.userAgent=null)}async reconnect(){if(!_s.isRtcReconnecting){_s.isRtcReconnecting=!0;try{await this.destroy(),await this.start(),console.log("重连成功"),this.isOffline=!1}catch(e){console.error("重连失败",e)}finally{_s.isRtcReconnecting=!1}}}async getNetworkStats(){if(this.activeSession){const e=this.activeSession.sessionDescriptionHandler?.peerConnection;if(!e)return null;const t=await e.getStats(),s={};return t.forEach(e=>{"candidate-pair"===e.type&&"succeeded"===e.state&&null!=e.currentRoundTripTime&&(s.rtt=+(1e3*e.currentRoundTripTime).toFixed(2)),"inbound-rtp"===e.type&&"audio"===e.kind&&(s.jitter=+(1e3*e.jitter).toFixed(2),s.packetsLost=e.packetsLost,s.packetsReceived=e.packetsReceived,e.bytesReceived&&e.timestamp&&(s.recvBitrate=+(e.bytesReceived/1024).toFixed(2))),"outbound-rtp"===e.type&&"audio"===e.kind&&(s.packetsSent=e.packetsSent,e.bytesSent&&e.timestamp&&(s.sendBitrate=+(e.bytesSent/1024).toFixed(2))),"codec"===e.type&&e.mimeType&&(s.codec=e.mimeType)}),{...s,rtt:s?.rtt||40}}try{const e=Date.now();await Ci(`${wi}/config/ping`,{});const t=Date.now();return{rtt:t-e||1}}catch(e){console.log(e)}}}let _i=class extends fs{static{this.styles=pt`
117
+ `}connectedCallback(){super.connectedCallback(),setTimeout(()=>{this.remove()},this.duration+300)}render(){return Jt`<div>${this.content}</div>`}};oi([di({type:String})],gi.prototype,"type",void 0),oi([di({type:String})],gi.prototype,"content",void 0),oi([di({type:Number})],gi.prototype,"duration",void 0),gi=oi([ai("my-message")],gi);const pi=(e,t,s=3e3)=>{let i=document.querySelector("#message-container");i||(i=document.createElement("div"),i.id="message-container",Object.assign(i.style,{position:"fixed",top:"14px",left:"50%",transform:"translateX(-50%)",zIndex:"9999"}),document.body.appendChild(i));const r=document.createElement("my-message");r.setAttribute("type",e),r.setAttribute("content",t),r.setAttribute("duration",String(s)),r.style.setProperty("--fade-delay",s/1e3+"s"),i.appendChild(r)},fi=(e,t=3e3)=>pi("success",e,t),mi=(e,t=3e3)=>pi("error",e,t),vi=(e,t=3e3)=>pi("warning",e,t),wi="/v1/aicc/bmserver",yi="/v1/aicc/ccs";let bi="",Ti="",Si="";async function Ci(e,t){const s=function(){const e=ni(Ti,Si);return ti.create({prefixUrl:`${bi}/api`,timeout:!1,headers:{Authorization:e},hooks:{afterResponse:[async(e,t,s)=>{const i=await s.clone().json();if(0!==i.code)throw mi(i.msg),new Error(JSON.stringify(i)||"请求失败");return s.json=async()=>i,s}]}})}(),i=e.replace(/^\/+/,"");return s.post(i,{json:t}).json()}const Ei=new class extends Qe{updateRttObject(e){this.setState({rttObject:e})}}({rttObject:{rtt:null,jitter:null,packetsLost:null,packetsReceived:null,packetsSent:null,sendBitrate:null,recvBitrate:null,codec:null}});class Ai{constructor(e,t){this.config=e,this.eventCallback=t,this.userAgent=null,this.registerer=null,this.activeSession=null,this.callTimeoutTimer=null,this.incomingInvitation=null,this.reconnectAttempts=0,this.maxReconnectAttempts=5,this.reconnectTimer=null,this.isOffline=!1,this.autoAnswerTimer=null,this.isManuallyStopped=!1,this.handleMessage=e=>{},this.handleNetworkInfoChange=e=>{if(e.rttObject?.rtt){0===(e.rttObject?.rtt||0)?this.isOffline||(this.isOffline=!0,this.handleOffline()):this.isOffline&&(this.isOffline=!1,this.handleOnline())}},this.handleOffline=()=>{console.warn("[SIPClient] 网络断开,准备销毁 SIP 客户端",(new Date).toLocaleString()),this.destroy()},this.handleOnline=()=>{console.warn("[SIPClient] 网络恢复,准备重新启动 SIP 客户端",(new Date).toLocaleString()),this.reconnect()},Ei.subscribe(this.handleNetworkInfoChange)}async start(){const e=Xe.makeURI(`sip:${this.config.user}@${this.config.server}`);if(!e)throw new Error("无效的SIP配置");const t={uri:e,authorizationUsername:this.config.user,authorizationPassword:this.config.password,transportOptions:{server:this.config.webSocket,connectionTimeout:30,keepAliveInterval:0},sessionDescriptionHandlerFactoryOptions:{alwaysAcquireMediaFirst:!0,peerConnectionConfiguration:{iceServers:[]},iceGatheringTimeout:400},logBuiltinEnabled:!1};this.userAgent=new Xe(t),this.setupEventListeners(),await this.userAgent.start(),await this.register()}setupEventListeners(){this.userAgent&&(this.userAgent.transport.stateChange.addListener(e=>{let t;switch(e){case ee.Connecting:t=st.WEB_RTC_CONNECTING;break;case ee.Connected:t=st.WEB_RTC_CONNECTED,this.reconnectAttempts=0,_s.isRtcReconnecting=!1,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null);break;case ee.Disconnecting:t=st.WEB_RTC_DISCONNECTING;break;case ee.Disconnected:{if(this.isManuallyStopped)return void console.warn("[SIP] 手动断开,不触发重连");t=st.WEB_RTC_DISCONNECTED;const e=Rs.get("answerDevice");if(!_s.isRtcReconnecting&&this.reconnectAttempts<this.maxReconnectAttempts&&1===e){this.reconnectAttempts++;const e=1e3*this.reconnectAttempts;console.warn(`SIP WebSocket 断开,第 ${this.reconnectAttempts} 次尝试重连,${e}ms 后重试...`),this.reconnectTimer=setTimeout(()=>{this.reconnect()},e)}else this.reconnectAttempts>=this.maxReconnectAttempts&&console.error("SIP 重连失败:已达到最大重试次数");break}default:t="unknown"}this.eventCallback?.({type:t})}),this.userAgent.transport.stateChange.addListener(e=>{}),this.userAgent.delegate={onInvite:e=>{this.handleIncomingCall(e)}})}async register(){if(this.userAgent){this.registerer=new ne(this.userAgent),this.registerer?.stateChange.addListener(e=>{let t;switch(e){case X.Initial:t="initial";break;case X.Registered:t=st.WEB_RTC_REGISTERED;break;case X.Unregistered:t=st.WEB_RTC_UNREGISTERED;break;case X.Terminated:t=st.WEB_RTC_TERMINATED;break;default:t="unknown"}this.eventCallback?.({type:t})});try{await this.registerer.register()}catch(e){this.eventCallback?.({type:st.WEB_RTC_REGISTER_FAILED,data:e})}}}attachRemoteAudio(e){const t=e.sessionDescriptionHandler;if(t){const e=t.peerConnection;if(!e)return;const s=new MediaStream;e.getReceivers().forEach(e=>{e.track&&"audio"===e.track.kind&&s.addTrack(e.track)});let i=document.getElementById("sip-remote-audio");i||(i=document.createElement("audio"),i.id="sip-remote-audio",i.autoplay=!0,i.style.display="none",document.body.appendChild(i)),i.srcObject=s,i.play().catch(e=>{console.error("音频播放失败,需要用户交互触发",e)})}}handleSessionState(e,t,s){e.stateChange.addListener(t=>{switch(t){case K.Established:clearTimeout(this.callTimeoutTimer),this.activeSession=e,this.attachRemoteAudio(e);break;case K.Terminating:break;case K.Terminated:this.activeSession=null,clearTimeout(this.callTimeoutTimer);case K.Establishing:}})}handleIncomingCall(e){this.incomingInvitation=e,this.handleSessionState(e,!1);const{soft_device_auto_answer:t,auto_answer_time:s}=_s.agentInfo,i=Rs.get("direction"),r=1===t;if(i===tt.OUTGOING)this.answerCall();else if(r&&i===tt.INCOMING){const e=Number(s)||0;e>0?this.autoAnswerTimer=setTimeout(()=>{this.answerCall(),this.autoAnswerTimer=null},e):this.answerCall()}}async makeCall(e){if(Rs.getState().isCalling)throw new Error("正在呼叫中,请勿重复点击");if(!this.userAgent)throw Rs.updateIsCalling(!1),Rs.updateDirection(null),new Error("SIP客户端未初始化");Rs.updateIsCalling(!0),Rs.updateDirection(tt.OUTGOING);const t=Xe.makeURI(`sip:${e}@${this.config.server}`);if(!t)throw new Error("无效的被叫号码");const s=new Z(this.userAgent,t);this.handleSessionState(s,!0,e);try{this.activeSession=s,await s.invite(),this.config.callTimeout&&(this.callTimeoutTimer=setTimeout(()=>{s.state!==K.Established&&(this.hangup(),this.eventCallback?.({type:"error",data:{message:"呼叫超时未接通"}}))},this.config.callTimeout))}catch(e){this.eventCallback?.({type:"error",data:{message:"呼叫失败",detail:e}}),Rs.updateIsCalling(!1),Rs.updateDirection(null)}}async answerCall(){if(!this.incomingInvitation)throw new Error("无来电可接听");this.autoAnswerTimer&&(clearTimeout(this.autoAnswerTimer),this.autoAnswerTimer=null);try{console.log((new Date).toLocaleString()),await this.incomingInvitation.accept(),console.log((new Date).toLocaleString()),this.activeSession=this.incomingInvitation,this.incomingInvitation=null}catch(e){this.eventCallback?.({type:st.WEB_RTC_ANSWER_FAILED,data:{detail:e}})}}async rejectInCall(){if(this.incomingInvitation)try{await this.incomingInvitation.reject(),this.incomingInvitation=null,Rs.updateDirection(null),Rs.updateIsCalling(!1),clearTimeout(this.callTimeoutTimer)}catch(e){this.eventCallback?.({type:"error",data:{message:"拒接失败",detail:e}})}}sendDTMF(e){if(!this.activeSession)throw new Error("当前没有活跃的通话");const t=this.activeSession.sessionDescriptionHandler;t&&"function"==typeof t.sendDtmf?(t.sendDtmf(e),this.eventCallback?.({type:st.WEB_RTC_SEND_DTMF,data:{tone:e}})):console.warn("DTMF发送不支持或未初始化")}async hangup(){if(this.activeSession){clearTimeout(this.callTimeoutTimer);const e=this.activeSession;if(!e)return;const t=e.state;try{t===K.Established?await e.bye():t===K.Establishing&&(e instanceof Z?await e.cancel():e instanceof J&&await e.reject()),clearTimeout(this.callTimeoutTimer)}catch(e){console.error("挂断失败",e)}finally{this.activeSession=null}}}async rejectOutCall(){this.activeSession instanceof J&&(await this.activeSession.reject(),this.activeSession=null)}async destroy(){if(this.isManuallyStopped=!0,clearTimeout(this.callTimeoutTimer),this.activeSession&&await this.hangup(),this.registerer){try{await(this.userAgent?.stop()),await this.registerer.unregister()}catch(e){console.warn("注销失败",e)}this.registerer=null}this.userAgent&&(await this.userAgent.stop(),this.userAgent=null)}async reconnect(){if(!_s.isRtcReconnecting){_s.isRtcReconnecting=!0;try{await this.destroy(),await this.start(),console.log("重连成功"),this.isOffline=!1}catch(e){console.error("重连失败",e)}finally{_s.isRtcReconnecting=!1}}}async getNetworkStats(){if(this.activeSession){const e=this.activeSession.sessionDescriptionHandler?.peerConnection;if(!e)return null;const t=await e.getStats(),s={};return t.forEach(e=>{"candidate-pair"===e.type&&"succeeded"===e.state&&null!=e.currentRoundTripTime&&(s.rtt=+(1e3*e.currentRoundTripTime).toFixed(2)),"inbound-rtp"===e.type&&"audio"===e.kind&&(s.jitter=+(1e3*e.jitter).toFixed(2),s.packetsLost=e.packetsLost,s.packetsReceived=e.packetsReceived,e.bytesReceived&&e.timestamp&&(s.recvBitrate=+(e.bytesReceived/1024).toFixed(2))),"outbound-rtp"===e.type&&"audio"===e.kind&&(s.packetsSent=e.packetsSent,e.bytesSent&&e.timestamp&&(s.sendBitrate=+(e.bytesSent/1024).toFixed(2))),"codec"===e.type&&e.mimeType&&(s.codec=e.mimeType)}),{...s,rtt:s?.rtt||40}}try{const e=Date.now();await Ci(`${wi}/config/ping`,{});const t=Date.now();return{rtt:t-e||1}}catch(e){console.log(e)}}}let _i=class extends fs{static{this.styles=pt`
118
118
  .timer-text {
119
119
  width: 70px;
120
120
  font-size: 14px;
@@ -808,4 +808,4 @@ const ai=e=>(t,s)=>{void 0!==s?s.addInitializer(()=>{customElements.define(e,t)}
808
808
  `:""}
809
809
  </div>
810
810
  </my-popover>
811
- `;gs(i,this.rttHTML)}}const Oi=new class{constructor(){this.events={}}on(e,t){this.events[e]=this.events[e]||[],this.events[e].push(t)}off(e,t){this.events[e]=(this.events[e]||[]).filter(e=>e!==t)}emit(e,...t){(this.events[e]||[]).forEach(e=>e(...t))}};class qi{static requestPermission(){"Notification"in window&&"default"===Notification.permission&&Notification.requestPermission()}static async requestMediaPermissions(){try{const e=await navigator.mediaDevices.enumerateDevices();if(e.some(e=>"audioinput"===e.kind&&""!==e.label))return console.log("已授权麦克风权限,无需再次获取"),null;const t=await navigator.mediaDevices.getUserMedia({audio:!0});return console.log("获取音频权限成功"),t}catch(e){return console.warn("获取音频权限失败:",e),null}}static show(e,t,s){if("Notification"in window&&"granted"===Notification.permission){const i=new Notification(e,{body:t,tag:"xy-global-notification",renotify:!0});i.onclick=()=>{window.focus(),i.close(),s?.()},setTimeout(()=>i.close(),5e3)}}static async checkMediaPermissions(){try{return(await navigator.mediaDevices.enumerateDevices()).some(e=>"audioinput"===e.kind&&""!==e.label)}catch(e){return console.warn("无法检查设备权限:",e),!1}}}let Li=null;class Ui{constructor(e){this.url=e,this.ws=null,this.heartbeatWorker=null,this.reconnectDelay=3e3,this.manualClose=!1,this.apiClient=new Hi,this.initWebSocket()}static getInstance(e){if(!Li){if(!e)throw new Error("WebSocket 尚未初始化");Li=new Ui(e)}return Li}stateIdleChange(){const{state:e}=_s.stateObject;e!==et.IDLE&&this.apiClient.changeState({data:{state:et.IDLE,state_name:"空闲"}})}putAgentState(){_s.sessionId="";const{post_call_process_time:e}=_s.agentInfo,t=e||0;_s.autoStateTimer&&(clearTimeout(_s.autoStateTimer),_s.autoStateTimer=null),t>0?_s.autoStateTimer=setTimeout(()=>{_s.autoStateTimer=null,this.stateIdleChange()},t):this.stateIdleChange()}initWebSocket(){this.ws=new WebSocket(this.url),this.ws.onopen=()=>{this.sendMessage({type:0}),this.startHeartbeat()},this.ws.onmessage=e=>{const t=JSON.parse(e.data),{type:s,data:i,code:r,msg:n}=t;if(0!==s&&console.warn(s),s===rt.OUT_CALL&&0!==r&&(ct.emit(it.OUT_FAILED,n),this.stateIdleChange()),s===rt.AGENT_STATE){const{state:e,state_name:t}=i;_s.stateObject={state:e,state_name:t},"振铃中"===t&&Rs.updateDisplayText(ot.AGENT_RINGING),ct.emit(it.AGENT_STATE,{state:e,state_name:t})}s===rt.OUT_CALL_INCOMING_CALL&&(ct.emit(it.OUT_INCOMING_CALL,i),_s.sessionId=i.session_id,Rs.updateActionConfigs(bs)),s===rt.OUT_CALL_END&&(1===i.device_type?(this.putAgentState(),Rs.reset(),ct.emit(it.OUT_HANGUP,i)):2===i.device_type&&(Rs.get("direction")===tt.CONSULT?(Rs.reset(),ct.emit(it.OUR_SIDE_CONSULT_HANGUP,i),this.putAgentState()):Rs.get("isCalling")&&(Rs.updateDisplayText(ot.CALLING),ct.emit(it.OTHER_SIDE_CONSULT_HANGUP,i),Rs.updateActionConfigs(Ts)))),s===rt.OUT_CALL_RINGING&&(ct.emit(it.OUT_RINGING,i),Rs.updateDisplayText(ot.RINGING)),s===rt.OUT_CALL_ANSWER&&(ct.emit(it.OUT_OFF_ANSWER,i),Rs.updateOutCallIsAnswer(!0),Rs.updateDisplayText(ot.CALLING),Rs.updateActionConfigs(Ts)),s===rt.CONSULT_RINGING&&(ct.emit(it.CONSULT_RINGING,i),Rs.updateDisplayText(ot.AGENT_RINGING),Rs.updateActionConfigs([])),s===rt.CONSULT_ANSWER&&(ct.emit(it.CONSULT_OFF_HOOK,i),Rs.updateConsultIsAnswer(!0),Rs.updateDisplayText(ot.CONSULTING),Rs.updateActionConfigs(Ss)),s===rt.CONSULT_FAIL&&(ct.emit(it.CONSULT_FAILED),Rs.updateDisplayText(ot.CALLING),Rs.updateActionConfigs(Ts)),s===rt.CONSULT_CALL_IN&&(ct.emit(it.CONSULT_INCOMING,i),Rs.updateIsCalling(!0),Rs.updateDirection(tt.CONSULT),Rs.updateDisplayText(ot.CONSULT_CALL_IN),Rs.updateActionConfigs(Es),_s.enableBrowserAlert&&qi.show("📞 咨询来电",`坐席 [${i.agent_name}] 正在咨询...`,()=>{ct.emit(nt.NOTIFICATION_CLICK,{agent_mane:i.agent_name})})),s===rt.CONSULT_CALL_IN_SUCCESS&&(_s.sessionId=i.session_id,ct.emit(it.CONSULT_CALL_IN_SUCCESS,i),Rs.updateConsultIsAnswer(!0),Rs.updateDisplayText(ot.CALLING),Rs.updateActionConfigs(Cs)),s===rt.MUTE&&(0===r?(Rs.updateIsMuted(!0),ct.emit(it.MUTE_SUCCESS)):ct.emit(it.MUTE_FAILED,n)),s===rt.UNMUTE&&(0===r?(Rs.updateIsMuted(!1),ct.emit(it.UNMUTE_SUCCESS)):ct.emit(it.UNMUTE_FAILED,n)),s===rt.CONSULT_TRANSFER&&(0===r?ct.emit(it.CONSULT_TRANSFER_SUCCESS):ct.emit(it.CONSULT_TRANSFER_FAILED,n)),s===rt.CONSULT_TRANSFER_FAILED&&ct.emit(it.CONSULT_TRANSFER_FAILED,i.status),s===rt.CONSULT_TRANSFER_OFF_HOOK&&(Rs.updateDirection(tt.INCOMING),Rs.updateDisplayText(ot.CALLING),ct.emit(it.CONSULT_TRANSFER_OFF_HOOK),Rs.updateActionConfigs(As),Rs.updateIncomingIsAnswer(!0),Rs.updateConsultIsAnswer(!1)),s===rt.INCOMING_CALL&&(_s.sessionId=i.session_id,Rs.updateIsCalling(!0),Rs.updateDirection(tt.INCOMING),Rs.updateDisplayText(ot.INCOMING_CALL),ct.emit(it.INCOMING_CALL,{customer_phone:i.customer_phone}),Rs.updateActionConfigs(Es),_s.enableBrowserAlert&&qi.show("📞 客户来电",`客户 [${i.customer_phone}] 正在呼入...`,()=>{ct.emit(nt.NOTIFICATION_CLICK,{customer_phone:i.customer_phone})})),s===rt.INCOMING_CALL_OFF_HOOK&&(Rs.updateIncomingIsAnswer(!0),ct.emit(it.INCOMING_CALL_OFF_HOOK),Rs.updateActionConfigs(As),Rs.updateDisplayText(ot.CALLING)),s===rt.INCOMING_CALL_END&&1===i.device_type&&(ct.emit(it.INCOMING_CALL_END),Rs.reset(),Rs.updateActionConfigs(ws),this.putAgentState()),0!==s&&Oi.emit("ws-message",t)},this.ws.onclose=()=>{this.stopHeartbeat(),this.manualClose||setTimeout(()=>this.initWebSocket(),this.reconnectDelay)},this.ws.onerror=()=>{this.ws?.close()}}startHeartbeat(){const e=new Blob(["\n let timer;\n self.onmessage = function (e) {\n if (e.data === 'start') {\n timer = setInterval(() => {\n self.postMessage('heartbeat');\n }, 14000);\n } else if (e.data === 'stop') {\n clearInterval(timer);\n }\n };\n "],{type:"application/javascript"}),t=URL.createObjectURL(e);this.heartbeatWorker=new Worker(t),this.heartbeatWorker.onmessage=()=>{this.sendMessage({type:0})},this.heartbeatWorker.postMessage("start")}stopHeartbeat(){this.heartbeatWorker&&(this.heartbeatWorker.postMessage("stop"),this.heartbeatWorker.terminate(),this.heartbeatWorker=null)}sendMessage(e){this.ws?.readyState===WebSocket.OPEN?this.ws.send(JSON.stringify(e)):console.warn("WebSocket 未连接,消息未发送")}close(){this.manualClose=!0,this.stopHeartbeat(),this.ws?.close()}static __internalClose(){Li&&(Li.close(),Li=null)}static __internalSend(e){if(!Li)throw new Error("WebSocket 尚未初始化");Li.sendMessage(e)}}return class{#e=null;#t=new Hi;#s=null;#i=!1;#r;constructor(){clearInterval(this.#s),this.#s=setInterval(async()=>{const e=await this.#n();Ei.updateRttObject(e)},2e3),qi.requestMediaPermissions(),window.addEventListener("beforeunload",()=>{this.destroy()}),this.#r=Rs.subscribeKey("answerDevice",e=>{1===e&&this.initWebRtc()})}async initWebRtc(){const e=_s.agentInfo.sip_account_list.find(e=>1===e.device_type);if(e){const t=e.sip_register_addr;this.#e=new Ai({server:t.split(":")[0],user:e.sip_account,password:e.sip_password,webSocket:`wss://${t}`,callTimeout:3e4},this.#o),await this.#e.start()}}async init({actionNodeParams:e,url:t,appKey:s,appSecret:i,agentNo:r,enableBrowserAlert:n}){try{const{viewHtmlElement:o,rttHTML:a}=e;bi=t||"https://aicc-api.yescloudy.com",Ti=s,Si=i;const c=ni(s,i);await this.#t._agentLogin(r);const{agent_no:h,current_answer_device:d,init_state:l}=_s.agentInfo;l===et.IDLE?(Rs.updateActionConfigs(ws),_s.stateObject={state:l,state_name:"空闲"}):l===et.BUSY&&(Rs.updateActionConfigs(ys),_s.stateObject={state:l,state_name:"忙碌"}),Ui.getInstance(`ws://112.90.183.54:8719/api/v1/ws/call/${h}?Authorization=${c}`),Rs.updateAnswerDevice(d),1===d&&await this.initWebRtc(),e&&o&&(this.#i=!0,new Pi({container:o,rttHTML:a,statusParams:{state:et.IDLE,statusName:"空闲"}},this)),_s.enableBrowserAlert=n,n&&qi.requestPermission()}catch(e){this.#i&&mi("初始化失败"),console.log(e)}}#o=e=>{const{type:t,data:s}=e;ct.emit(t,s)};#a=e=>{};on(e,t){return ct.on(e,t),this}off(e,t){ct.off(e,t)}offAll(){ct.clearAllListeners()}#c(){if(!this.#e)throw new Error("请先初始化SDK");const e=Rs.get("isCalling"),t=_s.sessionId;if(!e)throw new Error("当前没有通话");if(!t)throw new Error("sessionId不存在")}#h(){const e=Rs.get("isHold"),{sessionId:t}=_s;if(t){if(e)throw new Error("当前已处于保持状态");Ui.__internalSend({type:rt.CALL_HOLD,data:{session_id:t}}),Rs.updateDisplayText(ot.HOLD),Rs.updateIsHold(!0)}}#d(){const{isHold:e}=Rs.getState(),{sessionId:t}=_s;if(t){if(!e)throw new Error("当前未处于保持状态");Ui.__internalSend({type:rt.CALL_UNHOLD,data:{session_id:t}}),Rs.updateDisplayText(""),Rs.updateIsHold(!1)}}#l(e){if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:t}=Rs.getState();if(!t)throw new Error("当前没有通话");this.#e.sendDTMF(e)}#u(){if(1!==Rs.get("answerDevice"))throw new Error("当前设备不支持此功能");if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:e,direction:t,outCallIsAnswer:s}=Rs.getState();if(!e)throw new Error("当前没有通话");s||(t===tt.INCOMING?this.#g():t===tt.OUTGOING&&this.#p())}async#f(e){if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:t,outCallIsAnswer:s,consultIsAnswer:i,incomingIsAnswer:r}=Rs.getState(),n=_s.sessionId;if(i)throw new Error("当前正在咨询");if(!(t||s||r))throw new Error("当前没有通话");if(!n)throw new Error("sessionId不存在");Ui.__internalSend({type:rt.CONSULT_REQUEST,data:{session_id:n,consultation_agent_no:e}})}async#m(){if(1!==Rs.get("answerDevice"))throw new Error("当前设备不支持此功能");if(!this.#e)throw new Error("请先初始化SDK");await this.#e.answerCall()}async#g(){if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:e}=Rs.getState();if(!e)throw new Error("当前没有通话");await this.#e.rejectInCall()}async#v(e){if(!this.#e)throw new Error("请先初始化SDK");if(Rs.getState().isCalling)throw new Error("正在呼叫中,请勿重复点击");Rs.updateIsCalling(!0),Rs.updateDirection(tt.OUTGOING),Ui.__internalSend({type:rt.OUT_CALL,data:{customer_phone:e}})}async#p(){const{isCalling:e}=Rs.getState();if(!this.#e)throw new Error("请先初始化SDK");if(!e)throw new Error("当前没有通话");await this.#e.rejectOutCall()}async#w(){if(!this.#e)throw new Error("请先初始化SDK");if(!Rs.get("isCalling"))throw new Error("当前没有通话");await this.#e.hangup()}#y(){this.#c();const e=Rs.get("actionConfigs"),t=Rs.get("isMuted"),s=_s.sessionId;if(t)throw new Error("当前未处于静音状态");if(!e.includes("mute"))throw new Error("当前状态请勿调用静音");Ui.__internalSend({type:rt.MUTE,data:{session_id:s}})}#b(){this.#c();const e=Rs.get("actionConfigs"),t=Rs.get("isMuted"),s=_s.sessionId;if(!t)throw new Error("当前未处于未静音状态");if(!e.includes("unmute"))throw new Error("当前状态请勿调用静音");Ui.__internalSend({type:rt.UNMUTE,data:{session_id:s}})}#T(){this.#c();const e=Rs.get("consultIsAnswer"),t=_s.sessionId;if(!Rs.get("actionConfigs").includes("consult_transfer"))throw new Error("当前状态请勿调用咨询转移");if(!e)throw new Error("当前未处于咨询中");Ui.__internalSend({type:rt.CONSULT_TRANSFER,data:{session_id:t}})}async destroyRtc(){await(this.#e?.destroy())}destroy(){this.destroyRtc(),clearInterval(this.#s),Oi.off("ws-message",this.#a),Ui.__internalClose(),this.offAll(),this.#r?.()}async#n(){return this.#e?this.#e.getNetworkStats():{}}get call_api(){return this.#i?{answerCall:this.#m.bind(this),makeCall:this.#v.bind(this),cancelCall:this.#u.bind(this),hangup:this.#w.bind(this),sendDTMF:this.#l.bind(this),holdCall:this.#h.bind(this),unholdCall:this.#d.bind(this),consultCall:async e=>{try{await this.#f(e)}catch(e){throw console.warn(e),e}},consultTransfer:this.#T.bind(this),mute:this.#y.bind(this),unmute:this.#b.bind(this)}:{}}get agent_api(){return this.#i?{changeDevice:this.#t.changeDevice.bind(this.#t),changeState:this.#t.changeState.bind(this.#t),getIdleAgentList:e=>this.#t.getIdleAgentList(e),getAgentState:e=>this.#t.getAgentState(e)}:{}}}});
811
+ `;gs(i,this.rttHTML)}}const Oi=new class{constructor(){this.events={}}on(e,t){this.events[e]=this.events[e]||[],this.events[e].push(t)}off(e,t){this.events[e]=(this.events[e]||[]).filter(e=>e!==t)}emit(e,...t){(this.events[e]||[]).forEach(e=>e(...t))}};class qi{static requestPermission(){"Notification"in window&&"default"===Notification.permission&&Notification.requestPermission()}static async requestMediaPermissions(){try{const e=await navigator.mediaDevices.enumerateDevices();if(e.some(e=>"audioinput"===e.kind&&""!==e.label))return console.log("已授权麦克风权限,无需再次获取"),null;const t=await navigator.mediaDevices.getUserMedia({audio:!0});return console.log("获取音频权限成功"),t}catch(e){return console.warn("获取音频权限失败:",e),null}}static show(e,t,s){if("Notification"in window&&"granted"===Notification.permission){const i=new Notification(e,{body:t,tag:"xy-global-notification",renotify:!0});i.onclick=()=>{window.focus(),i.close(),s?.()},setTimeout(()=>i.close(),5e3)}}static async checkMediaPermissions(){try{return(await navigator.mediaDevices.enumerateDevices()).some(e=>"audioinput"===e.kind&&""!==e.label)}catch(e){return console.warn("无法检查设备权限:",e),!1}}}let Li=null;class Ui{constructor(e){this.url=e,this.ws=null,this.heartbeatWorker=null,this.reconnectDelay=3e3,this.manualClose=!1,this.apiClient=new Hi,this.initWebSocket()}static getInstance(e){if(!Li){if(!e)throw new Error("WebSocket 尚未初始化");Li=new Ui(e)}return Li}stateIdleChange(){const{state:e}=_s.stateObject;e!==et.IDLE&&this.apiClient.changeState({data:{state:et.IDLE,state_name:"空闲"}})}putAgentState(){_s.sessionId="";const{post_call_process_time:e}=_s.agentInfo,t=e||0;_s.autoStateTimer&&(clearTimeout(_s.autoStateTimer),_s.autoStateTimer=null),t>0?_s.autoStateTimer=setTimeout(()=>{_s.autoStateTimer=null,this.stateIdleChange()},t):this.stateIdleChange()}initWebSocket(){this.ws=new WebSocket(this.url),this.ws.onopen=()=>{this.sendMessage({type:0}),this.startHeartbeat()},this.ws.onmessage=e=>{const t=JSON.parse(e.data),{type:s,data:i,code:r,msg:n}=t;if(0!==s&&console.warn(s),s===rt.OUT_CALL&&0!==r&&(ct.emit(it.OUT_FAILED,n),this.stateIdleChange()),s===rt.AGENT_STATE){const{state:e,state_name:t}=i;_s.stateObject={state:e,state_name:t},"振铃中"===t&&Rs.updateDisplayText(ot.AGENT_RINGING),ct.emit(it.AGENT_STATE,{state:e,state_name:t})}s===rt.OUT_CALL_INCOMING_CALL&&(ct.emit(it.OUT_INCOMING_CALL,i),_s.sessionId=i.session_id,Rs.updateActionConfigs(bs)),s===rt.OUT_CALL_END&&(1===i.device_type?(this.putAgentState(),Rs.reset(),ct.emit(it.OUT_HANGUP,i)):2===i.device_type&&(Rs.get("direction")===tt.CONSULT?(Rs.reset(),ct.emit(it.OUR_SIDE_CONSULT_HANGUP,i),this.putAgentState()):Rs.get("isCalling")&&(Rs.updateDisplayText(ot.CALLING),ct.emit(it.OTHER_SIDE_CONSULT_HANGUP,i),Rs.updateActionConfigs(Ts)))),s===rt.OUT_CALL_RINGING&&(ct.emit(it.OUT_RINGING,i),Rs.updateDisplayText(ot.RINGING)),s===rt.OUT_CALL_ANSWER&&(ct.emit(it.OUT_OFF_ANSWER,i),Rs.updateOutCallIsAnswer(!0),Rs.updateDisplayText(ot.CALLING),Rs.updateActionConfigs(Ts)),s===rt.CONSULT_RINGING&&(ct.emit(it.CONSULT_RINGING,i),Rs.updateDisplayText(ot.AGENT_RINGING),Rs.updateActionConfigs([])),s===rt.CONSULT_ANSWER&&(ct.emit(it.CONSULT_OFF_HOOK,i),Rs.updateConsultIsAnswer(!0),Rs.updateDisplayText(ot.CONSULTING),Rs.updateActionConfigs(Ss)),s===rt.CONSULT_FAIL&&(ct.emit(it.CONSULT_FAILED),Rs.updateDisplayText(ot.CALLING),Rs.updateActionConfigs(Ts)),s===rt.CONSULT_CALL_IN&&(ct.emit(it.CONSULT_INCOMING,i),Rs.updateIsCalling(!0),Rs.updateDirection(tt.CONSULT),Rs.updateDisplayText(ot.CONSULT_CALL_IN),Rs.updateActionConfigs(Es),_s.enableBrowserAlert&&qi.show("📞 咨询来电",`坐席 [${i.agent_name}] 正在咨询...`,()=>{ct.emit(nt.NOTIFICATION_CLICK,{agent_mane:i.agent_name})})),s===rt.CONSULT_CALL_IN_SUCCESS&&(_s.sessionId=i.session_id,ct.emit(it.CONSULT_CALL_IN_SUCCESS,i),Rs.updateConsultIsAnswer(!0),Rs.updateDisplayText(ot.CALLING),Rs.updateActionConfigs(Cs)),s===rt.MUTE&&(0===r?(Rs.updateIsMuted(!0),ct.emit(it.MUTE_SUCCESS)):ct.emit(it.MUTE_FAILED,n)),s===rt.UNMUTE&&(0===r?(Rs.updateIsMuted(!1),ct.emit(it.UNMUTE_SUCCESS)):ct.emit(it.UNMUTE_FAILED,n)),s===rt.CONSULT_TRANSFER&&(0===r?ct.emit(it.CONSULT_TRANSFER_SUCCESS):ct.emit(it.CONSULT_TRANSFER_FAILED,n)),s===rt.CONSULT_TRANSFER_FAILED&&ct.emit(it.CONSULT_TRANSFER_FAILED,i.status),s===rt.CONSULT_TRANSFER_OFF_HOOK&&(Rs.updateDirection(tt.INCOMING),Rs.updateDisplayText(ot.CALLING),ct.emit(it.CONSULT_TRANSFER_OFF_HOOK),Rs.updateActionConfigs(As),Rs.updateIncomingIsAnswer(!0),Rs.updateConsultIsAnswer(!1)),s===rt.INCOMING_CALL&&(_s.sessionId=i.session_id,Rs.updateIsCalling(!0),Rs.updateDirection(tt.INCOMING),Rs.updateDisplayText(ot.INCOMING_CALL),ct.emit(it.INCOMING_CALL,{customer_phone:i.customer_phone}),Rs.updateActionConfigs(Es),_s.enableBrowserAlert&&qi.show("📞 客户来电",`客户 [${i.customer_phone}] 正在呼入...`,()=>{ct.emit(nt.NOTIFICATION_CLICK,{customer_phone:i.customer_phone})})),s===rt.INCOMING_CALL_OFF_HOOK&&(Rs.updateIncomingIsAnswer(!0),ct.emit(it.INCOMING_CALL_OFF_HOOK),Rs.updateActionConfigs(As),Rs.updateDisplayText(ot.CALLING)),s===rt.INCOMING_CALL_END&&1===i.device_type&&(ct.emit(it.INCOMING_CALL_END),Rs.reset(),Rs.updateActionConfigs(ws),this.putAgentState()),0!==s&&Oi.emit("ws-message",t)},this.ws.onclose=()=>{this.stopHeartbeat(),this.manualClose||setTimeout(()=>this.initWebSocket(),this.reconnectDelay)},this.ws.onerror=()=>{this.ws?.close()}}startHeartbeat(){const e=new Blob(["\n let timer;\n self.onmessage = function (e) {\n if (e.data === 'start') {\n timer = setInterval(() => {\n self.postMessage('heartbeat');\n }, 14000);\n } else if (e.data === 'stop') {\n clearInterval(timer);\n }\n };\n "],{type:"application/javascript"}),t=URL.createObjectURL(e);this.heartbeatWorker=new Worker(t),this.heartbeatWorker.onmessage=()=>{this.sendMessage({type:0})},this.heartbeatWorker.postMessage("start")}stopHeartbeat(){this.heartbeatWorker&&(this.heartbeatWorker.postMessage("stop"),this.heartbeatWorker.terminate(),this.heartbeatWorker=null)}sendMessage(e){this.ws?.readyState===WebSocket.OPEN?this.ws.send(JSON.stringify(e)):console.warn("WebSocket 未连接,消息未发送")}close(){this.manualClose=!0,this.stopHeartbeat(),this.ws?.close()}static __internalClose(){Li&&(Li.close(),Li=null)}static __internalSend(e){if(!Li)throw new Error("WebSocket 尚未初始化");Li.sendMessage(e)}}return class{#e=null;#t=new Hi;#s=null;#i=!1;#r;constructor(){clearInterval(this.#s),this.#s=setInterval(async()=>{const e=await this.#n();Ei.updateRttObject(e)},2e3),qi.requestMediaPermissions(),window.addEventListener("beforeunload",()=>{this.destroy()}),this.#r=Rs.subscribeKey("answerDevice",e=>{1===e&&this.initWebRtc()})}async initWebRtc(){const e=_s.agentInfo.sip_account_list.find(e=>1===e.device_type);if(e){const t=e.sip_register_addr;this.#e=new Ai({server:t.split(":")[0],user:e.sip_account,password:e.sip_password,webSocket:`wss://${t}`,callTimeout:3e4},this.#o),await this.#e.start()}}async init({actionNodeParams:e,url:t,appKey:s,appSecret:i,agentNo:r,enableBrowserAlert:n}){try{const{viewHtmlElement:o,rttHTML:a}=e;bi=t||"https://aicc-api.yescloudy.com",Ti=s,Si=i;const c=ni(s,i);await this.#t._agentLogin(r);const{agent_no:h,current_answer_device:d,init_state:l}=_s.agentInfo;l===et.IDLE?(Rs.updateActionConfigs(ws),_s.stateObject={state:l,state_name:"空闲"}):l===et.BUSY&&(Rs.updateActionConfigs(ys),_s.stateObject={state:l,state_name:"忙碌"}),Ui.getInstance(`ws://112.90.183.54:8719/api/v1/ws/call/${h}?Authorization=${c}`),Rs.updateAnswerDevice(d),1===d&&await this.initWebRtc(),e&&o&&(this.#i=!0,new Pi({container:o,rttHTML:a,statusParams:{state:et.IDLE,statusName:"空闲"}},this)),_s.enableBrowserAlert=n,n&&qi.requestPermission()}catch(e){this.#i&&mi("初始化失败"),console.log(e)}}#o=e=>{const{type:t,data:s}=e;ct.emit(t,s)};#a=e=>{};on(e,t){return ct.on(e,t),this}off(e,t){ct.off(e,t)}offAll(){ct.clearAllListeners()}#c(){if(!this.#e)throw new Error("请先初始化SDK");const e=Rs.get("isCalling"),t=_s.sessionId;if(!e)throw new Error("当前没有通话");if(!t)throw new Error("sessionId不存在")}#h(){const e=Rs.get("isHold"),{sessionId:t}=_s;if(t){if(e)throw new Error("当前已处于保持状态");Ui.__internalSend({type:rt.CALL_HOLD,data:{session_id:t}}),Rs.updateDisplayText(ot.HOLD),Rs.updateIsHold(!0)}}#d(){const{isHold:e}=Rs.getState(),{sessionId:t}=_s;if(t){if(!e)throw new Error("当前未处于保持状态");Ui.__internalSend({type:rt.CALL_UNHOLD,data:{session_id:t}}),Rs.updateDisplayText(""),Rs.updateIsHold(!1)}}#l(e){if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:t}=Rs.getState();if(!t)throw new Error("当前没有通话");this.#e.sendDTMF(e)}#u(){if(1!==Rs.get("answerDevice"))throw new Error("当前设备不支持此功能");if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:e,direction:t,outCallIsAnswer:s}=Rs.getState();if(!e)throw new Error("当前没有通话");s||(t===tt.INCOMING?this.#g():t===tt.OUTGOING&&this.#p())}async#f(e){if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:t,outCallIsAnswer:s,consultIsAnswer:i,incomingIsAnswer:r}=Rs.getState(),n=_s.sessionId;if(i)throw new Error("当前正在咨询");if(!(t||s||r))throw new Error("当前没有通话");if(!n)throw new Error("sessionId不存在");Ui.__internalSend({type:rt.CONSULT_REQUEST,data:{session_id:n,consultation_agent_no:e}})}async#m(){if(1!==Rs.get("answerDevice"))throw new Error("当前设备不支持此功能");if(!this.#e)throw new Error("请先初始化SDK");await this.#e.answerCall()}async#g(){if(!this.#e)throw new Error("请先初始化SDK");const{isCalling:e}=Rs.getState();if(!e)throw new Error("当前没有通话");await this.#e.rejectInCall()}async#v(e){if(!this.#e)throw new Error("请先初始化SDK");if(Rs.getState().isCalling)throw new Error("正在呼叫中,请勿重复点击");Rs.updateIsCalling(!0),Rs.updateDirection(tt.OUTGOING),Ui.__internalSend({type:rt.OUT_CALL,data:{customer_phone:e}})}async#p(){const{isCalling:e}=Rs.getState();if(!this.#e)throw new Error("请先初始化SDK");if(!e)throw new Error("当前没有通话");await this.#e.rejectOutCall()}async#w(){if(!this.#e)throw new Error("请先初始化SDK");if(!Rs.get("isCalling"))throw new Error("当前没有通话");await this.#e.hangup()}#y(){this.#c();const e=Rs.get("actionConfigs"),t=Rs.get("isMuted"),s=_s.sessionId;if(t)throw new Error("当前未处于静音状态");if(!e.includes("mute"))throw new Error("当前状态请勿调用静音");Ui.__internalSend({type:rt.MUTE,data:{session_id:s}})}#b(){this.#c();const e=Rs.get("actionConfigs"),t=Rs.get("isMuted"),s=_s.sessionId;if(!t)throw new Error("当前未处于未静音状态");if(!e.includes("unmute"))throw new Error("当前状态请勿调用静音");Ui.__internalSend({type:rt.UNMUTE,data:{session_id:s}})}#T(){this.#c();const e=Rs.get("consultIsAnswer"),t=_s.sessionId;if(!Rs.get("actionConfigs").includes("consult_transfer"))throw new Error("当前状态请勿调用咨询转移");if(!e)throw new Error("当前未处于咨询中");Ui.__internalSend({type:rt.CONSULT_TRANSFER,data:{session_id:t}})}async destroyRtc(){await(this.#e?.destroy())}destroy(){this.destroyRtc(),clearInterval(this.#s),Oi.off("ws-message",this.#a),Ui.__internalClose(),this.offAll(),this.#r?.(),this.#e=null,this.#t=null,this.#i=!1,console.log(1111)}async#n(){return this.#e?this.#e.getNetworkStats():{}}get call_api(){return this.#i?{answerCall:this.#m.bind(this),makeCall:this.#v.bind(this),cancelCall:this.#u.bind(this),hangup:this.#w.bind(this),sendDTMF:this.#l.bind(this),holdCall:this.#h.bind(this),unholdCall:this.#d.bind(this),consultCall:async e=>{try{await this.#f(e)}catch(e){throw console.warn(e),e}},consultTransfer:this.#T.bind(this),mute:this.#y.bind(this),unmute:this.#b.bind(this)}:{}}get agent_api(){return this.#i?{changeDevice:this.#t.changeDevice.bind(this.#t),changeState:this.#t.changeState.bind(this.#t),getIdleAgentList:e=>this.#t.getIdleAgentList(e),getAgentState:e=>this.#t.getAgentState(e)}:{}}}});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ysyt-agent-sdk",
3
- "version": "1.0.23",
3
+ "version": "1.0.25",
4
4
  "main": "dist/ysyt-agent-sdk.cjs.js",
5
5
  "module": "dist/ysyt-agent-sdk.esm.js",
6
6
  "types": "dist/index.d.ts",