@shenyin/embedded-call-widget 2.6.3 → 2.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,7 +25,7 @@ createCallButton({
25
25
 
26
26
  ```html
27
27
  <div id="call-button"></div>
28
- <script src="https://cdn.jsdelivr.net/npm/@shenyin/embedded-call-widget@2.6.3/embedded-call-widget.iife.js"></script>
28
+ <script src="https://cdn.jsdelivr.net/npm/@shenyin/embedded-call-widget@2.6.4/embedded-call-widget.iife.js"></script>
29
29
  <script>
30
30
  EmbeddedCallWidget.createCallButton({
31
31
  mount: '#call-button',
package/checksums.txt CHANGED
@@ -1,5 +1,5 @@
1
1
  69cdd0a3334992efd6cf570233e1cd2d0aacab04360546b4fd95e776e0a499d2 embedded-call-widget.js
2
- 667706853fecb195053410b91c2fb6576f18d5bdfc4a3789f33d2f6c69ea5e72 embedded-call-widget-runtime.js
2
+ 75900b00c8e0e7347c394dc2f6945b6d575c76caa932de9f341410806c2d3bb1 embedded-call-widget-runtime.js
3
3
  f4664df394b9287ba012c49a4423439bd103eccffd9e8a212c72fbeffc589054 embedded-call-widget.iife.js
4
- fcf91deb9410b66bc74b367611240cae4d49222e3aa9a8f426b24a2b2016ad2e embedded-call-widget-runtime.iife.js
5
- 36ab5893d574aa865384832fc2b235f4e7071c73f10bb86fed618df82ac6d098 manifest.json
4
+ 06bf9d92f55c439ef384c2eacf42dc5f4b1552d53ff412a356a824621451a1bd embedded-call-widget-runtime.iife.js
5
+ bb9482710307907115cc316119cf58b49befac2ade467482d2a97d72ab644579 manifest.json
@@ -98,7 +98,7 @@ Duration=`+2e3}};return e.info({requestOptions:n}).then(()=>{})}}async transfer(
98
98
  .logs { max-height: 140px; padding: 0 16px 16px; }
99
99
  }
100
100
  `,Q={idle:"网页电话已加载,正在准备待机能力。",requesting:"正在向服务端申请网页通话会话。",standbyRequesting:"正在向服务端申请 widget 待机会话。",registering:"临时账号已拿到,正在连接并执行 REGISTER。",standby:"REGISTER 已完成,widget 正在等待来电。",incoming:"收到新的来电,请选择接听或拒接。",calling:"已发起呼叫,正在等待对端振铃或接听。",connected:"通话已接通,媒体链路正在运行。",ended:"通话已结束,网页电话正在恢复待机。",rejected:"本次来电已拒接,widget 仍保持待机。",missed:"本次来电未接听,widget 仍保持待机。",failed:"本次呼叫失败,请先看下方日志。"},mi="手机网页首次接入时,请允许麦克风权限;若切到后台后再回来,先确认页面仍保持前台活跃。",ft=15e3,wi=15e3,pt=2,Se=ft*2,yi=1200,bi=3e4,vi=4e3,De={ready:"线路可用",preparing:"正在准备",working:"正在呼叫",recovering:"正在恢复",offline:"当前离线"};function Si(a,e){return e?`${a}
101
- ${JSON.stringify(e,null,2)}`:a}function Ti(a){return a==="outbound"?"outbound":"terminal"}function Ci(a){return typeof a=="function"}function Ei(){return typeof window>"u"?!1:new URLSearchParams(window.location.search).get("embeddedCallWidgetE2E")==="1"}function Ri(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():`widget-${Date.now()}-${Math.random().toString(16).slice(2,10)}`}function Ve(a){const e=String(a?.message||a||"");return/Failed to fetch|NetworkError|Load failed|network/i.test(e)}function ne(a={},e=null){const t=a?.advanced&&typeof a.advanced=="object"?a.advanced:{},i=String(t.visitorBusinessKey??"").trim();if(i)return i;const s=String(a?.businessKey??"").trim();return s||String(e?.default_business_key??"").trim()}function xi(a){const e=new Error(a);return e.name="EmbeddedCallWidgetLifecycleCancelledError",e.code="widget_runtime_run_stale",e}function mt(a){return a?.code==="sip_client_replaced"||a?.code==="widget_runtime_run_stale"}class Ii{constructor({mount:e,shadowRoot:t,options:i}){this.mountNode=e,this.shadowRoot=t,this.options=i,this.mode=Ti(i.mode),this.legacyOutboundOnly=this.mode==="outbound",this.apiBaseUrl=$e(i.apiBaseUrl||window.location.origin).href,this.client=null,this.issuedSession=null,this.bootstrap=null,this.turnIceServers=[],this.state="idle",this.finalizing=!1,this.isMobileViewport=!1,this.pendingIncomingCall=null,this.visibilityHandler=null,this.sessionTouchTimer=null,this.sessionRecoveryInFlight=null,this.transportDisconnectTimer=null,this.transportDisconnectContext=null,this.e2eBridgeEnabled=Ei(),this.terminalFailureOverride="",this.presenceId=Ri(),this.presenceTouchTimer=null,this.presenceFailureCount=0,this.availabilityState="preparing",this.availabilityDetail="正在连接后台并准备网页电话待机。",this.connectivityRecoveryInFlight=null,this.lifecycleRunId=0,this.pendingLifecycleMode=null,this.expectedClientTeardown=null,this.autoStandbyRecoveryTimer=null,this.recentlyReleasedSessions=new Map,this.networkOnline=typeof navigator>"u"?!0:navigator.onLine!==!1,this.pageVisible=typeof document>"u"?!0:document.visibilityState==="visible",this.onlineHandler=null,this.offlineHandler=null,this.refs={}}async mount(){this.isMobileViewport=typeof window<"u"&&window.matchMedia("(max-width: 480px)").matches,this.shadowRoot.innerHTML=`
101
+ ${JSON.stringify(e,null,2)}`:a}function Ti(a){return a==="outbound"?"outbound":"terminal"}function Ci(a){return typeof a=="function"}function Ei(){return typeof window>"u"?!1:new URLSearchParams(window.location.search).get("embeddedCallWidgetE2E")==="1"}function Ri(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():`widget-${Date.now()}-${Math.random().toString(16).slice(2,10)}`}function Ve(a){const e=String(a?.message||a||"");return/Failed to fetch|NetworkError|Load failed|network/i.test(e)}function ne(a={},e=null){const t=a?.advanced&&typeof a.advanced=="object"?a.advanced:{},i=String(t.visitorBusinessKey??"").trim();if(i)return i;const s=String(a?.businessKey??"").trim();return s||String(e?.default_business_key??"").trim()}function xi(a){const e=new Error(a);return e.name="EmbeddedCallWidgetLifecycleCancelledError",e.code="widget_runtime_run_stale",e}function mt(a){return a?.code==="sip_client_replaced"||a?.code==="widget_runtime_run_stale"}class Ii{constructor({mount:e,shadowRoot:t,options:i}){this.mountNode=e,this.shadowRoot=t,this.options=i,this.mode=Ti(i.mode),this.legacyOutboundOnly=this.mode==="outbound",this.apiBaseUrl=$e(i.apiBaseUrl||window.location.origin).href,this.client=null,this.issuedSession=null,this.bootstrap=null,this.turnIceServers=[],this.state="idle",this.finalizing=!1,this.isMobileViewport=!1,this.pendingIncomingCall=null,this.visibilityHandler=null,this.sessionTouchTimer=null,this.sessionRecoveryInFlight=null,this.transportDisconnectTimer=null,this.transportDisconnectContext=null,this.e2eBridgeEnabled=Ei(),this.terminalFailureOverride="",this.presenceId=Ri(),this.presenceTouchTimer=null,this.presenceFailureCount=0,this.availabilityState="preparing",this.availabilityDetail="正在连接后台并准备网页电话待机。",this.connectivityRecoveryInFlight=null,this.lifecycleRunId=0,this.pendingLifecycleMode=null,this.expectedClientTeardown=null,this.expectedTermination=null,this.autoStandbyRecoveryTimer=null,this.recentlyReleasedSessions=new Map,this.networkOnline=typeof navigator>"u"?!0:navigator.onLine!==!1,this.pageVisible=typeof document>"u"?!0:document.visibilityState==="visible",this.onlineHandler=null,this.offlineHandler=null,this.refs={}}async mount(){this.isMobileViewport=typeof window<"u"&&window.matchMedia("(max-width: 480px)").matches,this.shadowRoot.innerHTML=`
102
102
  <style>${pi}</style>
103
103
  <section class="widget">
104
104
  <div class="panel">
@@ -118,4 +118,4 @@ ${JSON.stringify(e,null,2)}`:a}function Ti(a){return a==="outbound"?"outbound":"
118
118
  <audio class="audio" data-role="audio" autoplay playsinline></audio>
119
119
  </div>
120
120
  </section>
121
- `,this.refs.status=this.shadowRoot.querySelector('[data-role="status"]'),this.refs.availability=this.shadowRoot.querySelector('[data-role="availability"]'),this.refs.hint=this.shadowRoot.querySelector('[data-role="hint"]'),this.refs.meta=this.shadowRoot.querySelector('[data-role="meta"]'),this.refs.primary=this.shadowRoot.querySelector('[data-role="primary"]'),this.refs.secondary=this.shadowRoot.querySelector('[data-role="secondary"]'),this.refs.logs=this.shadowRoot.querySelector('[data-role="logs"]'),this.refs.audio=this.shadowRoot.querySelector('[data-role="audio"]'),this.refs.primary.addEventListener("click",()=>{this.handlePrimaryAction()}),this.refs.secondary.addEventListener("click",()=>{this.handleSecondaryAction()}),this.bindPageLifecycle(),this.registerE2EBridge(),this.startPresenceTouch(),this.render()}bindPageLifecycle(){typeof document>"u"||(this.visibilityHandler=()=>{if(this.pageVisible=document.visibilityState==="visible",document.visibilityState==="hidden"){this.reportIssuedSessionEvent("visibility-hidden",{visible:!1}),this.touchWidgetPresence("visibility-hidden"),this.appendLog("页面已切到后台",{state:this.state,advice:"回到前台后请先确认页面仍保持活动,再继续观察通话状态。"}),this.isMobileViewport&&["requesting","registering","calling","connected"].includes(this.state)&&(this.refs.status.textContent="页面已切到后台;手机网页回到前台后请确认通话仍在继续。");return}if(this.isMobileViewport&&(this.appendLog("页面已回到前台",{state:this.state}),["requesting","registering","calling","connected"].includes(this.state)&&(this.refs.status.textContent=Q[this.state]||Q.idle)),this.touchWidgetPresence("visibility-visible"),!this.legacyOutboundOnly&&["standby","registering","standbyRequesting","failed","idle","missed","rejected","ended"].includes(this.state)){const e=this.client?.getConnectionSnapshot?.()||null,t=!!(e?.connected&&(this.state!=="standby"||e?.registered));this.issuedSession?.session_id&&t?this.touchIssuedSession(this.state,{trigger:"visibility-visible"}):(this.appendLog("页面回到前台后检测到待机链路未处于健康状态,准备自动重建。",{state:this.state,has_session:!!this.issuedSession?.session_id,client_snapshot:e}),this.client||this.issuedSession?.session_id?this.disconnectAndCleanup("visibility-visible-unhealthy-client"):this.startStandby({force:!0}))}this.reportIssuedSessionEvent("visibility-visible",{visible:!0}),this.render()},document.addEventListener("visibilitychange",this.visibilityHandler),typeof window<"u"&&(this.onlineHandler=()=>{this.networkOnline=!0,this.setAvailabilityState("recovering","网络已恢复,正在重新连接网页电话。"),this.touchWidgetPresence("browser-online"),this.legacyOutboundOnly||this.restartStandbyAfterConnectivity("browser-online")},this.offlineHandler=()=>{this.networkOnline=!1,this.setAvailabilityState("offline","当前网络连接已断开,正在等待恢复。"),["incoming","connected"].includes(this.state)?this.render():this.setState("failed","当前网络连接已断开,正在等待恢复。")},window.addEventListener("online",this.onlineHandler),window.addEventListener("offline",this.offlineHandler)))}open(){this.mountNode.scrollIntoView({block:"nearest",inline:"nearest"})}beginLifecycleRun(){return this.lifecycleRunId+=1,this.lifecycleRunId}assertLifecycleRunCurrent(e){if(e!==this.lifecycleRunId)throw xi("网页电话已切换到新的连接流程,旧流程不再继续。")}emitHostCallback(e,t){const i=this.options?.[e];if(Ci(i))try{i(t)}catch(s){this.appendLog("宿主回调执行失败",{callback:e,message:s?.message||String(s)})}}getSnapshot(){return{mode:this.mode,state:this.state,sessionMode:this.issuedSession?.session_mode||null,sessionId:this.issuedSession?.session_id||null,siteKey:this.options.siteKey||this.bootstrap?.default_site_key||null,businessKey:this.issuedSession?.resolved_business_key||ne(this.options,this.bootstrap)||null,standbyMode:this.issuedSession?.standby_mode||null,standbyState:this.issuedSession?.standby_state||this.options.standbyState||null,widgetSipNumber:this.issuedSession?.widget_anchor_number||null,browserSipUsername:this.issuedSession?.browser_sip_username||this.issuedSession?.sip_username||null,routeNote:this.issuedSession?.route_note||null,incomingAnchors:Array.isArray(this.issuedSession?.incoming_anchors)?this.issuedSession.incoming_anchors:[],hasClient:!!this.client,hasIssuedSession:!!this.issuedSession?.session_id,primaryText:this.refs.primary?.textContent?.trim()||"",secondaryText:this.refs.secondary?.textContent?.trim()||"",statusText:this.refs.status?.textContent?.trim()||"",availabilityState:this.availabilityState,availabilityLabel:this.refs.availability?.textContent?.trim()||"",presenceId:this.presenceId,networkOnline:this.networkOnline,pendingIncomingCall:this.pendingIncomingCall}}registerE2EBridge(){if(!this.e2eBridgeEnabled||typeof window>"u")return;const e=window.location.hostname||"example.invalid";window.__embeddedCallWidgetE2E__={getSnapshot:()=>this.getSnapshot(),simulateIncomingCall:(t={})=>{const i=t.host||e,s=t.user||"2001";return this.pendingIncomingCall={displayName:t.displayName||"测试来电",user:s,host:i,direction:"inbound",uri:t.uri||`sip:${s}@${i}`,e2eSimulated:!0},this.setState("incoming"),this.emitHostCallback("onIncomingCall",this.pendingIncomingCall),this.getSnapshot()},answerIncomingCall:async()=>(await this.answerIncomingCall(),this.getSnapshot()),declineIncomingCall:async()=>(await this.declineIncomingCall(),this.getSnapshot()),hangupCall:async()=>(await this.hangupCall(),this.getSnapshot())}}async handlePrimaryAction(){if(this.availabilityState==="offline"){const e=this.networkOnline===!1?"当前网络连接已断开,正在尝试恢复,请稍后再试。":"当前无法连接通话服务,正在尝试恢复,请稍后再试。";this.appendLog("访客点击了离线中的 widget",{state:this.state,availability:this.availabilityState,message:e}),["incoming","connected"].includes(this.state)||this.setState("failed",e),this.render();return}if(this.state==="incoming"){await this.answerIncomingCall();return}if(this.legacyOutboundOnly){await this.connectAndCall();return}if(!this.client&&!this.issuedSession){await this.startStandby();return}await this.connectAndCall()}async handleSecondaryAction(){if(this.state==="incoming"){await this.declineIncomingCall();return}if(this.state==="connected"&&this.client){await this.hangupCall();return}await this.disconnectAndCleanup("manual-disconnect")}async connectAndCall(){if(["requesting","registering","calling","connected"].includes(this.state))return;const e=this.beginLifecycleRun();try{this.terminalFailureOverride="",this.clearAutoStandbyRecovery(),this.pendingLifecycleMode="outbound",this.setAvailabilityState("working","正在建立呼叫链路。"),!this.legacyOutboundOnly&&this.issuedSession?.session_mode==="widget_standby"&&(this.appendLog("访客主动呼叫前,先释放当前待机会话并切换到呼叫链路。",{widget_anchor_number:this.issuedSession?.widget_anchor_number||null,resolved_business_key:this.issuedSession?.resolved_business_key||ne(this.options,this.bootstrap)||null}),await this.disconnectAndCleanup("switch-to-outbound-call",{preserveLifecycleRun:!0,suppressStateReset:!0,suppressClientTeardownState:"switch-to-outbound-call"}),this.assertLifecycleRunCurrent(e)),this.setState("requesting"),await this.loadBootstrap(),this.assertLifecycleRunCurrent(e),this.turnIceServers=await this.fetchTurnIceServers(),this.assertLifecycleRunCurrent(e);const t=await this.fetchIssuedSession();this.assertLifecycleRunCurrent(e),await this.ensureClient(t),this.assertLifecycleRunCurrent(e),this.setState("registering"),await this.client.ensureReady(),this.assertLifecycleRunCurrent(e),this.setState("calling"),this.syncAvailabilityState(),await this.client.call(t.target),this.assertLifecycleRunCurrent(e),this.appendLog("已发起呼叫",{target:t.target,route_note:t.routeNote||null})}catch(t){if(this.pendingLifecycleMode=null,mt(t)){this.appendLog("已忽略过期的网页电话呼叫流程",{message:t?.message||String(t)});return}const i=t?.message||String(t);if(this.terminalFailureOverride=i,this.setState("failed",i),Ve(t)||this.networkOnline===!1?this.setAvailabilityState("offline",this.networkOnline===!1?"当前网络连接已断开,正在等待恢复。":"当前无法连接通话服务,正在自动尝试恢复。"):this.setAvailabilityState("recovering","呼叫失败,正在准备恢复网页电话待机。"),this.appendLog("连接并呼叫失败",{message:i}),await this.releaseIssuedSession("failed","connect-and-call-failed"),!this.legacyOutboundOnly){this.appendLog("呼叫失败后,网页电话将恢复待机。",{message:i}),await this.startStandby({force:!0});return}this.setState("failed",i)}}async startStandby(e={}){if(this.legacyOutboundOnly)throw new Error("当前 widget 仍处于 legacy outbound 兼容模式");const{force:t=!1}=e;if(!t&&["standbyRequesting","registering","incoming","connected"].includes(this.state))return;const i=this.beginLifecycleRun();try{this.clearAutoStandbyRecovery(),this.pendingLifecycleMode="standby",this.terminalFailureOverride="",this.setState("standbyRequesting"),this.setAvailabilityState("preparing","正在申请新的待机会话。"),await this.loadBootstrap(),this.assertLifecycleRunCurrent(i),this.turnIceServers=await this.fetchTurnIceServers(),this.assertLifecycleRunCurrent(i);const s=await this.fetchStandbySession();this.assertLifecycleRunCurrent(i),await this.ensureClient(s),this.assertLifecycleRunCurrent(i),this.setState("registering","待机会话已拿到,正在连接并执行 REGISTER。"),await this.client.ensureReady(),this.assertLifecycleRunCurrent(i),this.pendingLifecycleMode=null,this.setState("standby"),this.setAvailabilityState("ready","当前可被呼叫,也可由访客主动发起呼叫。"),this.appendLog("widget 已进入待机",{session_id:this.issuedSession?.session_id||null,route_business_key:this.issuedSession?.resolved_business_key||ne(this.options,this.bootstrap)||null,standby_mode:this.issuedSession?.standby_mode||null,widget_anchor_number:this.issuedSession?.widget_anchor_number||null,browser_sip_username:this.issuedSession?.browser_sip_username||this.issuedSession?.sip_username||null}),this.emitHostCallback("onStandbyReady",this.getSnapshot())}catch(s){if(mt(s)){this.appendLog("已忽略过期的网页电话待机流程",{message:s?.message||String(s)});return}this.pendingLifecycleMode=null;const r=s?.message||String(s);this.terminalFailureOverride=r,this.setState("failed",r),Ve(s)||this.networkOnline===!1?this.setAvailabilityState("offline",this.networkOnline===!1?"当前网络连接已断开,正在等待恢复。":"当前无法连接通话服务,正在自动尝试恢复。"):this.setAvailabilityState("recovering","当前未能完成待机注册,系统会继续自动恢复。"),this.appendLog("进入待机失败",{message:r}),await this.releaseIssuedSession("failed","standby-start-failed"),this.setState("failed",r),!this.legacyOutboundOnly&&this.networkOnline!==!1&&this.scheduleAutoStandbyRecovery("standby-start-failed")}}async ensureClient(e){const t={sip_username:e.sipUsername,sip_password:e.password,sip_domain:e.sipDomain,webrtc_url:e.transport,ice_servers:this.turnIceServers};this.client?.matchesAccount(t)||(this.client&&await this.client.destroy(),this.client=new hi(t,{remoteAudioElement:this.refs.audio,onStateChange:(i,s)=>{this.handleClientState(i,s)},onError:i=>this.appendLog("SIP 客户端错误",{message:i?.message||String(i)})}))}async handleClientState(e,t={}){if(this.syncSessionTouch(e),this.reportIssuedSessionEvent(e,t,e),(e==="connected"||e==="registered"||e==="ringing"||e==="dialing"||e==="answered")&&await this.clearTransportDisconnectGrace(!0),e==="registered"){this.pendingLifecycleMode=null,this.issuedSession?.session_mode==="widget_standby"?(this.setState("standby"),this.setAvailabilityState("ready","当前可被呼叫,也可由访客主动发起呼叫。")):this.setState("registering","REGISTER 已完成,正在准备发起呼叫。");return}if(e==="incoming"){this.pendingLifecycleMode=null,this.pendingIncomingCall=t&&typeof t=="object"?{...t}:null,this.setState("incoming"),this.setAvailabilityState("ready","已有新来电,请直接接听或拒接。"),this.appendLog("收到新的来电",this.pendingIncomingCall),this.emitHostCallback("onIncomingCall",this.pendingIncomingCall);return}if(e==="ringing"||e==="dialing"){this.setState("calling",t?.message||Q.calling);return}if(e==="answered"){this.pendingLifecycleMode=null,this.terminalFailureOverride="";const i=this.pendingIncomingCall||t||null;this.setState("connected"),this.setAvailabilityState("ready","当前通话已接通。"),this.emitHostCallback("onCallAnswered",i);return}if(e==="terminated"){if(this.issuedSession?.session_mode==="widget_standby"){await this.handleStandbyTermination(t);return}t&&typeof t=="object"&&(t.source==="invite-response"||t.source==="invite-exception"||t.code||t.status||t.stage)&&(this.terminalFailureOverride=t?.message||Q.failed,this.setState("failed",this.terminalFailureOverride)),await this.finalizeAfterTermination(t?.hangupSource||"terminated");return}if(e==="failed"){if(this.isExpectedClientTeardown())return;this.pendingLifecycleMode=null,await this.clearTransportDisconnectGrace(!1),this.stopSessionTouch(),this.terminalFailureOverride=t?.message||Q.failed,this.setState("failed",this.terminalFailureOverride),this.setAvailabilityState("recovering","通话链路异常断开,系统会继续自动恢复。"),await this.releaseIssuedSession("failed","sip-client-failed");return}if(e==="disconnected"||e==="unregistered"){if(this.isExpectedClientTeardown())return;if(this.stopSessionTouch(),["calling","connected"].includes(this.state)){this.setState("connected","SIP 信令短暂中断,正在等待恢复。"),this.setAvailabilityState("recovering","通话信令短暂中断,正在等待恢复。"),this.scheduleTransportDisconnectGrace(e);return}const s=this.terminalFailureOverride||this.client?.lastFailureMeta?.message||null;if(s){this.terminalFailureOverride=s,this.setState("failed",s);return}if(this.state==="failed")return;this.state!=="idle"&&this.state!=="ended"&&(this.pendingLifecycleMode=null,this.setState("ended"),this.setAvailabilityState("recovering","通话已结束,正在恢复网页电话待机。"),this.scheduleAutoStandbyRecovery(`client-${e}`))}}stopTransportDisconnectGrace(){this.transportDisconnectTimer&&(window.clearTimeout(this.transportDisconnectTimer),this.transportDisconnectTimer=null),this.transportDisconnectContext=null}sameTransportDisconnectContext(e){return!e||!this.issuedSession?.session_id?!1:e.sessionId===this.issuedSession.session_id}async clearTransportDisconnectGrace(e){const t=this.transportDisconnectContext,i=!!(e&&t?.reported&&this.sameTransportDisconnectContext(t));this.stopTransportDisconnectGrace(),i&&(await this.reportIssuedSessionEvent("transport-restored",{disconnectDurationMs:Date.now()-(t.startedAt||Date.now()),graceMs:Se},this.state),this.appendLog("网页通话信令已恢复",{disconnectDurationMs:Date.now()-(t.startedAt||Date.now()),graceMs:Se}))}scheduleTransportDisconnectGrace(e){if(!this.issuedSession?.session_id)return;this.stopTransportDisconnectGrace();const t={sessionId:this.issuedSession.session_id,startedAt:Date.now(),reported:!1,state:e};this.transportDisconnectContext=t,this.transportDisconnectTimer=window.setTimeout(()=>{this.transportDisconnectTimer=null,this.sameTransportDisconnectContext(t)&&(t.reported=!0,this.reportIssuedSessionEvent("transport-disconnected",{state:e,graceMs:Se},this.state),this.appendLog("网页通话信令断开超过宽限窗口",{graceMs:Se,state:e}))},Se)}async finalizeAfterTermination(e){if(!this.finalizing){this.beginLifecycleRun(),this.finalizing=!0;try{if(this.pendingLifecycleMode=null,this.stopSessionTouch(),this.stopTransportDisconnectGrace(),await this.releaseIssuedSession("released",e),this.client&&(await this.client.destroy(),this.client=null),!this.legacyOutboundOnly){this.setState("idle","上一通通话已结束,正在恢复网页电话待机。"),this.setAvailabilityState("recovering","上一通通话已结束,正在恢复网页电话待机。"),this.appendLog("通话已结束,网页电话正在恢复待机。",{reason:e}),await this.startStandby({force:!0});return}const t=this.terminalFailureOverride||(this.state==="failed"?this.refs.status?.textContent?.trim()||Q.failed:"");t?this.setState("failed",t):this.setState("ended"),this.appendLog("通话已结束并完成清理",{reason:e})}finally{this.finalizing=!1}}}async handleStandbyTermination(e={}){this.stopTransportDisconnectGrace();const t=this.state,i=e?.hangupSource||"terminated";t==="incoming"?i==="local"?(this.setState("rejected"),this.setAvailabilityState("ready","当前来电已拒接,widget 仍保持可用。"),this.emitHostCallback("onCallRejected",this.pendingIncomingCall||e||null)):(this.setState("missed"),this.setAvailabilityState("ready","当前来电未接听,widget 仍保持可用。"),this.emitHostCallback("onCallMissed",this.pendingIncomingCall||e||null)):(this.setState("standby","上一通来电已结束,继续等待下一通来电。"),this.setAvailabilityState("ready","当前可被呼叫,也可由访客主动发起呼叫。")),this.appendLog("待机会话中的来电已结束",{hangupSource:i,previousState:t}),this.pendingIncomingCall=null}async answerIncomingCall(){if(this.state==="incoming"){if(this.e2eBridgeEnabled&&this.pendingIncomingCall?.e2eSimulated){this.setState("connected","测试桥已模拟接听当前来电。"),this.emitHostCallback("onCallAnswered",this.pendingIncomingCall);return}if(!this.client&&this.e2eBridgeEnabled){this.setState("connected","测试桥已模拟接听当前来电。"),this.emitHostCallback("onCallAnswered",this.pendingIncomingCall);return}if(!this.client)throw new Error("当前没有可接听的 SIP 会话");await this.client.answer()}}async declineIncomingCall(){if(this.state==="incoming"){if(this.e2eBridgeEnabled&&this.pendingIncomingCall?.e2eSimulated){this.setState("rejected"),this.emitHostCallback("onCallRejected",this.pendingIncomingCall),this.pendingIncomingCall=null;return}if(!this.client&&this.e2eBridgeEnabled){this.setState("rejected"),this.emitHostCallback("onCallRejected",this.pendingIncomingCall),this.pendingIncomingCall=null;return}if(!this.client)throw new Error("当前没有可拒接的 SIP 会话");await this.client.decline()}}async hangupCall(){if(this.e2eBridgeEnabled&&this.pendingIncomingCall?.e2eSimulated){this.legacyOutboundOnly?this.setState("ended"):this.setState("standby","测试桥已模拟挂断,网页电话已恢复待机。"),this.pendingIncomingCall=null;return}if(!this.client&&this.e2eBridgeEnabled){this.legacyOutboundOnly?this.setState("ended"):this.setState("standby","测试桥已模拟挂断,网页电话已恢复待机。"),this.pendingIncomingCall=null;return}this.client&&await this.client.hangup()}async disconnectAndCleanup(e,t={}){const{preserveLifecycleRun:i=!1,suppressStateReset:s=!1,suppressClientTeardownState:r=null}=t;i||this.beginLifecycleRun(),r&&this.suppressExpectedClientTeardown(r),this.stopSessionTouch(),this.stopTransportDisconnectGrace(),this.reportIssuedSessionEvent("manual-disconnect",{reason:e},this.state),this.pendingIncomingCall=null,this.terminalFailureOverride="",this.client&&(await this.client.destroy(),this.client=null),await this.releaseIssuedSession("released",e),s||(this.pendingLifecycleMode=null,this.setState("idle"),this.setAvailabilityState("recovering","网页电话已断开,系统正在自动恢复待机。"),this.appendLog("连接已断开",{reason:e}),!this.legacyOutboundOnly&&this.networkOnline!==!1&&this.scheduleAutoStandbyRecovery(e))}async loadBootstrap(){if(this.bootstrap)return this.bootstrap;const e=await fetch(X(this.apiBaseUrl,"api/sip/embedded-call-demo-bootstrap"),{credentials:"include",cache:"no-store"});if(!e.ok)throw new Error(`默认模式读取失败:${e.status}`);return this.bootstrap=await e.json(),this.bootstrap}async fetchTurnIceServers(){const e=await fetch(X(this.apiBaseUrl,"api/sip/webrtc-turn-credentials"),{credentials:"include",cache:"no-store"});if(!e.ok)return this.appendLog("TURN 凭证获取失败,继续尝试直连",{status:e.status}),[];const t=await e.json();return Array.isArray(t?.ice_servers)?t.ice_servers:[]}async fetchIssuedSession(){const e=this.options.siteKey||this.bootstrap?.default_site_key||"",t=ne(this.options,this.bootstrap);if(!e)throw new Error("当前 SDK 模式必须明确提供 siteKey,或让服务端下发默认站点键");const i={site_key:e,standby_state:this.options.standbyState||void 0,selected_primary_account:this.options.selectedPrimaryAccount||void 0,selected_middle_layer_account:this.options.selectedMiddleLayerAccount||void 0,display_name:this.options.displayName||void 0,page_url:window.location.href,access_token:this.options.accessToken||void 0};t&&(i.business_key=t);const s=await fetch(X(this.apiBaseUrl,"api/sip/issue-call-session"),{method:"POST",credentials:"include",cache:"no-store",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});if(!s.ok){const n=await s.json().catch(()=>({}));throw new Error(n?.detail||`会话签发失败:${s.status}`)}const r=await s.json();return this.issuedSession=r,this.reportIssuedSessionEvent("issued-session-fetched",{sessionId:r.session_id,targetExtension:r.target_extension,dialTargetExtension:r.dial_target_extension||r.target_extension,resolvedTargetInternalNumbers:r.resolved_target_internal_numbers||[],resolvedBusinessKey:r.resolved_business_key||null},"requesting"),this.appendLog("已获取服务端签发会话",{session_id:r.session_id,target_extension:r.target_extension,dial_target_extension:r.dial_target_extension||r.target_extension||null,resolved_target_internal_numbers:r.resolved_target_internal_numbers||[],resolved_business_key:r.resolved_business_key||null,resolved_route_mode:r.resolved_route_mode||null,route_note:r.route_note||null}),{sessionMode:r.session_mode||"outbound",sipUsername:r.sip_username,sipDomain:r.sip_domain,password:r.sip_password,transport:gt({apiBaseUrl:this.apiBaseUrl,transportBaseUrl:this.options.transportBaseUrl||"",transportUrl:r.ws_server}),target:r.dial_target_extension||r.target_extension,routeNote:r.route_note||""}}async fetchStandbySession(){const e=this.options.siteKey||this.bootstrap?.default_site_key||"",t=ne(this.options)||null;if(!e)throw new Error("当前 SDK 待机模式必须明确提供 siteKey,或让服务端下发默认站点键");const i={site_key:e,standby_state:this.options.standbyState||void 0,selected_primary_account:this.options.selectedPrimaryAccount||void 0,selected_middle_layer_account:this.options.selectedMiddleLayerAccount||void 0,display_name:this.options.displayName||void 0,page_url:window.location.href,access_token:this.options.accessToken||void 0};t&&(i.business_key=t);const s=await fetch(X(this.apiBaseUrl,"api/sip/issue-widget-standby-session"),{method:"POST",credentials:"include",cache:"no-store",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});if(!s.ok){const n=await s.json().catch(()=>({}));throw new Error(n?.detail||`待机会话签发失败:${s.status}`)}const r=await s.json();return this.issuedSession=r,this.reportIssuedSessionEvent("standby-session-fetched",{sessionId:r.session_id,resolvedBusinessKey:r.resolved_business_key||null,standbyMode:r.standby_mode||null},"standbyRequesting"),this.appendLog("已获取 widget 待机会话",{session_id:r.session_id,resolved_business_key:r.resolved_business_key||null,standby_mode:r.standby_mode||null,standby_state:r.standby_state||null,widget_anchor_number:r.widget_anchor_number||null,browser_sip_username:r.browser_sip_username||r.sip_username||null,route_note:r.route_note||null}),{sessionMode:r.session_mode||"widget_standby",sipUsername:r.sip_username,sipDomain:r.sip_domain,password:r.sip_password,transport:gt({apiBaseUrl:this.apiBaseUrl,transportBaseUrl:this.options.transportBaseUrl||"",transportUrl:r.ws_server}),target:null,routeNote:r.route_note||""}}async releaseIssuedSession(e,t){if(!this.issuedSession?.session_id)return;const i=this.issuedSession.session_id;this.markRecentlyReleasedSession(i,t),this.issuedSession=null,await fetch(X(this.apiBaseUrl,"api/sip/release-call-session"),{method:"POST",credentials:"include",cache:"no-store",headers:{"Content-Type":"application/json"},body:JSON.stringify({session_id:i,status:e,reason:t,client_summary:this.buildClientEvidenceSummary(e,{releaseReason:t})})}).catch(()=>{})}buildClientEvidenceSummary(e=null,t=null){const i={runtimeMode:"embedded-widget-runtime",widgetVersion:"2026-03-29-runtime-evidence",widgetMode:this.mode,sessionMode:this.issuedSession?.session_mode||null,state:e||this.state,visible:typeof document>"u"?!0:document.visibilityState==="visible",pageUrl:typeof window>"u"?null:window.location.href,connectionSnapshot:this.client?.getConnectionSnapshot?.()||null};return t&&typeof t=="object"&&(i.eventDetail=t),i}buildPresenceEvidenceSummary(e=null,t=null){return{...this.buildClientEvidenceSummary(e,t),presenceId:this.presenceId,availabilityState:this.availabilityState,availabilityDetail:this.availabilityDetail,networkOnline:this.networkOnline,pageVisible:this.pageVisible}}setAvailabilityState(e,t=""){this.availabilityState=e,this.availabilityDetail=t||this.availabilityDetail,this.refs.availability&&(this.refs.availability.textContent=t?`${De[e]||De.recovering} · ${t}`:De[e]||De.recovering,this.refs.availability.className=`availability availability--${e}`),this.render()}suppressExpectedClientTeardown(e,t=vi){this.expectedClientTeardown={reason:e,expiresAt:Date.now()+t}}isExpectedClientTeardown(){const e=this.expectedClientTeardown;return e?e.expiresAt<=Date.now()?(this.expectedClientTeardown=null,!1):!0:!1}clearAutoStandbyRecovery(){this.autoStandbyRecoveryTimer&&(window.clearTimeout(this.autoStandbyRecoveryTimer),this.autoStandbyRecoveryTimer=null)}scheduleAutoStandbyRecovery(e,t=yi){this.legacyOutboundOnly||this.networkOnline===!1||this.autoStandbyRecoveryTimer||this.client||this.issuedSession?.session_id||this.finalizing||this.pendingLifecycleMode==="outbound"||this.sessionRecoveryInFlight||this.connectivityRecoveryInFlight||["requesting","standbyRequesting","registering","calling","incoming","connected"].includes(this.state)||(this.autoStandbyRecoveryTimer=window.setTimeout(()=>{this.autoStandbyRecoveryTimer=null,!(this.legacyOutboundOnly||this.networkOnline===!1)&&(this.client||this.issuedSession?.session_id||this.finalizing||this.pendingLifecycleMode==="outbound"||(this.appendLog("检测到页面当前不在通话中,开始自动恢复待机。",{reason:e}),this.startStandby({force:!0})))},t))}pruneRecentlyReleasedSessions(){const e=Date.now();for(const[t,i]of this.recentlyReleasedSessions.entries())(i?.expiresAt||0)<=e&&this.recentlyReleasedSessions.delete(t)}markRecentlyReleasedSession(e,t){e&&(this.pruneRecentlyReleasedSessions(),this.recentlyReleasedSessions.set(e,{reason:t,expiresAt:Date.now()+bi}))}wasSessionRecentlyReleased(e){return e?(this.pruneRecentlyReleasedSessions(),this.recentlyReleasedSessions.has(e)):!1}syncAvailabilityState(){if(this.networkOnline===!1){this.setAvailabilityState("offline","当前网络连接已断开,正在等待恢复。");return}if(this.pendingLifecycleMode==="outbound"&&["requesting","registering","calling"].includes(this.state)){this.setAvailabilityState("working",this.state==="calling"?"正在等待对端振铃或接听。":"正在建立呼叫链路。");return}if(["requesting","standbyRequesting","registering"].includes(this.state)){this.setAvailabilityState("preparing","页面仍在线,正在建立网页电话连接。");return}if(!this.legacyOutboundOnly&&!this.issuedSession?.session_id&&!this.pendingLifecycleMode&&!["calling","connected","incoming"].includes(this.state)){this.setAvailabilityState("recovering","页面仍在线,正在自动恢复待机。"),this.scheduleAutoStandbyRecovery("missing-issued-session");return}this.setAvailabilityState("ready","当前可被呼叫,也可由访客主动发起呼叫。")}stopPresenceTouch(){this.presenceTouchTimer&&(window.clearInterval(this.presenceTouchTimer),this.presenceTouchTimer=null)}async restartStandbyAfterConnectivity(e){if(!(this.legacyOutboundOnly||this.connectivityRecoveryInFlight)&&!["connected","incoming","calling","requesting","standbyRequesting","registering"].includes(this.state)){this.connectivityRecoveryInFlight=(async()=>{this.appendLog("检测到连通性已恢复,准备重建网页电话待机。",{trigger:e,state:this.state,hasSession:!!this.issuedSession?.session_id}),(this.client||this.issuedSession?.session_id)&&await this.disconnectAndCleanup(`connectivity-restored-${e}`),await this.startStandby({force:!0})})();try{await this.connectivityRecoveryInFlight}finally{this.connectivityRecoveryInFlight=null}}}async handleServerSessionAction(e,t={}){return this.legacyOutboundOnly||String(t?.session_action||"").trim().toLowerCase()!=="restart_standby"||this.sessionRecoveryInFlight||this.connectivityRecoveryInFlight?!1:["requesting","standbyRequesting","calling","incoming","connected"].includes(this.state)?(this.appendLog("服务端已要求重建待机,但当前阶段暂不主动打断。",{source:e,state:this.state,session_action_reason:t?.session_action_reason||null,session_action_detail:t?.session_action_detail||null}),!1):(this.appendLog("服务端要求当前页面放弃旧锚点并重新进入待机。",{source:e,session_action_reason:t?.session_action_reason||null,session_action_detail:t?.session_action_detail||null,resolved_session_id:t?.resolved_session_id||null,resolved_widget_anchor_number:t?.resolved_widget_anchor_number||null,resolved_browser_sip_username:t?.resolved_browser_sip_username||null}),this.setAvailabilityState("recovering",t?.session_action_detail||"当前锚点已失效,正在自动切换到新的待机线路。"),await this.disconnectAndCleanup(`server-session-action-${t?.session_action_reason||e}`,{preserveLifecycleRun:!0}),await this.startStandby({force:!0}),!0)}async touchWidgetPresence(e="interval"){if(this.legacyOutboundOnly)return;this.networkOnline=typeof navigator>"u"?!0:navigator.onLine!==!1,this.pageVisible=typeof document>"u"?!0:document.visibilityState==="visible";const t=this.availabilityState==="offline";try{const i=await fetch(X(this.apiBaseUrl,"api/sip/touch-widget-presence"),{method:"POST",credentials:"include",cache:"no-store",keepalive:!0,headers:{"Content-Type":"application/json"},body:JSON.stringify({presence_id:this.presenceId,state:this.state,site_key:this.options.siteKey||this.bootstrap?.default_site_key||void 0,requested_business_key:ne(this.options,this.bootstrap)||void 0,resolved_business_key:this.issuedSession?.resolved_business_key||void 0,display_name:this.options.displayName||void 0,page_url:typeof window>"u"?void 0:window.location.href,session_id:this.issuedSession?.session_id||void 0,session_mode:this.issuedSession?.session_mode||void 0,widget_anchor_number:this.issuedSession?.widget_anchor_number||void 0,browser_sip_username:this.issuedSession?.browser_sip_username||this.issuedSession?.sip_username||void 0,standby_mode:this.issuedSession?.standby_mode||void 0,standby_state:this.issuedSession?.standby_state||this.options.standbyState||void 0,selected_primary_account:this.options.selectedPrimaryAccount||void 0,selected_middle_layer_account:this.options.selectedMiddleLayerAccount||void 0,route_note:this.issuedSession?.route_note||void 0,visible:this.pageVisible,network_online:this.networkOnline,client_summary:this.buildPresenceEvidenceSummary(this.state,{trigger:e})})});if(!i.ok)throw new Error(`运行态心跳失败:${i.status}`);const s=await i.json().catch(()=>({}));this.presenceFailureCount=0,await this.handleServerSessionAction("touch-widget-presence",s)||this.syncAvailabilityState(),t&&await this.restartStandbyAfterConnectivity(`presence-${e}`)}catch(i){if(this.presenceFailureCount+=1,this.networkOnline===!1||Ve(i)||this.presenceFailureCount>=pt){const r=this.networkOnline===!1?"当前网络连接已断开,正在等待恢复。":"当前无法连接通话服务,正在自动尝试恢复。";this.setAvailabilityState("offline",r)}else this.setAvailabilityState("recovering","正在确认后台连接状态。");this.presenceFailureCount<=pt&&this.appendLog("widget 运行态心跳失败",{trigger:e,failure_count:this.presenceFailureCount,message:i?.message||String(i)})}}startPresenceTouch(){this.legacyOutboundOnly||(this.touchWidgetPresence("mount"),this.presenceTouchTimer||(this.presenceTouchTimer=window.setInterval(()=>{this.touchWidgetPresence("interval")},wi)))}async touchIssuedSession(e,t=null){if(!this.issuedSession?.session_id)return;const i=this.issuedSession.session_id;try{const s=await fetch(X(this.apiBaseUrl,"api/sip/touch-call-session"),{method:"POST",credentials:"include",cache:"no-store",keepalive:!0,headers:{"Content-Type":"application/json"},body:JSON.stringify({session_id:i,state:e,client_summary:this.buildClientEvidenceSummary(e,t)})});if(!s.ok){const n=new Error(`会话心跳失败:${s.status}`);throw n.status=s.status,n}const r=await s.json().catch(()=>({}));await this.handleServerSessionAction("touch-call-session",r)}catch(s){if(s?.status===404&&this.wasSessionRecentlyReleased(i))return;this.appendLog("网页通话会话心跳失败",{session_id:i,state:e,message:s?.message||String(s)}),s?.status===404&&this.handleMissingIssuedSession(i,e)}}async handleMissingIssuedSession(e,t){if(!(this.sessionRecoveryInFlight||this.issuedSession?.session_id!==e)){this.sessionRecoveryInFlight=(async()=>{this.stopSessionTouch(),this.appendLog("网页电话待机会话已失效,准备自动恢复。",{session_id:e,state:t}),this.setAvailabilityState("recovering","原待机会话已失效,正在自动恢复。"),this.issuedSession?.session_id===e&&(this.issuedSession=null),!this.legacyOutboundOnly&&["standby","registering","standbyRequesting","failed","idle","missed","rejected","ended"].includes(this.state)&&(this.setState("standbyRequesting","待机会话已失效,正在自动重新进入待机。"),await this.startStandby({force:!0}))})();try{await this.sessionRecoveryInFlight}finally{this.sessionRecoveryInFlight=null}}}async reportIssuedSessionEvent(e,t=null,i=null){if(!this.issuedSession?.session_id)return;const s=this.issuedSession.session_id,r=i||this.state;let n=null,o="";try{const d=await fetch(X(this.apiBaseUrl,"api/sip/report-call-session-event"),{method:"POST",credentials:"include",cache:"no-store",keepalive:!0,headers:{"Content-Type":"application/json"},body:JSON.stringify({session_id:s,source:"widget-runtime",event:e,state:r,detail:this.buildClientEvidenceSummary(r,t)})});if(!d.ok){n=d.status;const p=await d.json().catch(()=>({}));throw o=p?.detail?String(p.detail):"",new Error(o||`事件留证失败:${d.status}`)}}catch(d){await fetch(X(this.apiBaseUrl,"api/sip/report-call-session-event-delivery-failure"),{method:"POST",credentials:"include",cache:"no-store",keepalive:!0,headers:{"Content-Type":"application/json"},body:JSON.stringify({session_id:s,source:"widget-runtime",event:e,state:r,detail:this.buildClientEvidenceSummary(r,t),delivery_error:{status:typeof n=="number"?n:null,detail:o||"",message:d?.message||String(d)}})}).catch(()=>{}),this.appendLog("网页通话事件留证失败",{session_id:s,event:e,state:r,message:d?.message||String(d)})}}stopSessionTouch(){this.sessionTouchTimer&&(window.clearInterval(this.sessionTouchTimer),this.sessionTouchTimer=null)}syncSessionTouch(e){if(!(!!this.issuedSession?.session_id&&["connected","registered","dialing","ringing","answered","standby","incoming"].includes(e))){this.stopSessionTouch();return}this.touchIssuedSession(e),this.sessionTouchTimer||(this.sessionTouchTimer=window.setInterval(()=>{this.touchIssuedSession(this.state)},ft))}setState(e,t=""){this.state=e,this.refs.status.textContent=t||Q[e]||Q.idle,this.emitHostCallback("onStateChange",this.getSnapshot()),this.syncAvailabilityState(),this.render()}appendLog(e,t){const i=document.createElement("div");for(i.className="log",i.textContent=Si(e,t),this.refs.logs.prepend(i);this.refs.logs.children.length>8;)this.refs.logs.lastElementChild?.remove()}render(){const e=["requesting","standbyRequesting","registering","calling"].includes(this.state),t=["calling","connected"].includes(this.state),i=!!(this.client||this.issuedSession),s=!this.legacyOutboundOnly&&!i&&this.availabilityState!=="offline"&&["idle","failed","ended"].includes(this.state);this.refs.status&&(this.refs.status.className=["status",this.availabilityState==="offline"?"status--offline":this.availabilityState==="recovering"?"status--recovering":""].filter(Boolean).join(" ")),this.legacyOutboundOnly?(this.refs.primary.disabled=e,this.refs.primary.textContent=t?"通话进行中":"连接并呼叫",this.refs.secondary.disabled=!t&&!i,this.refs.secondary.textContent=t?"挂断并清理":"断开并清理"):(this.refs.primary.disabled=e||s||this.state==="connected",this.state==="incoming"?this.refs.primary.textContent="接听来电":e||s?this.availabilityState==="working"?this.refs.primary.textContent="正在建立呼叫":this.availabilityState==="preparing"?this.refs.primary.textContent="正在准备网页电话":this.refs.primary.textContent=this.state==="calling"?"通话进行中":"正在恢复网页电话":this.availabilityState==="offline"?this.refs.primary.textContent="网络恢复后再试":!i||["idle","failed","ended"].includes(this.state)?this.refs.primary.textContent="恢复网页电话待机":this.refs.primary.textContent="呼叫管理员",this.refs.secondary.disabled=!i,this.refs.secondary.textContent=this.state==="incoming"?"拒接来电":this.state==="connected"?"挂断并恢复待机":"断开电话");const r=this.options.siteKey||this.bootstrap?.default_site_key||"等待服务端返回",n=this.issuedSession?.resolved_business_key||ne(this.options,this.bootstrap)||"等待服务端返回";this.legacyOutboundOnly?this.refs.meta.textContent=`入站规则:${n};站点键:${r}`:this.refs.meta.textContent=`站点键:${r};网页电话会先进入可被叫待机;访客主动呼叫默认规则:${n}`,this.availabilityState==="offline"?this.refs.hint.textContent=this.networkOnline===!1?"当前浏览器网络已断开。系统会在网络恢复后自动尝试重连网页电话。":"当前无法连接通话服务。系统会继续自动恢复,暂时不要重复点击。":this.availabilityState==="preparing"?this.refs.hint.textContent="当前正在准备待机能力。只要页面在线,系统会自动进入可呼入、可呼出的网页电话状态。":this.availabilityState==="working"?this.refs.hint.textContent="当前正在切换到外呼链路,请等待振铃、接通或自动回到待机。":this.availabilityState==="recovering"?this.refs.hint.textContent="当前页面仍在线,系统正在自动恢复可被叫待机,不需要刷新页面或手动切换模式。":this.refs.hint.textContent=this.isMobileViewport?mi:"桌面网页可直接使用;若宿主站点启用了来源白名单或接入令牌,请按站点配置提供对应参数。"}async destroy(){await this.disconnectAndCleanup("destroy-widget"),typeof document<"u"&&this.visibilityHandler&&(document.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null),this.stopSessionTouch(),this.stopPresenceTouch(),this.stopTransportDisconnectGrace(),this.pendingIncomingCall=null,typeof window<"u"&&(this.onlineHandler&&(window.removeEventListener("online",this.onlineHandler),this.onlineHandler=null),this.offlineHandler&&(window.removeEventListener("offline",this.offlineHandler),this.offlineHandler=null)),this.e2eBridgeEnabled&&typeof window<"u"&&window.__embeddedCallWidgetE2E__&&delete window.__embeddedCallWidgetE2E__,this.shadowRoot.innerHTML=""}}function wt(a){return new Ii(a)}return typeof window<"u"&&(window.__EmbeddedCallWidgetRuntime__={createEmbeddedCallWidgetRuntime:wt}),ke.createEmbeddedCallWidgetRuntime=wt,Object.defineProperty(ke,Symbol.toStringTag,{value:"Module"}),ke})({});
121
+ `,this.refs.status=this.shadowRoot.querySelector('[data-role="status"]'),this.refs.availability=this.shadowRoot.querySelector('[data-role="availability"]'),this.refs.hint=this.shadowRoot.querySelector('[data-role="hint"]'),this.refs.meta=this.shadowRoot.querySelector('[data-role="meta"]'),this.refs.primary=this.shadowRoot.querySelector('[data-role="primary"]'),this.refs.secondary=this.shadowRoot.querySelector('[data-role="secondary"]'),this.refs.logs=this.shadowRoot.querySelector('[data-role="logs"]'),this.refs.audio=this.shadowRoot.querySelector('[data-role="audio"]'),this.refs.primary.addEventListener("click",()=>{this.handlePrimaryAction()}),this.refs.secondary.addEventListener("click",()=>{this.handleSecondaryAction()}),this.bindPageLifecycle(),this.registerE2EBridge(),this.startPresenceTouch(),this.render()}bindPageLifecycle(){typeof document>"u"||(this.visibilityHandler=()=>{if(this.pageVisible=document.visibilityState==="visible",document.visibilityState==="hidden"){this.reportIssuedSessionEvent("visibility-hidden",{visible:!1}),this.touchWidgetPresence("visibility-hidden"),this.appendLog("页面已切到后台",{state:this.state,advice:"回到前台后请先确认页面仍保持活动,再继续观察通话状态。"}),this.isMobileViewport&&["requesting","registering","calling","connected"].includes(this.state)&&(this.refs.status.textContent="页面已切到后台;手机网页回到前台后请确认通话仍在继续。");return}if(this.isMobileViewport&&(this.appendLog("页面已回到前台",{state:this.state}),["requesting","registering","calling","connected"].includes(this.state)&&(this.refs.status.textContent=Q[this.state]||Q.idle)),this.touchWidgetPresence("visibility-visible"),!this.legacyOutboundOnly&&["standby","registering","standbyRequesting","failed","idle","missed","rejected","ended"].includes(this.state)){const e=this.client?.getConnectionSnapshot?.()||null,t=!!(e?.connected&&(this.state!=="standby"||e?.registered));this.issuedSession?.session_id&&t?this.touchIssuedSession(this.state,{trigger:"visibility-visible"}):(this.appendLog("页面回到前台后检测到待机链路未处于健康状态,准备自动重建。",{state:this.state,has_session:!!this.issuedSession?.session_id,client_snapshot:e}),this.client||this.issuedSession?.session_id?this.disconnectAndCleanup("visibility-visible-unhealthy-client"):this.startStandby({force:!0}))}this.reportIssuedSessionEvent("visibility-visible",{visible:!0}),this.render()},document.addEventListener("visibilitychange",this.visibilityHandler),typeof window<"u"&&(this.onlineHandler=()=>{this.networkOnline=!0,this.setAvailabilityState("recovering","网络已恢复,正在重新连接网页电话。"),this.touchWidgetPresence("browser-online"),this.legacyOutboundOnly||this.restartStandbyAfterConnectivity("browser-online")},this.offlineHandler=()=>{this.networkOnline=!1,this.setAvailabilityState("offline","当前网络连接已断开,正在等待恢复。"),["incoming","connected"].includes(this.state)?this.render():this.setState("failed","当前网络连接已断开,正在等待恢复。")},window.addEventListener("online",this.onlineHandler),window.addEventListener("offline",this.offlineHandler)))}open(){this.mountNode.scrollIntoView({block:"nearest",inline:"nearest"})}beginLifecycleRun(){return this.lifecycleRunId+=1,this.lifecycleRunId}assertLifecycleRunCurrent(e){if(e!==this.lifecycleRunId)throw xi("网页电话已切换到新的连接流程,旧流程不再继续。")}emitHostCallback(e,t){const i=this.options?.[e];if(Ci(i))try{i(t)}catch(s){this.appendLog("宿主回调执行失败",{callback:e,message:s?.message||String(s)})}}getSnapshot(){return{mode:this.mode,state:this.state,sessionMode:this.issuedSession?.session_mode||null,sessionId:this.issuedSession?.session_id||null,siteKey:this.options.siteKey||this.bootstrap?.default_site_key||null,businessKey:this.issuedSession?.resolved_business_key||ne(this.options,this.bootstrap)||null,standbyMode:this.issuedSession?.standby_mode||null,standbyState:this.issuedSession?.standby_state||this.options.standbyState||null,widgetSipNumber:this.issuedSession?.widget_anchor_number||null,browserSipUsername:this.issuedSession?.browser_sip_username||this.issuedSession?.sip_username||null,routeNote:this.issuedSession?.route_note||null,incomingAnchors:Array.isArray(this.issuedSession?.incoming_anchors)?this.issuedSession.incoming_anchors:[],hasClient:!!this.client,hasIssuedSession:!!this.issuedSession?.session_id,primaryText:this.refs.primary?.textContent?.trim()||"",secondaryText:this.refs.secondary?.textContent?.trim()||"",statusText:this.refs.status?.textContent?.trim()||"",availabilityState:this.availabilityState,availabilityLabel:this.refs.availability?.textContent?.trim()||"",presenceId:this.presenceId,networkOnline:this.networkOnline,pendingIncomingCall:this.pendingIncomingCall}}registerE2EBridge(){if(!this.e2eBridgeEnabled||typeof window>"u")return;const e=window.location.hostname||"example.invalid";window.__embeddedCallWidgetE2E__={getSnapshot:()=>this.getSnapshot(),simulateIncomingCall:(t={})=>{const i=t.host||e,s=t.user||"2001";return this.pendingIncomingCall={displayName:t.displayName||"测试来电",user:s,host:i,direction:"inbound",uri:t.uri||`sip:${s}@${i}`,e2eSimulated:!0},this.setState("incoming"),this.emitHostCallback("onIncomingCall",this.pendingIncomingCall),this.getSnapshot()},answerIncomingCall:async()=>(await this.answerIncomingCall(),this.getSnapshot()),declineIncomingCall:async()=>(await this.declineIncomingCall(),this.getSnapshot()),hangupCall:async()=>(await this.hangupCall(),this.getSnapshot())}}async handlePrimaryAction(){if(this.availabilityState==="offline"){const e=this.networkOnline===!1?"当前网络连接已断开,正在尝试恢复,请稍后再试。":"当前无法连接通话服务,正在尝试恢复,请稍后再试。";this.appendLog("访客点击了离线中的 widget",{state:this.state,availability:this.availabilityState,message:e}),["incoming","connected"].includes(this.state)||this.setState("failed",e),this.render();return}if(this.state==="incoming"){await this.answerIncomingCall();return}if(this.legacyOutboundOnly){await this.connectAndCall();return}if(!this.client&&!this.issuedSession){await this.startStandby();return}await this.connectAndCall()}async handleSecondaryAction(){if(this.state==="incoming"){await this.declineIncomingCall();return}if(this.state==="connected"&&this.client){await this.hangupCall();return}await this.disconnectAndCleanup("manual-disconnect")}async connectAndCall(){if(["requesting","registering","calling","connected"].includes(this.state))return;const e=this.beginLifecycleRun();try{this.terminalFailureOverride="",this.clearAutoStandbyRecovery(),this.pendingLifecycleMode="outbound",this.setAvailabilityState("working","正在建立呼叫链路。"),!this.legacyOutboundOnly&&this.issuedSession?.session_mode==="widget_standby"&&(this.appendLog("访客主动呼叫前,先释放当前待机会话并切换到呼叫链路。",{widget_anchor_number:this.issuedSession?.widget_anchor_number||null,resolved_business_key:this.issuedSession?.resolved_business_key||ne(this.options,this.bootstrap)||null}),await this.disconnectAndCleanup("switch-to-outbound-call",{preserveLifecycleRun:!0,suppressStateReset:!0,suppressClientTeardownState:"switch-to-outbound-call"}),this.assertLifecycleRunCurrent(e)),this.setState("requesting"),await this.loadBootstrap(),this.assertLifecycleRunCurrent(e),this.turnIceServers=await this.fetchTurnIceServers(),this.assertLifecycleRunCurrent(e);const t=await this.fetchIssuedSession();this.assertLifecycleRunCurrent(e),await this.ensureClient(t),this.assertLifecycleRunCurrent(e),this.setState("registering"),await this.client.ensureReady(),this.assertLifecycleRunCurrent(e),this.setState("calling"),this.syncAvailabilityState(),await this.client.call(t.target),this.assertLifecycleRunCurrent(e),this.appendLog("已发起呼叫",{target:t.target,route_note:t.routeNote||null})}catch(t){if(this.pendingLifecycleMode=null,mt(t)){this.appendLog("已忽略过期的网页电话呼叫流程",{message:t?.message||String(t)});return}const i=t?.message||String(t);if(this.terminalFailureOverride=i,this.setState("failed",i),Ve(t)||this.networkOnline===!1?this.setAvailabilityState("offline",this.networkOnline===!1?"当前网络连接已断开,正在等待恢复。":"当前无法连接通话服务,正在自动尝试恢复。"):this.setAvailabilityState("recovering","呼叫失败,正在准备恢复网页电话待机。"),this.appendLog("连接并呼叫失败",{message:i}),await this.releaseIssuedSession("failed","connect-and-call-failed"),!this.legacyOutboundOnly){this.appendLog("呼叫失败后,网页电话将恢复待机。",{message:i}),await this.startStandby({force:!0});return}this.setState("failed",i)}}async startStandby(e={}){if(this.legacyOutboundOnly)throw new Error("当前 widget 仍处于 legacy outbound 兼容模式");const{force:t=!1}=e;if(!t&&["standbyRequesting","registering","incoming","connected"].includes(this.state))return;const i=this.beginLifecycleRun();try{this.clearAutoStandbyRecovery(),this.pendingLifecycleMode="standby",this.terminalFailureOverride="",this.setState("standbyRequesting"),this.setAvailabilityState("preparing","正在申请新的待机会话。"),await this.loadBootstrap(),this.assertLifecycleRunCurrent(i),this.turnIceServers=await this.fetchTurnIceServers(),this.assertLifecycleRunCurrent(i);const s=await this.fetchStandbySession();this.assertLifecycleRunCurrent(i),await this.ensureClient(s),this.assertLifecycleRunCurrent(i),this.setState("registering","待机会话已拿到,正在连接并执行 REGISTER。"),await this.client.ensureReady(),this.assertLifecycleRunCurrent(i),this.pendingLifecycleMode=null,this.setState("standby"),this.setAvailabilityState("ready","当前可被呼叫,也可由访客主动发起呼叫。"),this.appendLog("widget 已进入待机",{session_id:this.issuedSession?.session_id||null,route_business_key:this.issuedSession?.resolved_business_key||ne(this.options,this.bootstrap)||null,standby_mode:this.issuedSession?.standby_mode||null,widget_anchor_number:this.issuedSession?.widget_anchor_number||null,browser_sip_username:this.issuedSession?.browser_sip_username||this.issuedSession?.sip_username||null}),this.emitHostCallback("onStandbyReady",this.getSnapshot())}catch(s){if(mt(s)){this.appendLog("已忽略过期的网页电话待机流程",{message:s?.message||String(s)});return}this.pendingLifecycleMode=null;const r=s?.message||String(s);this.terminalFailureOverride=r,this.setState("failed",r),Ve(s)||this.networkOnline===!1?this.setAvailabilityState("offline",this.networkOnline===!1?"当前网络连接已断开,正在等待恢复。":"当前无法连接通话服务,正在自动尝试恢复。"):this.setAvailabilityState("recovering","当前未能完成待机注册,系统会继续自动恢复。"),this.appendLog("进入待机失败",{message:r}),await this.releaseIssuedSession("failed","standby-start-failed"),this.setState("failed",r),!this.legacyOutboundOnly&&this.networkOnline!==!1&&this.scheduleAutoStandbyRecovery("standby-start-failed")}}async ensureClient(e){const t={sip_username:e.sipUsername,sip_password:e.password,sip_domain:e.sipDomain,webrtc_url:e.transport,ice_servers:this.turnIceServers};this.client?.matchesAccount(t)||(this.client&&await this.client.destroy(),this.client=new hi(t,{remoteAudioElement:this.refs.audio,onStateChange:(i,s)=>{this.handleClientState(i,s)},onError:i=>this.appendLog("SIP 客户端错误",{message:i?.message||String(i)})}))}async handleClientState(e,t={}){if(this.syncSessionTouch(e),this.reportIssuedSessionEvent(e,t,e),(e==="connected"||e==="registered"||e==="ringing"||e==="dialing"||e==="answered")&&await this.clearTransportDisconnectGrace(!0),e==="registered"){this.pendingLifecycleMode=null,this.issuedSession?.session_mode==="widget_standby"?(this.setState("standby"),this.setAvailabilityState("ready","当前可被呼叫,也可由访客主动发起呼叫。")):this.setState("registering","REGISTER 已完成,正在准备发起呼叫。");return}if(e==="incoming"){this.pendingLifecycleMode=null,this.pendingIncomingCall=t&&typeof t=="object"?{...t}:null,this.setState("incoming"),this.setAvailabilityState("ready","已有新来电,请直接接听或拒接。"),this.appendLog("收到新的来电",this.pendingIncomingCall),this.emitHostCallback("onIncomingCall",this.pendingIncomingCall);return}if(e==="ringing"||e==="dialing"){this.setState("calling",t?.message||Q.calling);return}if(e==="answered"){this.pendingLifecycleMode=null,this.terminalFailureOverride="";const i=this.pendingIncomingCall||t||null;this.setState("connected"),this.setAvailabilityState("ready","当前通话已接通。"),this.emitHostCallback("onCallAnswered",i);return}if(e==="terminated"){if(this.issuedSession?.session_mode==="widget_standby"){await this.handleStandbyTermination(t);return}t&&typeof t=="object"&&(t.source==="invite-response"||t.source==="invite-exception"||t.code||t.status||t.stage)&&(this.terminalFailureOverride=t?.message||Q.failed,this.setState("failed",this.terminalFailureOverride)),await this.finalizeAfterTermination(t?.hangupSource||"terminated");return}if(e==="failed"){if(this.isExpectedClientTeardown())return;this.pendingLifecycleMode=null,await this.clearTransportDisconnectGrace(!1),this.stopSessionTouch(),this.terminalFailureOverride=t?.message||Q.failed,this.setState("failed",this.terminalFailureOverride),this.setAvailabilityState("recovering","通话链路异常断开,系统会继续自动恢复。"),await this.releaseIssuedSession("failed","sip-client-failed");return}if(e==="disconnected"||e==="unregistered"){if(this.isExpectedClientTeardown()||this.isExpectedTermination()||!this.issuedSession?.session_id&&this.pendingLifecycleMode==="standby")return;if(this.stopSessionTouch(),["calling","connected"].includes(this.state)){this.setState("connected","SIP 信令短暂中断,正在等待恢复。"),this.setAvailabilityState("recovering","通话信令短暂中断,正在等待恢复。"),this.scheduleTransportDisconnectGrace(e);return}const s=this.terminalFailureOverride||this.client?.lastFailureMeta?.message||null;if(s){this.terminalFailureOverride=s,this.setState("failed",s);return}if(this.state==="failed")return;this.state!=="idle"&&this.state!=="ended"&&(this.pendingLifecycleMode=null,this.setState("ended"),this.setAvailabilityState("recovering","通话已结束,正在恢复网页电话待机。"),this.scheduleAutoStandbyRecovery(`client-${e}`))}}stopTransportDisconnectGrace(){this.transportDisconnectTimer&&(window.clearTimeout(this.transportDisconnectTimer),this.transportDisconnectTimer=null),this.transportDisconnectContext=null}sameTransportDisconnectContext(e){return!e||!this.issuedSession?.session_id?!1:e.sessionId===this.issuedSession.session_id}async clearTransportDisconnectGrace(e){const t=this.transportDisconnectContext,i=!!(e&&t?.reported&&this.sameTransportDisconnectContext(t));this.stopTransportDisconnectGrace(),i&&(await this.reportIssuedSessionEvent("transport-restored",{disconnectDurationMs:Date.now()-(t.startedAt||Date.now()),graceMs:Se},this.state),this.appendLog("网页通话信令已恢复",{disconnectDurationMs:Date.now()-(t.startedAt||Date.now()),graceMs:Se}))}scheduleTransportDisconnectGrace(e){if(!this.issuedSession?.session_id)return;this.stopTransportDisconnectGrace();const t={sessionId:this.issuedSession.session_id,startedAt:Date.now(),reported:!1,state:e};this.transportDisconnectContext=t,this.transportDisconnectTimer=window.setTimeout(()=>{this.transportDisconnectTimer=null,this.sameTransportDisconnectContext(t)&&(t.reported=!0,this.reportIssuedSessionEvent("transport-disconnected",{state:e,graceMs:Se},this.state),this.appendLog("网页通话信令断开超过宽限窗口",{graceMs:Se,state:e}))},Se)}async finalizeAfterTermination(e){if(!this.finalizing){this.beginLifecycleRun(),this.finalizing=!0;try{if(this.pendingLifecycleMode=null,this.stopSessionTouch(),this.stopTransportDisconnectGrace(),await this.releaseIssuedSession("released",e),this.client&&(await this.client.destroy(),this.client=null),!this.legacyOutboundOnly){this.pendingLifecycleMode="standby",this.setState("standbyRequesting","上一通通话已结束,正在自动回到待机。"),this.setAvailabilityState("preparing","页面仍在线,正在自动回到待机。"),this.appendLog("通话已结束,网页电话正在自动回到待机。",{reason:e}),await this.startStandby({force:!0});return}const t=this.terminalFailureOverride||(this.state==="failed"?this.refs.status?.textContent?.trim()||Q.failed:"");t?this.setState("failed",t):this.setState("ended"),this.appendLog("通话已结束并完成清理",{reason:e})}finally{this.finalizing=!1}}}async handleStandbyTermination(e={}){this.stopTransportDisconnectGrace();const t=this.state,i=e?.hangupSource||"terminated";t==="incoming"?i==="local"?(this.setState("rejected"),this.setAvailabilityState("ready","当前来电已拒接,widget 仍保持可用。"),this.emitHostCallback("onCallRejected",this.pendingIncomingCall||e||null)):(this.setState("missed"),this.setAvailabilityState("ready","当前来电未接听,widget 仍保持可用。"),this.emitHostCallback("onCallMissed",this.pendingIncomingCall||e||null)):(this.setState("standby","上一通来电已结束,继续等待下一通来电。"),this.setAvailabilityState("ready","当前可被呼叫,也可由访客主动发起呼叫。")),this.appendLog("待机会话中的来电已结束",{hangupSource:i,previousState:t}),this.pendingIncomingCall=null}async answerIncomingCall(){if(this.state==="incoming"){if(this.e2eBridgeEnabled&&this.pendingIncomingCall?.e2eSimulated){this.setState("connected","测试桥已模拟接听当前来电。"),this.emitHostCallback("onCallAnswered",this.pendingIncomingCall);return}if(!this.client&&this.e2eBridgeEnabled){this.setState("connected","测试桥已模拟接听当前来电。"),this.emitHostCallback("onCallAnswered",this.pendingIncomingCall);return}if(!this.client)throw new Error("当前没有可接听的 SIP 会话");await this.client.answer()}}async declineIncomingCall(){if(this.state==="incoming"){if(this.e2eBridgeEnabled&&this.pendingIncomingCall?.e2eSimulated){this.setState("rejected"),this.emitHostCallback("onCallRejected",this.pendingIncomingCall),this.pendingIncomingCall=null;return}if(!this.client&&this.e2eBridgeEnabled){this.setState("rejected"),this.emitHostCallback("onCallRejected",this.pendingIncomingCall),this.pendingIncomingCall=null;return}if(!this.client)throw new Error("当前没有可拒接的 SIP 会话");await this.client.decline()}}async hangupCall(){if(this.e2eBridgeEnabled&&this.pendingIncomingCall?.e2eSimulated){this.legacyOutboundOnly?this.setState("ended"):this.setState("standby","测试桥已模拟挂断,网页电话已恢复待机。"),this.pendingIncomingCall=null;return}if(!this.client&&this.e2eBridgeEnabled){this.legacyOutboundOnly?this.setState("ended"):this.setState("standby","测试桥已模拟挂断,网页电话已恢复待机。"),this.pendingIncomingCall=null;return}this.client&&(this.expectTermination("hangup-call"),this.suppressExpectedClientTeardown("hangup-call"),await this.client.hangup())}async disconnectAndCleanup(e,t={}){const{preserveLifecycleRun:i=!1,suppressStateReset:s=!1,suppressClientTeardownState:r=null}=t;i||this.beginLifecycleRun(),r&&this.suppressExpectedClientTeardown(r),this.stopSessionTouch(),this.stopTransportDisconnectGrace(),this.reportIssuedSessionEvent("manual-disconnect",{reason:e},this.state),this.pendingIncomingCall=null,this.terminalFailureOverride="",this.client&&(await this.client.destroy(),this.client=null),await this.releaseIssuedSession("released",e),s||(this.pendingLifecycleMode=null,this.setState("idle"),this.setAvailabilityState("recovering","网页电话已断开,系统正在自动恢复待机。"),this.appendLog("连接已断开",{reason:e}),!this.legacyOutboundOnly&&this.networkOnline!==!1&&this.scheduleAutoStandbyRecovery(e))}async loadBootstrap(){if(this.bootstrap)return this.bootstrap;const e=await fetch(X(this.apiBaseUrl,"api/sip/embedded-call-demo-bootstrap"),{credentials:"include",cache:"no-store"});if(!e.ok)throw new Error(`默认模式读取失败:${e.status}`);return this.bootstrap=await e.json(),this.bootstrap}async fetchTurnIceServers(){const e=await fetch(X(this.apiBaseUrl,"api/sip/webrtc-turn-credentials"),{credentials:"include",cache:"no-store"});if(!e.ok)return this.appendLog("TURN 凭证获取失败,继续尝试直连",{status:e.status}),[];const t=await e.json();return Array.isArray(t?.ice_servers)?t.ice_servers:[]}async fetchIssuedSession(){const e=this.options.siteKey||this.bootstrap?.default_site_key||"",t=ne(this.options,this.bootstrap);if(!e)throw new Error("当前 SDK 模式必须明确提供 siteKey,或让服务端下发默认站点键");const i={site_key:e,standby_state:this.options.standbyState||void 0,selected_primary_account:this.options.selectedPrimaryAccount||void 0,selected_middle_layer_account:this.options.selectedMiddleLayerAccount||void 0,display_name:this.options.displayName||void 0,page_url:window.location.href,access_token:this.options.accessToken||void 0};t&&(i.business_key=t);const s=await fetch(X(this.apiBaseUrl,"api/sip/issue-call-session"),{method:"POST",credentials:"include",cache:"no-store",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});if(!s.ok){const n=await s.json().catch(()=>({}));throw new Error(n?.detail||`会话签发失败:${s.status}`)}const r=await s.json();return this.issuedSession=r,this.reportIssuedSessionEvent("issued-session-fetched",{sessionId:r.session_id,targetExtension:r.target_extension,dialTargetExtension:r.dial_target_extension||r.target_extension,resolvedTargetInternalNumbers:r.resolved_target_internal_numbers||[],resolvedBusinessKey:r.resolved_business_key||null},"requesting"),this.appendLog("已获取服务端签发会话",{session_id:r.session_id,target_extension:r.target_extension,dial_target_extension:r.dial_target_extension||r.target_extension||null,resolved_target_internal_numbers:r.resolved_target_internal_numbers||[],resolved_business_key:r.resolved_business_key||null,resolved_route_mode:r.resolved_route_mode||null,route_note:r.route_note||null}),{sessionMode:r.session_mode||"outbound",sipUsername:r.sip_username,sipDomain:r.sip_domain,password:r.sip_password,transport:gt({apiBaseUrl:this.apiBaseUrl,transportBaseUrl:this.options.transportBaseUrl||"",transportUrl:r.ws_server}),target:r.dial_target_extension||r.target_extension,routeNote:r.route_note||""}}async fetchStandbySession(){const e=this.options.siteKey||this.bootstrap?.default_site_key||"",t=ne(this.options)||null;if(!e)throw new Error("当前 SDK 待机模式必须明确提供 siteKey,或让服务端下发默认站点键");const i={site_key:e,standby_state:this.options.standbyState||void 0,selected_primary_account:this.options.selectedPrimaryAccount||void 0,selected_middle_layer_account:this.options.selectedMiddleLayerAccount||void 0,display_name:this.options.displayName||void 0,page_url:window.location.href,access_token:this.options.accessToken||void 0};t&&(i.business_key=t);const s=await fetch(X(this.apiBaseUrl,"api/sip/issue-widget-standby-session"),{method:"POST",credentials:"include",cache:"no-store",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});if(!s.ok){const n=await s.json().catch(()=>({}));throw new Error(n?.detail||`待机会话签发失败:${s.status}`)}const r=await s.json();return this.issuedSession=r,this.reportIssuedSessionEvent("standby-session-fetched",{sessionId:r.session_id,resolvedBusinessKey:r.resolved_business_key||null,standbyMode:r.standby_mode||null},"standbyRequesting"),this.appendLog("已获取 widget 待机会话",{session_id:r.session_id,resolved_business_key:r.resolved_business_key||null,standby_mode:r.standby_mode||null,standby_state:r.standby_state||null,widget_anchor_number:r.widget_anchor_number||null,browser_sip_username:r.browser_sip_username||r.sip_username||null,route_note:r.route_note||null}),{sessionMode:r.session_mode||"widget_standby",sipUsername:r.sip_username,sipDomain:r.sip_domain,password:r.sip_password,transport:gt({apiBaseUrl:this.apiBaseUrl,transportBaseUrl:this.options.transportBaseUrl||"",transportUrl:r.ws_server}),target:null,routeNote:r.route_note||""}}async releaseIssuedSession(e,t){if(!this.issuedSession?.session_id)return;const i=this.issuedSession.session_id;this.markRecentlyReleasedSession(i,t),this.issuedSession=null,await fetch(X(this.apiBaseUrl,"api/sip/release-call-session"),{method:"POST",credentials:"include",cache:"no-store",headers:{"Content-Type":"application/json"},body:JSON.stringify({session_id:i,status:e,reason:t,client_summary:this.buildClientEvidenceSummary(e,{releaseReason:t})})}).catch(()=>{})}buildClientEvidenceSummary(e=null,t=null){const i={runtimeMode:"embedded-widget-runtime",widgetVersion:"2026-03-29-runtime-evidence",widgetMode:this.mode,sessionMode:this.issuedSession?.session_mode||null,state:e||this.state,visible:typeof document>"u"?!0:document.visibilityState==="visible",pageUrl:typeof window>"u"?null:window.location.href,connectionSnapshot:this.client?.getConnectionSnapshot?.()||null};return t&&typeof t=="object"&&(i.eventDetail=t),i}buildPresenceEvidenceSummary(e=null,t=null){return{...this.buildClientEvidenceSummary(e,t),presenceId:this.presenceId,availabilityState:this.availabilityState,availabilityDetail:this.availabilityDetail,networkOnline:this.networkOnline,pageVisible:this.pageVisible}}setAvailabilityState(e,t=""){this.availabilityState=e,this.availabilityDetail=t||this.availabilityDetail,this.refs.availability&&(this.refs.availability.textContent=t?`${De[e]||De.recovering} · ${t}`:De[e]||De.recovering,this.refs.availability.className=`availability availability--${e}`),this.render()}suppressExpectedClientTeardown(e,t=vi){this.expectedClientTeardown={reason:e,expiresAt:Date.now()+t}}expectTermination(e,t=1e4){this.expectedTermination={reason:e,expiresAt:Date.now()+t}}clearExpectedTermination(){this.expectedTermination=null}isExpectedTermination(){const e=this.expectedTermination;return e?e.expiresAt<=Date.now()?(this.expectedTermination=null,!1):!0:!1}isExpectedClientTeardown(){const e=this.expectedClientTeardown;return e?e.expiresAt<=Date.now()?(this.expectedClientTeardown=null,!1):!0:!1}clearAutoStandbyRecovery(){this.autoStandbyRecoveryTimer&&(window.clearTimeout(this.autoStandbyRecoveryTimer),this.autoStandbyRecoveryTimer=null)}scheduleAutoStandbyRecovery(e,t=yi){this.legacyOutboundOnly||this.networkOnline===!1||this.autoStandbyRecoveryTimer||this.client||this.issuedSession?.session_id||this.finalizing||this.pendingLifecycleMode==="outbound"||this.sessionRecoveryInFlight||this.connectivityRecoveryInFlight||["requesting","standbyRequesting","registering","calling","incoming","connected"].includes(this.state)||(this.autoStandbyRecoveryTimer=window.setTimeout(()=>{this.autoStandbyRecoveryTimer=null,!(this.legacyOutboundOnly||this.networkOnline===!1)&&(this.client||this.issuedSession?.session_id||this.finalizing||this.pendingLifecycleMode==="outbound"||(this.appendLog("检测到页面当前不在通话中,开始自动恢复待机。",{reason:e}),this.startStandby({force:!0})))},t))}pruneRecentlyReleasedSessions(){const e=Date.now();for(const[t,i]of this.recentlyReleasedSessions.entries())(i?.expiresAt||0)<=e&&this.recentlyReleasedSessions.delete(t)}markRecentlyReleasedSession(e,t){e&&(this.pruneRecentlyReleasedSessions(),this.recentlyReleasedSessions.set(e,{reason:t,expiresAt:Date.now()+bi}))}wasSessionRecentlyReleased(e){return e?(this.pruneRecentlyReleasedSessions(),this.recentlyReleasedSessions.has(e)):!1}syncAvailabilityState(){if(this.networkOnline===!1){this.setAvailabilityState("offline","当前网络连接已断开,正在等待恢复。");return}if(this.pendingLifecycleMode==="outbound"&&["requesting","registering","calling"].includes(this.state)){this.setAvailabilityState("working",this.state==="calling"?"正在等待对端振铃或接听。":"正在建立呼叫链路。");return}if(["requesting","standbyRequesting","registering"].includes(this.state)){this.setAvailabilityState("preparing","页面仍在线,正在建立网页电话连接。");return}if(!this.legacyOutboundOnly&&!this.issuedSession?.session_id&&!this.pendingLifecycleMode&&!["calling","connected","incoming"].includes(this.state)){this.setAvailabilityState("recovering","页面仍在线,正在自动恢复待机。"),this.scheduleAutoStandbyRecovery("missing-issued-session");return}this.setAvailabilityState("ready","当前可被呼叫,也可由访客主动发起呼叫。")}stopPresenceTouch(){this.presenceTouchTimer&&(window.clearInterval(this.presenceTouchTimer),this.presenceTouchTimer=null)}async restartStandbyAfterConnectivity(e){if(!(this.legacyOutboundOnly||this.connectivityRecoveryInFlight)&&!["connected","incoming","calling","requesting","standbyRequesting","registering"].includes(this.state)){this.connectivityRecoveryInFlight=(async()=>{this.appendLog("检测到连通性已恢复,准备重建网页电话待机。",{trigger:e,state:this.state,hasSession:!!this.issuedSession?.session_id}),(this.client||this.issuedSession?.session_id)&&await this.disconnectAndCleanup(`connectivity-restored-${e}`),await this.startStandby({force:!0})})();try{await this.connectivityRecoveryInFlight}finally{this.connectivityRecoveryInFlight=null}}}async handleServerSessionAction(e,t={}){return this.legacyOutboundOnly||String(t?.session_action||"").trim().toLowerCase()!=="restart_standby"||this.sessionRecoveryInFlight||this.connectivityRecoveryInFlight?!1:["requesting","standbyRequesting","calling","incoming","connected"].includes(this.state)?(this.appendLog("服务端已要求重建待机,但当前阶段暂不主动打断。",{source:e,state:this.state,session_action_reason:t?.session_action_reason||null,session_action_detail:t?.session_action_detail||null}),!1):(this.appendLog("服务端要求当前页面放弃旧锚点并重新进入待机。",{source:e,session_action_reason:t?.session_action_reason||null,session_action_detail:t?.session_action_detail||null,resolved_session_id:t?.resolved_session_id||null,resolved_widget_anchor_number:t?.resolved_widget_anchor_number||null,resolved_browser_sip_username:t?.resolved_browser_sip_username||null}),this.setAvailabilityState("recovering",t?.session_action_detail||"当前锚点已失效,正在自动切换到新的待机线路。"),await this.disconnectAndCleanup(`server-session-action-${t?.session_action_reason||e}`,{preserveLifecycleRun:!0}),await this.startStandby({force:!0}),!0)}async touchWidgetPresence(e="interval"){if(this.legacyOutboundOnly)return;this.networkOnline=typeof navigator>"u"?!0:navigator.onLine!==!1,this.pageVisible=typeof document>"u"?!0:document.visibilityState==="visible";const t=this.availabilityState==="offline";try{const i=await fetch(X(this.apiBaseUrl,"api/sip/touch-widget-presence"),{method:"POST",credentials:"include",cache:"no-store",keepalive:!0,headers:{"Content-Type":"application/json"},body:JSON.stringify({presence_id:this.presenceId,state:this.state,site_key:this.options.siteKey||this.bootstrap?.default_site_key||void 0,requested_business_key:ne(this.options,this.bootstrap)||void 0,resolved_business_key:this.issuedSession?.resolved_business_key||void 0,display_name:this.options.displayName||void 0,page_url:typeof window>"u"?void 0:window.location.href,session_id:this.issuedSession?.session_id||void 0,session_mode:this.issuedSession?.session_mode||void 0,widget_anchor_number:this.issuedSession?.widget_anchor_number||void 0,browser_sip_username:this.issuedSession?.browser_sip_username||this.issuedSession?.sip_username||void 0,standby_mode:this.issuedSession?.standby_mode||void 0,standby_state:this.issuedSession?.standby_state||this.options.standbyState||void 0,selected_primary_account:this.options.selectedPrimaryAccount||void 0,selected_middle_layer_account:this.options.selectedMiddleLayerAccount||void 0,route_note:this.issuedSession?.route_note||void 0,visible:this.pageVisible,network_online:this.networkOnline,client_summary:this.buildPresenceEvidenceSummary(this.state,{trigger:e})})});if(!i.ok)throw new Error(`运行态心跳失败:${i.status}`);const s=await i.json().catch(()=>({}));this.presenceFailureCount=0,await this.handleServerSessionAction("touch-widget-presence",s)||this.syncAvailabilityState(),t&&await this.restartStandbyAfterConnectivity(`presence-${e}`)}catch(i){if(this.presenceFailureCount+=1,this.networkOnline===!1||Ve(i)||this.presenceFailureCount>=pt){const r=this.networkOnline===!1?"当前网络连接已断开,正在等待恢复。":"当前无法连接通话服务,正在自动尝试恢复。";this.setAvailabilityState("offline",r)}else this.setAvailabilityState("recovering","正在确认后台连接状态。");this.presenceFailureCount<=pt&&this.appendLog("widget 运行态心跳失败",{trigger:e,failure_count:this.presenceFailureCount,message:i?.message||String(i)})}}startPresenceTouch(){this.legacyOutboundOnly||(this.touchWidgetPresence("mount"),this.presenceTouchTimer||(this.presenceTouchTimer=window.setInterval(()=>{this.touchWidgetPresence("interval")},wi)))}async touchIssuedSession(e,t=null){if(!this.issuedSession?.session_id)return;const i=this.issuedSession.session_id;try{const s=await fetch(X(this.apiBaseUrl,"api/sip/touch-call-session"),{method:"POST",credentials:"include",cache:"no-store",keepalive:!0,headers:{"Content-Type":"application/json"},body:JSON.stringify({session_id:i,state:e,client_summary:this.buildClientEvidenceSummary(e,t)})});if(!s.ok){const n=new Error(`会话心跳失败:${s.status}`);throw n.status=s.status,n}const r=await s.json().catch(()=>({}));await this.handleServerSessionAction("touch-call-session",r)}catch(s){if(s?.status===404&&this.wasSessionRecentlyReleased(i))return;this.appendLog("网页通话会话心跳失败",{session_id:i,state:e,message:s?.message||String(s)}),s?.status===404&&this.handleMissingIssuedSession(i,e)}}async handleMissingIssuedSession(e,t){if(!(this.sessionRecoveryInFlight||this.issuedSession?.session_id!==e)){this.sessionRecoveryInFlight=(async()=>{this.stopSessionTouch(),this.appendLog("网页电话待机会话已失效,准备自动恢复。",{session_id:e,state:t}),this.setAvailabilityState("recovering","原待机会话已失效,正在自动恢复。"),this.issuedSession?.session_id===e&&(this.issuedSession=null),!this.legacyOutboundOnly&&["standby","registering","standbyRequesting","failed","idle","missed","rejected","ended"].includes(this.state)&&(this.setState("standbyRequesting","待机会话已失效,正在自动重新进入待机。"),await this.startStandby({force:!0}))})();try{await this.sessionRecoveryInFlight}finally{this.sessionRecoveryInFlight=null}}}async reportIssuedSessionEvent(e,t=null,i=null){if(!this.issuedSession?.session_id)return;const s=this.issuedSession.session_id,r=i||this.state;let n=null,o="";try{const d=await fetch(X(this.apiBaseUrl,"api/sip/report-call-session-event"),{method:"POST",credentials:"include",cache:"no-store",keepalive:!0,headers:{"Content-Type":"application/json"},body:JSON.stringify({session_id:s,source:"widget-runtime",event:e,state:r,detail:this.buildClientEvidenceSummary(r,t)})});if(!d.ok){n=d.status;const p=await d.json().catch(()=>({}));throw o=p?.detail?String(p.detail):"",new Error(o||`事件留证失败:${d.status}`)}}catch(d){await fetch(X(this.apiBaseUrl,"api/sip/report-call-session-event-delivery-failure"),{method:"POST",credentials:"include",cache:"no-store",keepalive:!0,headers:{"Content-Type":"application/json"},body:JSON.stringify({session_id:s,source:"widget-runtime",event:e,state:r,detail:this.buildClientEvidenceSummary(r,t),delivery_error:{status:typeof n=="number"?n:null,detail:o||"",message:d?.message||String(d)}})}).catch(()=>{}),this.appendLog("网页通话事件留证失败",{session_id:s,event:e,state:r,message:d?.message||String(d)})}}stopSessionTouch(){this.sessionTouchTimer&&(window.clearInterval(this.sessionTouchTimer),this.sessionTouchTimer=null)}syncSessionTouch(e){if(!(!!this.issuedSession?.session_id&&["connected","registered","dialing","ringing","answered","standby","incoming"].includes(e))){this.stopSessionTouch();return}this.touchIssuedSession(e),this.sessionTouchTimer||(this.sessionTouchTimer=window.setInterval(()=>{this.touchIssuedSession(this.state)},ft))}setState(e,t=""){this.state=e,this.refs.status.textContent=t||Q[e]||Q.idle,this.syncAvailabilityState(),this.render(),this.emitHostCallback("onStateChange",this.getSnapshot())}appendLog(e,t){const i=document.createElement("div");for(i.className="log",i.textContent=Si(e,t),this.refs.logs.prepend(i);this.refs.logs.children.length>8;)this.refs.logs.lastElementChild?.remove()}render(){const e=["requesting","standbyRequesting","registering","calling"].includes(this.state),t=["calling","connected"].includes(this.state),i=!!(this.client||this.issuedSession),s=!this.legacyOutboundOnly&&!i&&this.availabilityState!=="offline"&&["idle","failed","ended"].includes(this.state);this.refs.status&&(this.refs.status.className=["status",this.availabilityState==="offline"?"status--offline":this.availabilityState==="recovering"?"status--recovering":""].filter(Boolean).join(" ")),this.legacyOutboundOnly?(this.refs.primary.disabled=e,this.refs.primary.textContent=t?"通话进行中":"连接并呼叫",this.refs.secondary.disabled=!t&&!i,this.refs.secondary.textContent=t?"挂断并清理":"断开并清理"):(this.refs.primary.disabled=e||s||this.state==="connected",this.state==="incoming"?this.refs.primary.textContent="接听来电":this.state==="connected"?this.refs.primary.textContent="通话进行中":e||s?this.availabilityState==="working"?this.refs.primary.textContent="正在建立呼叫":this.availabilityState==="preparing"?this.refs.primary.textContent="正在准备网页电话":this.refs.primary.textContent=this.state==="calling"?"通话进行中":"正在恢复网页电话":this.availabilityState==="offline"?this.refs.primary.textContent="网络恢复后再试":!i||["idle","failed","ended"].includes(this.state)?this.refs.primary.textContent="恢复网页电话待机":this.refs.primary.textContent="呼叫管理员",this.refs.secondary.disabled=!i,this.refs.secondary.textContent=this.state==="incoming"?"拒接来电":this.state==="connected"?"挂断并恢复待机":"断开电话");const r=this.options.siteKey||this.bootstrap?.default_site_key||"等待服务端返回",n=this.issuedSession?.resolved_business_key||ne(this.options,this.bootstrap)||"等待服务端返回";this.legacyOutboundOnly?this.refs.meta.textContent=`入站规则:${n};站点键:${r}`:this.refs.meta.textContent=`站点键:${r};网页电话会先进入可被叫待机;访客主动呼叫默认规则:${n}`,this.availabilityState==="offline"?this.refs.hint.textContent=this.networkOnline===!1?"当前浏览器网络已断开。系统会在网络恢复后自动尝试重连网页电话。":"当前无法连接通话服务。系统会继续自动恢复,暂时不要重复点击。":this.availabilityState==="preparing"?this.refs.hint.textContent="当前正在准备待机能力。只要页面在线,系统会自动进入可呼入、可呼出的网页电话状态。":this.availabilityState==="working"?this.refs.hint.textContent="当前正在切换到外呼链路,请等待振铃、接通或自动回到待机。":this.availabilityState==="recovering"?this.refs.hint.textContent="当前页面仍在线,系统正在自动恢复可被叫待机,不需要刷新页面或手动切换模式。":this.refs.hint.textContent=this.isMobileViewport?mi:"桌面网页可直接使用;若宿主站点启用了来源白名单或接入令牌,请按站点配置提供对应参数。"}async destroy(){await this.disconnectAndCleanup("destroy-widget"),typeof document<"u"&&this.visibilityHandler&&(document.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null),this.stopSessionTouch(),this.stopPresenceTouch(),this.stopTransportDisconnectGrace(),this.pendingIncomingCall=null,typeof window<"u"&&(this.onlineHandler&&(window.removeEventListener("online",this.onlineHandler),this.onlineHandler=null),this.offlineHandler&&(window.removeEventListener("offline",this.offlineHandler),this.offlineHandler=null)),this.e2eBridgeEnabled&&typeof window<"u"&&window.__embeddedCallWidgetE2E__&&delete window.__embeddedCallWidgetE2E__,this.shadowRoot.innerHTML=""}}function wt(a){return new Ii(a)}return typeof window<"u"&&(window.__EmbeddedCallWidgetRuntime__={createEmbeddedCallWidgetRuntime:wt}),ke.createEmbeddedCallWidgetRuntime=wt,Object.defineProperty(ke,Symbol.toStringTag,{value:"Module"}),ke})({});
@@ -30,7 +30,7 @@ class pe extends le {
30
30
  super(e || "An error occurred during state transition.");
31
31
  }
32
32
  }
33
- class Rt {
33
+ class xt {
34
34
  /** @internal */
35
35
  constructor(e) {
36
36
  this.incomingAckRequest = e;
@@ -40,7 +40,7 @@ class Rt {
40
40
  return this.incomingAckRequest.message;
41
41
  }
42
42
  }
43
- class xt {
43
+ class Rt {
44
44
  /** @internal */
45
45
  constructor(e) {
46
46
  this.incomingByeRequest = e;
@@ -1873,7 +1873,7 @@ class de {
1873
1873
  function ot(a) {
1874
1874
  return a === "application/sdp" ? "session" : "render";
1875
1875
  }
1876
- function Re(a) {
1876
+ function xe(a) {
1877
1877
  const e = typeof a == "string" ? a : a.body, t = typeof a == "string" ? "application/sdp" : a.contentType;
1878
1878
  return { contentDisposition: ot(t), contentType: t, content: e };
1879
1879
  }
@@ -2454,7 +2454,7 @@ class ge {
2454
2454
  sessionDescriptionHandlerModifiers: this.pendingReinviteAck ? this._sessionDescriptionHandlerModifiersReInvite : this._sessionDescriptionHandlerModifiers
2455
2455
  };
2456
2456
  if (this.delegate && this.delegate.onAck) {
2457
- const s = new Rt(e);
2457
+ const s = new xt(e);
2458
2458
  this.delegate.onAck(s);
2459
2459
  }
2460
2460
  switch (this.pendingReinviteAck = !1, t.signalingState) {
@@ -2497,7 +2497,7 @@ class ge {
2497
2497
  return;
2498
2498
  }
2499
2499
  if (this.delegate && this.delegate.onBye) {
2500
- const t = new xt(e);
2500
+ const t = new Rt(e);
2501
2501
  this.delegate.onBye(t);
2502
2502
  } else
2503
2503
  e.accept();
@@ -2677,7 +2677,7 @@ class ge {
2677
2677
  getOffer(e) {
2678
2678
  const t = this.setupSessionDescriptionHandler(), i = e.sessionDescriptionHandlerOptions, s = e.sessionDescriptionHandlerModifiers;
2679
2679
  try {
2680
- return t.getDescription(i, s).then((r) => Re(r)).catch((r) => {
2680
+ return t.getDescription(i, s).then((r) => xe(r)).catch((r) => {
2681
2681
  this.logger.error("Session.getOffer: SDH getDescription rejected...");
2682
2682
  const n = r instanceof Error ? r : new Error("Session.getOffer unknown error.");
2683
2683
  throw this.logger.error(n.message), n;
@@ -2749,7 +2749,7 @@ class ge {
2749
2749
  return this.logger.error(o.message), Promise.reject(o);
2750
2750
  }
2751
2751
  try {
2752
- return i.setDescription(e.content, s, r).then(() => i.getDescription(s, r)).then((n) => Re(n)).catch((n) => {
2752
+ return i.setDescription(e.content, s, r).then(() => i.getDescription(s, r)).then((n) => xe(n)).catch((n) => {
2753
2753
  this.logger.error("Session.setOfferAndGetAnswer: SDH setDescription or getDescription rejected...");
2754
2754
  const o = n instanceof Error ? n : new Error("Session.setOfferAndGetAnswer unknown error.");
2755
2755
  throw this.logger.error(o.message), o;
@@ -3033,7 +3033,7 @@ class ce extends ge {
3033
3033
  const t = e.statusCode || 480, i = e.reasonPhrase ? e.reasonPhrase : _e(t), s = e.extraHeaders || [];
3034
3034
  if (t < 300 || t > 699)
3035
3035
  throw new TypeError("Invalid statusCode: " + t);
3036
- const r = e.body ? Re(e.body) : void 0;
3036
+ const r = e.body ? xe(e.body) : void 0;
3037
3037
  return t < 400 ? this.incomingInviteRequest.redirect([], { statusCode: t, reasonPhrase: i, extraHeaders: s, body: r }) : this.incomingInviteRequest.reject({ statusCode: t, reasonPhrase: i, extraHeaders: s, body: r }), this.stateTransition(m.Terminated), Promise.resolve();
3038
3038
  }
3039
3039
  /**
@@ -3129,7 +3129,7 @@ class ce extends ge {
3129
3129
  * @param options - Options bucket.
3130
3130
  */
3131
3131
  sendProgress(e = {}) {
3132
- const t = e.statusCode || 180, i = e.reasonPhrase, s = (e.extraHeaders || []).slice(), r = e.body ? Re(e.body) : void 0;
3132
+ const t = e.statusCode || 180, i = e.reasonPhrase, s = (e.extraHeaders || []).slice(), r = e.body ? xe(e.body) : void 0;
3133
3133
  if (t === 183 && !r)
3134
3134
  return this.sendProgressWithSDP(e);
3135
3135
  try {
@@ -3747,10 +3747,10 @@ class Mt {
3747
3747
  return this.userAgent.userAgentCore.request(this.request, e.requestDelegate), Promise.resolve();
3748
3748
  }
3749
3749
  }
3750
- var R;
3750
+ var x;
3751
3751
  (function(a) {
3752
3752
  a.Initial = "Initial", a.Registered = "Registered", a.Unregistered = "Unregistered", a.Terminated = "Terminated";
3753
- })(R = R || (R = {}));
3753
+ })(x = x || (x = {}));
3754
3754
  class z {
3755
3755
  /**
3756
3756
  * Constructs a new instance of the `Registerer` class.
@@ -3758,7 +3758,7 @@ class z {
3758
3758
  * @param options - Options bucket. See {@link RegistererOptions} for details.
3759
3759
  */
3760
3760
  constructor(e, t = {}) {
3761
- this.disposed = !1, this._contacts = [], this._retryAfter = void 0, this._state = R.Initial, this._waiting = !1, this._stateEventEmitter = new be(), this._waitingEventEmitter = new be(), this.userAgent = e;
3761
+ this.disposed = !1, this._contacts = [], this._retryAfter = void 0, this._state = x.Initial, this._waiting = !1, this._stateEventEmitter = new be(), this._waitingEventEmitter = new be(), this.userAgent = e;
3762
3762
  const i = e.configuration.uri.clone();
3763
3763
  if (i.user = void 0, this.options = Object.assign(Object.assign(Object.assign({}, z.defaultOptions()), { registrar: i }), z.stripUndefinedProperties(t)), this.options.extraContactHeaderParams = (this.options.extraContactHeaderParams || []).slice(), this.options.extraHeaders = (this.options.extraHeaders || []).slice(), !this.options.registrar)
3764
3764
  throw new Error("Registrar undefined.");
@@ -3851,7 +3851,7 @@ class z {
3851
3851
  dispose() {
3852
3852
  return this.disposed ? Promise.resolve() : (this.disposed = !0, this.logger.log(`Registerer ${this.id} in state ${this.state} is being disposed`), delete this.userAgent._registerers[this.id], new Promise((e) => {
3853
3853
  const t = () => {
3854
- if (!this.waiting && this._state === R.Registered) {
3854
+ if (!this.waiting && this._state === x.Registered) {
3855
3855
  this.stateChange.addListener(() => {
3856
3856
  this.terminated(), e();
3857
3857
  }, { once: !0 }), this.unregister();
@@ -3871,7 +3871,7 @@ class z {
3871
3871
  * Rejects with `RequestPendingError` if a REGISTER request is already in progress.
3872
3872
  */
3873
3873
  register(e = {}) {
3874
- if (this.state === R.Terminated)
3874
+ if (this.state === x.Terminated)
3875
3875
  throw this.stateError(), new Error("Registerer terminated. Unable to register.");
3876
3876
  if (this.disposed)
3877
3877
  throw this.stateError(), new Error("Registerer disposed. Unable to register.");
@@ -3964,16 +3964,16 @@ class z {
3964
3964
  * Rejects with `RequestPendingError` if a REGISTER request is already in progress.
3965
3965
  */
3966
3966
  unregister(e = {}) {
3967
- if (this.state === R.Terminated)
3967
+ if (this.state === x.Terminated)
3968
3968
  throw this.stateError(), new Error("Registerer terminated. Unable to register.");
3969
- if (this.disposed && this.state !== R.Registered)
3969
+ if (this.disposed && this.state !== x.Registered)
3970
3970
  throw this.stateError(), new Error("Registerer disposed. Unable to register.");
3971
3971
  if (this.waiting) {
3972
3972
  this.waitingWarning();
3973
3973
  const s = new ye("REGISTER request already in progress, waiting for final response");
3974
3974
  return Promise.reject(s);
3975
3975
  }
3976
- this._state !== R.Registered && !e.all && this.logger.warn("Not currently registered, but sending an unregister anyway.");
3976
+ this._state !== x.Registered && !e.all && this.logger.warn("Not currently registered, but sending an unregister anyway.");
3977
3977
  const t = (e.requestOptions && e.requestOptions.extraHeaders || []).slice();
3978
3978
  this.request.extraHeaders = t, e.all ? (t.push("Contact: *"), t.push("Expires: 0")) : t.push("Contact: " + this.generateContactHeader(0)), this.request.cseq++, this.request.setHeader("cseq", this.request.cseq + " REGISTER"), this.registrationTimer !== void 0 && (clearTimeout(this.registrationTimer), this.registrationTimer = void 0), this.waitingToggle(!0);
3979
3979
  const i = this.userAgent.userAgentCore.register(this.request, {
@@ -4018,19 +4018,19 @@ class z {
4018
4018
  this.registrationTimer = void 0, this.register();
4019
4019
  }, this.refreshFrequency / 100 * e * 1e3), this.registrationExpiredTimer = setTimeout(() => {
4020
4020
  this.logger.warn("Registration expired"), this.unregistered();
4021
- }, e * 1e3), this._state !== R.Registered && this.stateTransition(R.Registered);
4021
+ }, e * 1e3), this._state !== x.Registered && this.stateTransition(x.Registered);
4022
4022
  }
4023
4023
  /**
4024
4024
  * Helper function, called when unregistered.
4025
4025
  */
4026
4026
  unregistered() {
4027
- this.clearTimers(), this._state !== R.Unregistered && this.stateTransition(R.Unregistered);
4027
+ this.clearTimers(), this._state !== x.Unregistered && this.stateTransition(x.Unregistered);
4028
4028
  }
4029
4029
  /**
4030
4030
  * Helper function, called when terminated.
4031
4031
  */
4032
4032
  terminated() {
4033
- this.clearTimers(), this._state !== R.Terminated && this.stateTransition(R.Terminated);
4033
+ this.clearTimers(), this._state !== x.Terminated && this.stateTransition(x.Terminated);
4034
4034
  }
4035
4035
  /**
4036
4036
  * Transition registration state.
@@ -4040,22 +4040,22 @@ class z {
4040
4040
  throw new Error(`Invalid state transition from ${this._state} to ${e}`);
4041
4041
  };
4042
4042
  switch (this._state) {
4043
- case R.Initial:
4044
- e !== R.Registered && e !== R.Unregistered && e !== R.Terminated && t();
4043
+ case x.Initial:
4044
+ e !== x.Registered && e !== x.Unregistered && e !== x.Terminated && t();
4045
4045
  break;
4046
- case R.Registered:
4047
- e !== R.Unregistered && e !== R.Terminated && t();
4046
+ case x.Registered:
4047
+ e !== x.Unregistered && e !== x.Terminated && t();
4048
4048
  break;
4049
- case R.Unregistered:
4050
- e !== R.Registered && e !== R.Terminated && t();
4049
+ case x.Unregistered:
4050
+ e !== x.Registered && e !== x.Terminated && t();
4051
4051
  break;
4052
- case R.Terminated:
4052
+ case x.Terminated:
4053
4053
  t();
4054
4054
  break;
4055
4055
  default:
4056
4056
  throw new Error("Unrecognized state.");
4057
4057
  }
4058
- this._state = e, this.logger.log(`Registration transitioned to state ${this._state}`), this._stateEventEmitter.emit(this._state), e === R.Terminated && this.dispose();
4058
+ this._state = e, this.logger.log(`Registration transitioned to state ${this._state}`), this._stateEventEmitter.emit(this._state), e === x.Terminated && this.dispose();
4059
4059
  }
4060
4060
  /** True if the registerer is currently waiting for final response to a REGISTER request. */
4061
4061
  get waiting() {
@@ -4080,16 +4080,16 @@ class z {
4080
4080
  }
4081
4081
  /** Hopefully helpful as the standard behavior has been found to be unexpected. */
4082
4082
  stateError() {
4083
- let t = `An attempt was made to send a REGISTER request when the Registerer ${this.state === R.Terminated ? "is in 'Terminated' state" : "has been disposed"}.`;
4083
+ let t = `An attempt was made to send a REGISTER request when the Registerer ${this.state === x.Terminated ? "is in 'Terminated' state" : "has been disposed"}.`;
4084
4084
  t += " The Registerer transitions to 'Terminated' when Registerer.dispose() is called.", t += " Perhaps you called UserAgent.stop() which dipsoses of all Registerers?", this.logger.error(t);
4085
4085
  }
4086
4086
  }
4087
4087
  z.defaultExpires = 600;
4088
4088
  z.defaultRefreshFrequency = 99;
4089
- var x;
4089
+ var R;
4090
4090
  (function(a) {
4091
4091
  a.Initial = "Initial", a.NotifyWait = "NotifyWait", a.Pending = "Pending", a.Active = "Active", a.Terminated = "Terminated";
4092
- })(x = x || (x = {}));
4092
+ })(R = R || (R = {}));
4093
4093
  var T;
4094
4094
  (function(a) {
4095
4095
  a.Connecting = "Connecting", a.Connected = "Connected", a.Disconnecting = "Disconnecting", a.Disconnected = "Disconnected";
@@ -5977,7 +5977,7 @@ class Bt extends q {
5977
5977
  function jt(a) {
5978
5978
  return a.userAgentCore !== void 0;
5979
5979
  }
5980
- class xe extends J {
5980
+ class Re extends J {
5981
5981
  /**
5982
5982
  * NOTIFY UAS constructor.
5983
5983
  * @param dialogOrCore - Dialog for in dialog NOTIFY, UserAgentCore for out of dialog NOTIFY (deprecated).
@@ -6418,7 +6418,7 @@ class Ie extends fe {
6418
6418
  break;
6419
6419
  case v.NOTIFY:
6420
6420
  {
6421
- const t = new xe(this, e);
6421
+ const t = new Re(this, e);
6422
6422
  this.delegate && this.delegate.onNotify ? this.delegate.onNotify(t) : t.accept();
6423
6423
  }
6424
6424
  break;
@@ -6948,7 +6948,7 @@ class Qe extends fe {
6948
6948
  */
6949
6949
  subscribe(e, t = {}) {
6950
6950
  var i;
6951
- if (this.subscriptionState !== x.Pending && this.subscriptionState !== x.Active)
6951
+ if (this.subscriptionState !== R.Pending && this.subscriptionState !== R.Active)
6952
6952
  throw new Error(`Invalid state ${this.subscriptionState}. May only re-subscribe while in state "pending" or "active".`);
6953
6953
  this.logger.log(`SUBSCRIBE dialog ${this.id} sending SUBSCRIBE request`);
6954
6954
  const s = new ei(this, e, t);
@@ -6962,7 +6962,7 @@ class Qe extends fe {
6962
6962
  * https://tools.ietf.org/html/rfc6665#section-4.4.1
6963
6963
  */
6964
6964
  terminate() {
6965
- this.stateTransition(x.Terminated), this.onTerminated();
6965
+ this.stateTransition(R.Terminated), this.onTerminated();
6966
6966
  }
6967
6967
  /**
6968
6968
  * 4.1.2.3. Unsubscribing
@@ -6992,19 +6992,19 @@ class Qe extends fe {
6992
6992
  const s = i.state, r = i.expires ? Math.max(i.expires, 0) : void 0;
6993
6993
  switch (s) {
6994
6994
  case "pending":
6995
- this.stateTransition(x.Pending, r);
6995
+ this.stateTransition(R.Pending, r);
6996
6996
  break;
6997
6997
  case "active":
6998
- this.stateTransition(x.Active, r);
6998
+ this.stateTransition(R.Active, r);
6999
6999
  break;
7000
7000
  case "terminated":
7001
- this.stateTransition(x.Terminated, r);
7001
+ this.stateTransition(R.Terminated, r);
7002
7002
  break;
7003
7003
  default:
7004
7004
  this.logger.warn("Unrecognized subscription state.");
7005
7005
  break;
7006
7006
  }
7007
- const n = new xe(this, e);
7007
+ const n = new Re(this, e);
7008
7008
  this.delegate && this.delegate.onNotify ? this.delegate.onNotify(n) : n.accept();
7009
7009
  }
7010
7010
  onRefresh(e) {
@@ -7029,26 +7029,26 @@ class Qe extends fe {
7029
7029
  this.logger.warn(`Invalid subscription state transition from ${this.subscriptionState} to ${e}`);
7030
7030
  };
7031
7031
  switch (e) {
7032
- case x.Initial:
7032
+ case R.Initial:
7033
7033
  i();
7034
7034
  return;
7035
- case x.NotifyWait:
7035
+ case R.NotifyWait:
7036
7036
  i();
7037
7037
  return;
7038
- case x.Pending:
7039
- if (this.subscriptionState !== x.NotifyWait && this.subscriptionState !== x.Pending) {
7038
+ case R.Pending:
7039
+ if (this.subscriptionState !== R.NotifyWait && this.subscriptionState !== R.Pending) {
7040
7040
  i();
7041
7041
  return;
7042
7042
  }
7043
7043
  break;
7044
- case x.Active:
7045
- if (this.subscriptionState !== x.NotifyWait && this.subscriptionState !== x.Pending && this.subscriptionState !== x.Active) {
7044
+ case R.Active:
7045
+ if (this.subscriptionState !== R.NotifyWait && this.subscriptionState !== R.Pending && this.subscriptionState !== R.Active) {
7046
7046
  i();
7047
7047
  return;
7048
7048
  }
7049
7049
  break;
7050
- case x.Terminated:
7051
- if (this.subscriptionState !== x.NotifyWait && this.subscriptionState !== x.Pending && this.subscriptionState !== x.Active) {
7050
+ case R.Terminated:
7051
+ if (this.subscriptionState !== R.NotifyWait && this.subscriptionState !== R.Pending && this.subscriptionState !== R.Active) {
7052
7052
  i();
7053
7053
  return;
7054
7054
  }
@@ -7057,7 +7057,7 @@ class Qe extends fe {
7057
7057
  i();
7058
7058
  return;
7059
7059
  }
7060
- e === x.Pending && t && (this.subscriptionExpires = t), e === x.Active && t && (this.subscriptionExpires = t), e === x.Terminated && this.dispose(), this._subscriptionState = e;
7060
+ e === R.Pending && t && (this.subscriptionExpires = t), e === R.Active && t && (this.subscriptionExpires = t), e === R.Terminated && this.dispose(), this._subscriptionState = e;
7061
7061
  }
7062
7062
  /**
7063
7063
  * When refreshing a subscription, a subscriber starts Timer N, set to
@@ -7071,7 +7071,7 @@ class Qe extends fe {
7071
7071
  * https://tools.ietf.org/html/rfc6665#section-4.1.2.2
7072
7072
  */
7073
7073
  timerN() {
7074
- this.logger.warn("Timer N expired for SUBSCRIBE dialog. Timed out waiting for NOTIFY."), this.subscriptionState !== x.Terminated && (this.stateTransition(x.Terminated), this.onTerminated());
7074
+ this.logger.warn("Timer N expired for SUBSCRIBE dialog. Timed out waiting for NOTIFY."), this.subscriptionState !== R.Terminated && (this.stateTransition(R.Terminated), this.onTerminated());
7075
7075
  }
7076
7076
  }
7077
7077
  class ti extends q {
@@ -7082,7 +7082,7 @@ class ti extends q {
7082
7082
  const r = t.getHeader("Expires");
7083
7083
  if (!r)
7084
7084
  throw new Error("Expires undefined");
7085
- super(L, e, t, i), this.delegate = i, this.subscriberId = t.callId + t.fromTag + s, this.subscriptionExpiresRequested = this.subscriptionExpires = Number(r), this.subscriptionEvent = s, this.subscriptionState = x.NotifyWait, this.waitNotifyStart();
7085
+ super(L, e, t, i), this.delegate = i, this.subscriberId = t.callId + t.fromTag + s, this.subscriptionExpiresRequested = this.subscriptionExpires = Number(r), this.subscriptionEvent = s, this.subscriptionState = R.NotifyWait, this.waitNotifyStart();
7086
7086
  }
7087
7087
  /**
7088
7088
  * Destructor.
@@ -7132,18 +7132,18 @@ class ti extends q {
7132
7132
  throw new Error("Dialog already created. This implementation only supports install of single subscriptions.");
7133
7133
  switch (this.waitNotifyStop(), this.subscriptionExpires = i.expires ? Math.min(this.subscriptionExpires, Math.max(i.expires, 0)) : this.subscriptionExpires, s) {
7134
7134
  case "pending":
7135
- this.subscriptionState = x.Pending;
7135
+ this.subscriptionState = R.Pending;
7136
7136
  break;
7137
7137
  case "active":
7138
- this.subscriptionState = x.Active;
7138
+ this.subscriptionState = R.Active;
7139
7139
  break;
7140
7140
  case "terminated":
7141
- this.subscriptionState = x.Terminated;
7141
+ this.subscriptionState = R.Terminated;
7142
7142
  break;
7143
7143
  default:
7144
7144
  throw new Error(`Unrecognized state ${s}.`);
7145
7145
  }
7146
- if (this.subscriptionState !== x.Terminated) {
7146
+ if (this.subscriptionState !== R.Terminated) {
7147
7147
  const r = Qe.initialDialogStateForSubscription(this.message, e.message);
7148
7148
  this.dialog = new Qe(this.subscriptionEvent, this.subscriptionExpires, this.subscriptionState, this.core, r);
7149
7149
  }
@@ -7428,7 +7428,7 @@ class si {
7428
7428
  }
7429
7429
  const r = e.callId + e.toTag + s.event, n = this.subscribers.get(r);
7430
7430
  if (n) {
7431
- const o = new xe(this, e);
7431
+ const o = new Re(this, e);
7432
7432
  n.onNotify(o);
7433
7433
  return;
7434
7434
  }
@@ -7480,7 +7480,7 @@ class si {
7480
7480
  break;
7481
7481
  case v.NOTIFY:
7482
7482
  {
7483
- const t = new xe(this, e);
7483
+ const t = new Re(this, e);
7484
7484
  this.delegate.onNotify ? this.delegate.onNotify(t) : t.reject({ statusCode: 405 });
7485
7485
  }
7486
7486
  break;
@@ -9207,15 +9207,15 @@ class Ge {
9207
9207
  async register(e) {
9208
9208
  return this.logger.log("Registering UserAgent..."), this.shouldBeRegistered = !0, e !== void 0 && (this.registererRegisterOptions = Object.assign({}, e)), this.registerer || (this.registerer = new z(this.userAgent, this.registererOptions), this.registerer.stateChange.addListener((t) => {
9209
9209
  switch (t) {
9210
- case R.Initial:
9210
+ case x.Initial:
9211
9211
  break;
9212
- case R.Registered:
9212
+ case x.Registered:
9213
9213
  this.delegate && this.delegate.onRegistered && this.delegate.onRegistered();
9214
9214
  break;
9215
- case R.Unregistered:
9215
+ case x.Unregistered:
9216
9216
  this.delegate && this.delegate.onUnregistered && this.delegate.onUnregistered(), this.shouldBeRegistered && this.attemptRegistration();
9217
9217
  break;
9218
- case R.Terminated:
9218
+ case x.Terminated:
9219
9219
  break;
9220
9220
  default:
9221
9221
  throw new Error("Unknown registerer state.");
@@ -11161,10 +11161,10 @@ ${JSON.stringify(e, null, 2)}` : a;
11161
11161
  function Ei(a) {
11162
11162
  return a === "outbound" ? "outbound" : "terminal";
11163
11163
  }
11164
- function Ri(a) {
11164
+ function xi(a) {
11165
11165
  return typeof a == "function";
11166
11166
  }
11167
- function xi() {
11167
+ function Ri() {
11168
11168
  return typeof window > "u" ? !1 : new URLSearchParams(window.location.search).get("embeddedCallWidgetE2E") === "1";
11169
11169
  }
11170
11170
  function Ii() {
@@ -11190,7 +11190,7 @@ function rt(a) {
11190
11190
  }
11191
11191
  class Ai {
11192
11192
  constructor({ mount: e, shadowRoot: t, options: i }) {
11193
- this.mountNode = e, this.shadowRoot = t, this.options = i, this.mode = Ei(i.mode), this.legacyOutboundOnly = this.mode === "outbound", this.apiBaseUrl = De(i.apiBaseUrl || window.location.origin).href, this.client = null, this.issuedSession = null, this.bootstrap = null, this.turnIceServers = [], this.state = "idle", this.finalizing = !1, this.isMobileViewport = !1, this.pendingIncomingCall = null, this.visibilityHandler = null, this.sessionTouchTimer = null, this.sessionRecoveryInFlight = null, this.transportDisconnectTimer = null, this.transportDisconnectContext = null, this.e2eBridgeEnabled = xi(), this.terminalFailureOverride = "", this.presenceId = Ii(), this.presenceTouchTimer = null, this.presenceFailureCount = 0, this.availabilityState = "preparing", this.availabilityDetail = "正在连接后台并准备网页电话待机。", this.connectivityRecoveryInFlight = null, this.lifecycleRunId = 0, this.pendingLifecycleMode = null, this.expectedClientTeardown = null, this.autoStandbyRecoveryTimer = null, this.recentlyReleasedSessions = /* @__PURE__ */ new Map(), this.networkOnline = typeof navigator > "u" ? !0 : navigator.onLine !== !1, this.pageVisible = typeof document > "u" ? !0 : document.visibilityState === "visible", this.onlineHandler = null, this.offlineHandler = null, this.refs = {};
11193
+ this.mountNode = e, this.shadowRoot = t, this.options = i, this.mode = Ei(i.mode), this.legacyOutboundOnly = this.mode === "outbound", this.apiBaseUrl = De(i.apiBaseUrl || window.location.origin).href, this.client = null, this.issuedSession = null, this.bootstrap = null, this.turnIceServers = [], this.state = "idle", this.finalizing = !1, this.isMobileViewport = !1, this.pendingIncomingCall = null, this.visibilityHandler = null, this.sessionTouchTimer = null, this.sessionRecoveryInFlight = null, this.transportDisconnectTimer = null, this.transportDisconnectContext = null, this.e2eBridgeEnabled = Ri(), this.terminalFailureOverride = "", this.presenceId = Ii(), this.presenceTouchTimer = null, this.presenceFailureCount = 0, this.availabilityState = "preparing", this.availabilityDetail = "正在连接后台并准备网页电话待机。", this.connectivityRecoveryInFlight = null, this.lifecycleRunId = 0, this.pendingLifecycleMode = null, this.expectedClientTeardown = null, this.expectedTermination = null, this.autoStandbyRecoveryTimer = null, this.recentlyReleasedSessions = /* @__PURE__ */ new Map(), this.networkOnline = typeof navigator > "u" ? !0 : navigator.onLine !== !1, this.pageVisible = typeof document > "u" ? !0 : document.visibilityState === "visible", this.onlineHandler = null, this.offlineHandler = null, this.refs = {};
11194
11194
  }
11195
11195
  async mount() {
11196
11196
  this.isMobileViewport = typeof window < "u" && window.matchMedia("(max-width: 480px)").matches, this.shadowRoot.innerHTML = `
@@ -11259,7 +11259,7 @@ class Ai {
11259
11259
  }
11260
11260
  emitHostCallback(e, t) {
11261
11261
  const i = this.options?.[e];
11262
- if (Ri(i))
11262
+ if (xi(i))
11263
11263
  try {
11264
11264
  i(t);
11265
11265
  } catch (s) {
@@ -11464,7 +11464,7 @@ class Ai {
11464
11464
  return;
11465
11465
  }
11466
11466
  if (e === "disconnected" || e === "unregistered") {
11467
- if (this.isExpectedClientTeardown())
11467
+ if (this.isExpectedClientTeardown() || this.isExpectedTermination() || !this.issuedSession?.session_id && this.pendingLifecycleMode === "standby")
11468
11468
  return;
11469
11469
  if (this.stopSessionTouch(), ["calling", "connected"].includes(this.state)) {
11470
11470
  this.setState("connected", "SIP 信令短暂中断,正在等待恢复。"), this.setAvailabilityState("recovering", "通话信令短暂中断,正在等待恢复。"), this.scheduleTransportDisconnectGrace(e);
@@ -11521,7 +11521,7 @@ class Ai {
11521
11521
  this.beginLifecycleRun(), this.finalizing = !0;
11522
11522
  try {
11523
11523
  if (this.pendingLifecycleMode = null, this.stopSessionTouch(), this.stopTransportDisconnectGrace(), await this.releaseIssuedSession("released", e), this.client && (await this.client.destroy(), this.client = null), !this.legacyOutboundOnly) {
11524
- this.setState("idle", "上一通通话已结束,正在恢复网页电话待机。"), this.setAvailabilityState("recovering", "上一通通话已结束,正在恢复网页电话待机。"), this.appendLog("通话已结束,网页电话正在恢复待机。", { reason: e }), await this.startStandby({ force: !0 });
11524
+ this.pendingLifecycleMode = "standby", this.setState("standbyRequesting", "上一通通话已结束,正在自动回到待机。"), this.setAvailabilityState("preparing", "页面仍在线,正在自动回到待机。"), this.appendLog("通话已结束,网页电话正在自动回到待机。", { reason: e }), await this.startStandby({ force: !0 });
11525
11525
  return;
11526
11526
  }
11527
11527
  const t = this.terminalFailureOverride || (this.state === "failed" ? this.refs.status?.textContent?.trim() || Q.failed : "");
@@ -11578,7 +11578,7 @@ class Ai {
11578
11578
  this.legacyOutboundOnly ? this.setState("ended") : this.setState("standby", "测试桥已模拟挂断,网页电话已恢复待机。"), this.pendingIncomingCall = null;
11579
11579
  return;
11580
11580
  }
11581
- this.client && await this.client.hangup();
11581
+ this.client && (this.expectTermination("hangup-call"), this.suppressExpectedClientTeardown("hangup-call"), await this.client.hangup());
11582
11582
  }
11583
11583
  async disconnectAndCleanup(e, t = {}) {
11584
11584
  const {
@@ -11767,6 +11767,19 @@ class Ai {
11767
11767
  expiresAt: Date.now() + t
11768
11768
  };
11769
11769
  }
11770
+ expectTermination(e, t = 1e4) {
11771
+ this.expectedTermination = {
11772
+ reason: e,
11773
+ expiresAt: Date.now() + t
11774
+ };
11775
+ }
11776
+ clearExpectedTermination() {
11777
+ this.expectedTermination = null;
11778
+ }
11779
+ isExpectedTermination() {
11780
+ const e = this.expectedTermination;
11781
+ return e ? e.expiresAt <= Date.now() ? (this.expectedTermination = null, !1) : !0 : !1;
11782
+ }
11770
11783
  isExpectedClientTeardown() {
11771
11784
  const e = this.expectedClientTeardown;
11772
11785
  return e ? e.expiresAt <= Date.now() ? (this.expectedClientTeardown = null, !1) : !0 : !1;
@@ -12018,7 +12031,7 @@ class Ai {
12018
12031
  }, bt));
12019
12032
  }
12020
12033
  setState(e, t = "") {
12021
- this.state = e, this.refs.status.textContent = t || Q[e] || Q.idle, this.emitHostCallback("onStateChange", this.getSnapshot()), this.syncAvailabilityState(), this.render();
12034
+ this.state = e, this.refs.status.textContent = t || Q[e] || Q.idle, this.syncAvailabilityState(), this.render(), this.emitHostCallback("onStateChange", this.getSnapshot());
12022
12035
  }
12023
12036
  appendLog(e, t) {
12024
12037
  const i = document.createElement("div");
@@ -12030,7 +12043,7 @@ class Ai {
12030
12043
  this.refs.status && (this.refs.status.className = [
12031
12044
  "status",
12032
12045
  this.availabilityState === "offline" ? "status--offline" : this.availabilityState === "recovering" ? "status--recovering" : ""
12033
- ].filter(Boolean).join(" ")), this.legacyOutboundOnly ? (this.refs.primary.disabled = e, this.refs.primary.textContent = t ? "通话进行中" : "连接并呼叫", this.refs.secondary.disabled = !t && !i, this.refs.secondary.textContent = t ? "挂断并清理" : "断开并清理") : (this.refs.primary.disabled = e || s || this.state === "connected", this.state === "incoming" ? this.refs.primary.textContent = "接听来电" : e || s ? this.availabilityState === "working" ? this.refs.primary.textContent = "正在建立呼叫" : this.availabilityState === "preparing" ? this.refs.primary.textContent = "正在准备网页电话" : this.refs.primary.textContent = this.state === "calling" ? "通话进行中" : "正在恢复网页电话" : this.availabilityState === "offline" ? this.refs.primary.textContent = "网络恢复后再试" : !i || ["idle", "failed", "ended"].includes(this.state) ? this.refs.primary.textContent = "恢复网页电话待机" : this.refs.primary.textContent = "呼叫管理员", this.refs.secondary.disabled = !i, this.refs.secondary.textContent = this.state === "incoming" ? "拒接来电" : this.state === "connected" ? "挂断并恢复待机" : "断开电话");
12046
+ ].filter(Boolean).join(" ")), this.legacyOutboundOnly ? (this.refs.primary.disabled = e, this.refs.primary.textContent = t ? "通话进行中" : "连接并呼叫", this.refs.secondary.disabled = !t && !i, this.refs.secondary.textContent = t ? "挂断并清理" : "断开并清理") : (this.refs.primary.disabled = e || s || this.state === "connected", this.state === "incoming" ? this.refs.primary.textContent = "接听来电" : this.state === "connected" ? this.refs.primary.textContent = "通话进行中" : e || s ? this.availabilityState === "working" ? this.refs.primary.textContent = "正在建立呼叫" : this.availabilityState === "preparing" ? this.refs.primary.textContent = "正在准备网页电话" : this.refs.primary.textContent = this.state === "calling" ? "通话进行中" : "正在恢复网页电话" : this.availabilityState === "offline" ? this.refs.primary.textContent = "网络恢复后再试" : !i || ["idle", "failed", "ended"].includes(this.state) ? this.refs.primary.textContent = "恢复网页电话待机" : this.refs.primary.textContent = "呼叫管理员", this.refs.secondary.disabled = !i, this.refs.secondary.textContent = this.state === "incoming" ? "拒接来电" : this.state === "connected" ? "挂断并恢复待机" : "断开电话");
12034
12047
  const r = this.options.siteKey || this.bootstrap?.default_site_key || "等待服务端返回", n = this.issuedSession?.resolved_business_key || te(this.options, this.bootstrap) || "等待服务端返回";
12035
12048
  this.legacyOutboundOnly ? this.refs.meta.textContent = `入站规则:${n};站点键:${r}` : this.refs.meta.textContent = `站点键:${r};网页电话会先进入可被叫待机;访客主动呼叫默认规则:${n}`, this.availabilityState === "offline" ? this.refs.hint.textContent = this.networkOnline === !1 ? "当前浏览器网络已断开。系统会在网络恢复后自动尝试重连网页电话。" : "当前无法连接通话服务。系统会继续自动恢复,暂时不要重复点击。" : this.availabilityState === "preparing" ? this.refs.hint.textContent = "当前正在准备待机能力。只要页面在线,系统会自动进入可呼入、可呼出的网页电话状态。" : this.availabilityState === "working" ? this.refs.hint.textContent = "当前正在切换到外呼链路,请等待振铃、接通或自动回到待机。" : this.availabilityState === "recovering" ? this.refs.hint.textContent = "当前页面仍在线,系统正在自动恢复可被叫待机,不需要刷新页面或手动切换模式。" : this.refs.hint.textContent = this.isMobileViewport ? wi : "桌面网页可直接使用;若宿主站点启用了来源白名单或接入令牌,请按站点配置提供对应参数。";
12036
12049
  }
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "package_name": "@shenyin/embedded-call-widget",
3
- "source_package_version": "2.6.3",
4
- "built_at": "2026-04-07T03:53:30.902Z",
3
+ "source_package_version": "2.6.4",
4
+ "built_at": "2026-04-07T04:16:43.118Z",
5
5
  "outputs": {
6
6
  "esm_shell": "embedded-call-widget.js",
7
7
  "esm_runtime": "embedded-call-widget-runtime.js",
@@ -54,9 +54,9 @@
54
54
  },
55
55
  "release_metadata": {
56
56
  "npm_package_name": "@shenyin/embedded-call-widget",
57
- "git_tag": "embedded-call-widget-v2.6.3",
58
- "git_tag_alias": "v2.6.3",
59
- "cdn_version_path": "embedded-call-widget/2.6.3/",
57
+ "git_tag": "embedded-call-widget-v2.6.4",
58
+ "git_tag_alias": "v2.6.4",
59
+ "cdn_version_path": "embedded-call-widget/2.6.4/",
60
60
  "cdn_latest_path": "embedded-call-widget/latest/",
61
61
  "semver_channel": "stable"
62
62
  },
@@ -69,9 +69,9 @@
69
69
  },
70
70
  "esm_runtime": {
71
71
  "file": "embedded-call-widget-runtime.js",
72
- "size_bytes": 515145,
73
- "sha256": "667706853fecb195053410b91c2fb6576f18d5bdfc4a3789f33d2f6c69ea5e72",
74
- "sri_sha384": "sha384-7SeuYGzFRsu631iSwS60vOQKcUYlwdHz+Qhv1ErbMHZ0Wpll1HPC8ABvwEebeKm1"
72
+ "size_bytes": 515861,
73
+ "sha256": "75900b00c8e0e7347c394dc2f6945b6d575c76caa932de9f341410806c2d3bb1",
74
+ "sri_sha384": "sha384-VaIuRnJCyX0ELIAi/v5kM5b8YBfjL58MWQ/B+9viH9pV9dMaqWE0s91gqmdnxdeq"
75
75
  },
76
76
  "iife_shell": {
77
77
  "file": "embedded-call-widget.iife.js",
@@ -81,9 +81,9 @@
81
81
  },
82
82
  "iife_runtime": {
83
83
  "file": "embedded-call-widget-runtime.iife.js",
84
- "size_bytes": 332025,
85
- "sha256": "fcf91deb9410b66bc74b367611240cae4d49222e3aa9a8f426b24a2b2016ad2e",
86
- "sri_sha384": "sha384-WpirFZikgkgE4+yUcFbUYGMFTk1ldsBI/eMk2aZFNgoKBlGrn0OZ1dss5yoiibGI"
84
+ "size_bytes": 332628,
85
+ "sha256": "06bf9d92f55c439ef384c2eacf42dc5f4b1552d53ff412a356a824621451a1bd",
86
+ "sri_sha384": "sha384-8EeGh00EHdWG0ZdFP6R+RZfGjh8uS8a+ea6LhU0JospuHYpLbW6hjwypqyrdBeWS"
87
87
  }
88
88
  }
89
89
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shenyin/embedded-call-widget",
3
- "version": "2.6.3",
3
+ "version": "2.6.4",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "files": [