@shenyin/embedded-call-widget 2.6.6 → 2.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/checksums.txt +3 -3
- package/embedded-call-widget-runtime.iife.js +4 -4
- package/embedded-call-widget-runtime.js +80 -75
- package/manifest.json +11 -11
- package/package.json +1 -1
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.
|
|
28
|
+
<script src="https://cdn.jsdelivr.net/npm/@shenyin/embedded-call-widget@2.6.8/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
|
-
|
|
2
|
+
6483380c886da72bef01cbe618a6aa029dd1e9e498043ab049ca9f14eebf2ff1 embedded-call-widget-runtime.js
|
|
3
3
|
f4664df394b9287ba012c49a4423439bd103eccffd9e8a212c72fbeffc589054 embedded-call-widget.iife.js
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
07c282fa03f23eaba25be4a71771f2162136acc6af55370ca0b5e2ad3476c2f0 embedded-call-widget-runtime.iife.js
|
|
5
|
+
e30e2da4b713b9639b3a873427da3767136241946a9bd0d4ccbf722144fc1388 manifest.json
|
|
@@ -62,7 +62,7 @@ var EmbeddedCallWidgetRuntimeBundle=(function(ke){"use strict";const St="0.21.1"
|
|
|
62
62
|
\r
|
|
63
63
|
`))}startSendingKeepAlives(){const e=t=>{const i=t*.8;return 1e3*(Math.random()*(t-i)+i)};this.configuration.keepAliveInterval&&!this.keepAliveInterval&&(this.keepAliveInterval=setInterval(()=>{this.sendKeepAlive(),this.startSendingKeepAlives()},e(this.configuration.keepAliveInterval)))}stopSendingKeepAlives(){this.keepAliveInterval&&clearInterval(this.keepAliveInterval),this.keepAliveDebounceTimeout&&clearTimeout(this.keepAliveDebounceTimeout),this.keepAliveInterval=void 0,this.keepAliveDebounceTimeout=void 0}}ve.defaultOptions={server:"",connectionTimeout:5,keepAliveInterval:0,keepAliveDebounce:10,traceSip:!0};class G{constructor(e={}){if(this._publishers={},this._registerers={},this._sessions={},this._subscriptions={},this._state=k.Stopped,this._stateEventEmitter=new we,this.delegate=e.delegate,this.options=Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},G.defaultOptions()),{sipjsId:oe(5)}),{uri:new W("sip","anonymous."+oe(6),"anonymous.invalid")}),{viaHost:oe(12)+".invalid"}),G.stripUndefinedProperties(e)),this.options.hackIpInContact)if(typeof this.options.hackIpInContact=="boolean"&&this.options.hackIpInContact){const s=Math.floor(Math.random()*254+1);this.options.viaHost="192.0.2."+s}else this.options.hackIpInContact&&(this.options.viaHost=this.options.hackIpInContact);switch(this.loggerFactory=new Mt,this.logger=this.loggerFactory.getLogger("sip.UserAgent"),this.loggerFactory.builtinEnabled=this.options.logBuiltinEnabled,this.loggerFactory.connector=this.options.logConnector,this.options.logLevel){case"error":this.loggerFactory.level=M.error;break;case"warn":this.loggerFactory.level=M.warn;break;case"log":this.loggerFactory.level=M.log;break;case"debug":this.loggerFactory.level=M.debug;break}if(this.options.logConfiguration&&(this.logger.log("Configuration:"),Object.keys(this.options).forEach(t=>{const i=this.options[t];switch(t){case"uri":case"sessionDescriptionHandlerFactory":this.logger.log("· "+t+": "+i);break;case"authorizationPassword":this.logger.log("· "+t+": NOT SHOWN");break;case"transportConstructor":this.logger.log("· "+t+": "+i.name);break;default:this.logger.log("· "+t+": "+JSON.stringify(i))}})),this.options.transportOptions){const t=this.options.transportOptions,i=t.maxReconnectionAttempts,s=t.reconnectionTimeout;i!==void 0&&this.logger.warn('The transport option "maxReconnectionAttempts" as has apparently been specified and has been deprecated. It will no longer be available starting with SIP.js release 0.16.0. Please update accordingly.'),s!==void 0&&this.logger.warn('The transport option "reconnectionTimeout" as has apparently been specified and has been deprecated. It will no longer be available starting with SIP.js release 0.16.0. Please update accordingly.'),e.reconnectionDelay===void 0&&s!==void 0&&(this.options.reconnectionDelay=s),e.reconnectionAttempts===void 0&&i!==void 0&&(this.options.reconnectionAttempts=i)}if(e.reconnectionDelay!==void 0&&this.logger.warn('The user agent option "reconnectionDelay" as has apparently been specified and has been deprecated. It will no longer be available starting with SIP.js release 0.16.0. Please update accordingly.'),e.reconnectionAttempts!==void 0&&this.logger.warn('The user agent option "reconnectionAttempts" as has apparently been specified and has been deprecated. It will no longer be available starting with SIP.js release 0.16.0. Please update accordingly.'),this._transport=new this.options.transportConstructor(this.getLogger("sip.Transport"),this.options.transportOptions),this.initTransportCallbacks(),this._contact=this.initContact(),this._instanceId=this.options.instanceId?this.options.instanceId:G.newUUID(),D.parse(this._instanceId,"uuid")===-1)throw new Error("Invalid instanceId.");this._userAgentCore=this.initCore()}static makeURI(e){return D.URIParse(e)}static defaultOptions(){return{allowLegacyNotifications:!1,authorizationHa1:"",authorizationPassword:"",authorizationUsername:"",delegate:{},contactName:"",contactParams:{transport:"ws"},displayName:"",forceRport:!1,gracefulShutdown:!0,hackAllowUnregisteredOptionTags:!1,hackIpInContact:!1,hackViaTcp:!1,instanceId:"",instanceIdAlwaysAdded:!1,logBuiltinEnabled:!0,logConfiguration:!0,logConnector:()=>{},logLevel:"log",noAnswerTimeout:60,preloadedRouteSet:[],reconnectionAttempts:0,reconnectionDelay:4,sendInitialProvisionalResponse:!0,sessionDescriptionHandlerFactory:ri(),sessionDescriptionHandlerFactoryOptions:{},sipExtension100rel:V.Unsupported,sipExtensionReplaces:V.Unsupported,sipExtensionExtraSupported:[],sipjsId:"",transportConstructor:ve,transportOptions:{},uri:new W("sip","anonymous","anonymous.invalid"),userAgentString:"SIP.js/"+St,viaHost:""}}static newUUID(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,t=>{const i=Math.floor(Math.random()*16);return(t==="x"?i:i%4+8).toString(16)})}static stripUndefinedProperties(e){return Object.keys(e).reduce((t,i)=>(e[i]!==void 0&&(t[i]=e[i]),t),{})}get configuration(){return this.options}get contact(){return this._contact}get instanceId(){return this._instanceId}get state(){return this._state}get stateChange(){return this._stateEventEmitter}get transport(){return this._transport}get userAgentCore(){return this._userAgentCore}getLogger(e,t){return this.loggerFactory.getLogger(e,t)}getLoggerFactory(){return this.loggerFactory}isConnected(){return this.transport.isConnected()}reconnect(){return this.state===k.Stopped?Promise.reject(new Error("User agent stopped.")):Promise.resolve().then(()=>this.transport.connect())}start(){return this.state===k.Started?(this.logger.warn("User agent already started"),Promise.resolve()):(this.logger.log(`Starting ${this.configuration.uri}`),this.transitionState(k.Started),this.transport.connect())}async stop(){if(this.state===k.Stopped)return this.logger.warn("User agent already stopped"),Promise.resolve();if(this.logger.log(`Stopping ${this.configuration.uri}`),!this.options.gracefulShutdown)return this.logger.log("Dispose of transport"),this.transport.dispose().catch(o=>{throw this.logger.error(o.message),o}),this.logger.log("Dispose of core"),this.userAgentCore.dispose(),this._publishers={},this._registerers={},this._sessions={},this._subscriptions={},this.transitionState(k.Stopped),Promise.resolve();const e=Object.assign({},this._publishers),t=Object.assign({},this._registerers),i=Object.assign({},this._sessions),s=Object.assign({},this._subscriptions),r=this.transport,n=this.userAgentCore;this.logger.log("Dispose of registerers");for(const o in t)t[o]&&await t[o].dispose().catch(d=>{throw this.logger.error(d.message),delete this._registerers[o],d});this.logger.log("Dispose of sessions");for(const o in i)i[o]&&await i[o].dispose().catch(d=>{throw this.logger.error(d.message),delete this._sessions[o],d});this.logger.log("Dispose of subscriptions");for(const o in s)s[o]&&await s[o].dispose().catch(d=>{throw this.logger.error(d.message),delete this._subscriptions[o],d});this.logger.log("Dispose of publishers");for(const o in e)e[o]&&await e[o].dispose().catch(d=>{throw this.logger.error(d.message),delete this._publishers[o],d});this.logger.log("Dispose of transport"),await r.dispose().catch(o=>{throw this.logger.error(o.message),o}),this.logger.log("Dispose of core"),n.dispose(),this.transitionState(k.Stopped)}_makeInviter(e,t){return new Re(this,e,t)}attemptReconnection(e=1){const t=this.options.reconnectionAttempts,i=this.options.reconnectionDelay;if(e>t){this.logger.log("Maximum reconnection attempts reached");return}this.logger.log(`Reconnection attempt ${e} of ${t} - trying`),setTimeout(()=>{this.reconnect().then(()=>{this.logger.log(`Reconnection attempt ${e} of ${t} - succeeded`)}).catch(s=>{this.logger.error(s.message),this.logger.log(`Reconnection attempt ${e} of ${t} - failed`),this.attemptReconnection(++e)})},e===1?0:i*1e3)}initContact(){const e=this.options.contactName!==""?this.options.contactName:oe(8),t=this.options.contactParams;return{pubGruu:void 0,tempGruu:void 0,uri:new W("sip",e,this.options.viaHost,void 0,t),toString:(s={})=>{const r=s.anonymous||!1,n=s.outbound||!1,o=s.register||!1;let d="<";return r?d+=this.contact.tempGruu||`sip:anonymous@anonymous.invalid;transport=${t.transport?t.transport:"ws"}`:o?d+=this.contact.uri:d+=this.contact.pubGruu||this.contact.uri,n&&(d+=";ob"),d+=">",this.options.instanceIdAlwaysAdded&&(d+=';+sip.instance="<urn:uuid:'+this._instanceId+'>"'),d}}}initCore(){let e=[];e.push("outbound"),this.options.sipExtension100rel===V.Supported&&e.push("100rel"),this.options.sipExtensionReplaces===V.Supported&&e.push("replaces"),this.options.sipExtensionExtraSupported&&e.push(...this.options.sipExtensionExtraSupported),this.options.hackAllowUnregisteredOptionTags||(e=e.filter(r=>kt[r])),e=Array.from(new Set(e));const t=e.slice();(this.contact.pubGruu||this.contact.tempGruu)&&t.push("gruu");const i={aor:this.options.uri,contact:this.contact,displayName:this.options.displayName,loggerFactory:this.loggerFactory,hackViaTcp:this.options.hackViaTcp,routeSet:this.options.preloadedRouteSet,supportedOptionTags:e,supportedOptionTagsResponse:t,sipjsId:this.options.sipjsId,userAgentHeaderFieldValue:this.options.userAgentString,viaForceRport:this.options.forceRport,viaHost:this.options.viaHost,authenticationFactory:()=>{const r=this.options.authorizationUsername?this.options.authorizationUsername:this.options.uri.user,n=this.options.authorizationPassword?this.options.authorizationPassword:void 0,o=this.options.authorizationHa1?this.options.authorizationHa1:void 0;return new Ht(this.getLoggerFactory(),o,r,n)},transportAccessor:()=>this.transport},s={onInvite:r=>{var n;const o=new de(this,r);if(r.delegate={onCancel:d=>{o._onCancel(d)},onTransportError:d=>{this.logger.error("A transport error has occurred while handling an incoming INVITE request.")}},r.trying(),this.options.sipExtensionReplaces!==V.Unsupported){const p=r.message.parseHeader("replaces");if(p){const g=p.call_id;if(typeof g!="string")throw new Error("Type of call id is not string");const w=p.replaces_to_tag;if(typeof w!="string")throw new Error("Type of to tag is not string");const S=p.replaces_from_tag;if(typeof S!="string")throw new Error("type of from tag is not string");const I=g+w+S,_=this.userAgentCore.dialogs.get(I);if(!_){o.reject({statusCode:481});return}if(!_.early&&p.early_only===!0){o.reject({statusCode:486});return}const E=this._sessions[g+S]||this._sessions[g+w]||void 0;if(!E)throw new Error("Session does not exist.");o._replacee=E}}if(!((n=this.delegate)===null||n===void 0)&&n.onInvite){if(o.autoSendAnInitialProvisionalResponse){o.progress().then(()=>{var d;if(((d=this.delegate)===null||d===void 0?void 0:d.onInvite)===void 0)throw new Error("onInvite undefined.");this.delegate.onInvite(o)});return}this.delegate.onInvite(o);return}o.reject({statusCode:486})},onMessage:r=>{if(this.delegate&&this.delegate.onMessage){const n=new Qe(r);this.delegate.onMessage(n)}else r.accept()},onNotify:r=>{if(this.delegate&&this.delegate.onNotify){const n=new qe(r);this.delegate.onNotify(n)}else this.options.allowLegacyNotifications?r.accept():r.reject({statusCode:481})},onRefer:r=>{this.logger.warn("Received an out of dialog REFER request"),this.delegate&&this.delegate.onReferRequest?this.delegate.onReferRequest(r):r.reject({statusCode:405})},onRegister:r=>{this.logger.warn("Received an out of dialog REGISTER request"),this.delegate&&this.delegate.onRegisterRequest?this.delegate.onRegisterRequest(r):r.reject({statusCode:405})},onSubscribe:r=>{this.logger.warn("Received an out of dialog SUBSCRIBE request"),this.delegate&&this.delegate.onSubscribeRequest?this.delegate.onSubscribeRequest(r):r.reject({statusCode:405})}};return new ti(i,s)}initTransportCallbacks(){this.transport.onConnect=()=>this.onTransportConnect(),this.transport.onDisconnect=e=>this.onTransportDisconnect(e),this.transport.onMessage=e=>this.onTransportMessage(e)}onTransportConnect(){this.state!==k.Stopped&&this.delegate&&this.delegate.onConnect&&this.delegate.onConnect()}onTransportDisconnect(e){this.state!==k.Stopped&&(this.delegate&&this.delegate.onDisconnect&&this.delegate.onDisconnect(e),e&&this.options.reconnectionAttempts>0&&this.attemptReconnection())}onTransportMessage(e){const t=xe.parseMessage(e,this.getLogger("sip.Parser"));if(!t){this.logger.warn("Failed to parse incoming message. Dropping.");return}if(this.state===k.Stopped&&t instanceof ue){this.logger.warn(`Received ${t.method} request while stopped. Dropping.`);return}const i=()=>{const s=["from","to","call_id","cseq","via"];for(const r of s)if(!t.hasHeader(r))return this.logger.warn(`Missing mandatory header field : ${r}.`),!1;return!0};if(t instanceof ue){if(!i()){this.logger.warn("Request missing mandatory header field. Dropping.");return}if(!t.toTag&&t.callId.substr(0,5)===this.options.sipjsId){this.userAgentCore.replyStateless(t,{statusCode:482});return}const s=ye(t.body),r=t.getHeader("content-length");if(r&&s<Number(r)){this.userAgentCore.replyStateless(t,{statusCode:400});return}}if(t instanceof ee){if(!i()){this.logger.warn("Response missing mandatory header field. Dropping.");return}if(t.getHeaders("via").length>1){this.logger.warn("More than one Via header field present in the response. Dropping.");return}if(t.via.host!==this.options.viaHost||t.via.port!==void 0){this.logger.warn("Via sent-by in the response does not match UA Via host value. Dropping.");return}const s=ye(t.body),r=t.getHeader("content-length");if(r&&s<Number(r)){this.logger.warn("Message body length is lower than the value in Content-Length header field. Dropping.");return}}if(t instanceof ue){this.userAgentCore.receiveIncomingRequestFromTransport(t);return}if(t instanceof ee){this.userAgentCore.receiveIncomingResponseFromTransport(t);return}throw new Error("Invalid message type.")}transitionState(e,t){const i=()=>{throw new Error(`Invalid state transition from ${this._state} to ${e}`)};switch(this._state){case k.Started:e!==k.Stopped&&i();break;case k.Stopped:e!==k.Started&&i();break;default:throw new Error("Unknown state.")}this.logger.log(`Transitioned from ${this._state} to ${e}`),this._state=e,this._stateEventEmitter.emit(this._state)}}function ni(){return(a,e)=>({session:e,held:!1,muted:!1})}class Ne{constructor(e,t={}){this.managedSessions=[],this.attemptingReconnection=!1,this.optionsPingFailure=!1,this.optionsPingRunning=!1,this.shouldBeConnected=!1,this.shouldBeRegistered=!1,this.delegate=t.delegate,this.options=Object.assign({aor:"",autoStop:!0,delegate:{},iceStopWaitingOnServerReflexive:!1,managedSessionFactory:ni(),maxSimultaneousSessions:2,media:{},optionsPingInterval:-1,optionsPingRequestURI:"",reconnectionAttempts:3,reconnectionDelay:4,registrationRetry:!1,registrationRetryInterval:3,registerGuard:null,registererOptions:{},registererRegisterOptions:{},sendDTMFUsingSessionDescriptionHandler:!1,userAgentOptions:{}},Ne.stripUndefinedProperties(t));const i=Object.assign({},t.userAgentOptions);if(i.transportConstructor||(i.transportConstructor=ve),i.transportOptions||(i.transportOptions={server:e}),!i.uri&&t.aor){const s=G.makeURI(t.aor);if(!s)throw new Error(`Failed to create valid URI from ${t.aor}`);i.uri=s}if(this.userAgent=new G(i),this.userAgent.delegate={onConnect:()=>{this.logger.log("Connected"),this.delegate&&this.delegate.onServerConnect&&this.delegate.onServerConnect(),this.shouldBeRegistered&&this.register(),this.options.optionsPingInterval>0&&this.optionsPingStart()},onDisconnect:async s=>{this.logger.log("Disconnected");let r=!1;this.options.optionsPingInterval>0&&(r=this.optionsPingFailure,this.optionsPingFailure=!1,this.optionsPingStop()),this.delegate&&this.delegate.onServerDisconnect&&this.delegate.onServerDisconnect(s),(s||r)&&(this.registerer&&(this.logger.log("Disposing of registerer..."),this.registerer.dispose().catch(n=>{this.logger.debug("Error occurred disposing of registerer after connection with server was lost."),this.logger.debug(n.toString())}),this.registerer=void 0),this.managedSessions.slice().map(n=>n.session).forEach(async n=>{this.logger.log("Disposing of session..."),n.dispose().catch(o=>{this.logger.debug("Error occurred disposing of a session after connection with server was lost."),this.logger.debug(o.toString())})}),this.shouldBeConnected&&this.attemptReconnection())},onInvite:s=>{this.logger.log(`[${s.id}] Received INVITE`);const r=this.options.maxSimultaneousSessions;if(r!==0&&this.managedSessions.length>r){this.logger.warn(`[${s.id}] Session already in progress, rejecting INVITE...`),s.reject().then(()=>{this.logger.log(`[${s.id}] Rejected INVITE`)}).catch(o=>{this.logger.error(`[${s.id}] Failed to reject INVITE`),this.logger.error(o.toString())});return}const n={sessionDescriptionHandlerOptions:{constraints:this.constraints}};this.initSession(s,n),this.delegate&&this.delegate.onCallReceived?this.delegate.onCallReceived(s):(this.logger.warn(`[${s.id}] No handler available, rejecting INVITE...`),s.reject().then(()=>{this.logger.log(`[${s.id}] Rejected INVITE`)}).catch(o=>{this.logger.error(`[${s.id}] Failed to reject INVITE`),this.logger.error(o.toString())}))},onMessage:s=>{s.accept().then(()=>{this.delegate&&this.delegate.onMessageReceived&&this.delegate.onMessageReceived(s)})},onNotify:s=>{s.accept().then(()=>{this.delegate&&this.delegate.onNotificationReceived&&this.delegate.onNotificationReceived(s)})}},this.registererOptions=Object.assign({},t.registererOptions),this.registererRegisterOptions=Object.assign({},t.registererRegisterOptions),this.options.registrationRetry){this.registererRegisterOptions.requestDelegate=this.registererRegisterOptions.requestDelegate||{};const s=this.registererRegisterOptions.requestDelegate.onReject;this.registererRegisterOptions.requestDelegate.onReject=r=>{s&&s(r),this.attemptRegistration()}}this.logger=this.userAgent.getLogger("sip.SessionManager"),window.addEventListener("online",()=>{this.logger.log("Online"),this.shouldBeConnected&&this.connect()}),this.options.autoStop&&window.addEventListener("beforeunload",async()=>{this.shouldBeConnected=!1,this.shouldBeRegistered=!1,this.userAgent.state!==k.Stopped&&await this.userAgent.stop()})}static stripUndefinedProperties(e){return Object.keys(e).reduce((t,i)=>(e[i]!==void 0&&(t[i]=e[i]),t),{})}getLocalMediaStream(e){const t=e.sessionDescriptionHandler;if(t){if(!(t instanceof O))throw new Error("Session description handler not instance of web SessionDescriptionHandler");return t.localMediaStream}}getRemoteMediaStream(e){const t=e.sessionDescriptionHandler;if(t){if(!(t instanceof O))throw new Error("Session description handler not instance of web SessionDescriptionHandler");return t.remoteMediaStream}}getLocalAudioTrack(e){var t;return(t=this.getLocalMediaStream(e))===null||t===void 0?void 0:t.getTracks().find(i=>i.kind==="audio")}getLocalVideoTrack(e){var t;return(t=this.getLocalMediaStream(e))===null||t===void 0?void 0:t.getTracks().find(i=>i.kind==="video")}getRemoteAudioTrack(e){var t;return(t=this.getRemoteMediaStream(e))===null||t===void 0?void 0:t.getTracks().find(i=>i.kind==="audio")}getRemoteVideoTrack(e){var t;return(t=this.getRemoteMediaStream(e))===null||t===void 0?void 0:t.getTracks().find(i=>i.kind==="video")}async connect(){return this.logger.log("Connecting UserAgent..."),this.shouldBeConnected=!0,this.userAgent.state!==k.Started?this.userAgent.start():this.userAgent.reconnect()}async disconnect(){return this.logger.log("Disconnecting UserAgent..."),this.userAgent.state===k.Stopped?Promise.resolve():(this.shouldBeConnected=!1,this.shouldBeRegistered=!1,this.registerer=void 0,this.userAgent.stop())}isConnected(){return this.userAgent.isConnected()}async register(e){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=>{switch(t){case R.Initial:break;case R.Registered:this.delegate&&this.delegate.onRegistered&&this.delegate.onRegistered();break;case R.Unregistered:this.delegate&&this.delegate.onUnregistered&&this.delegate.onUnregistered(),this.shouldBeRegistered&&this.attemptRegistration();break;case R.Terminated:break;default:throw new Error("Unknown registerer state.")}})),this.attemptRegistration(!0)}async unregister(e){return this.logger.log("Unregistering UserAgent..."),this.shouldBeRegistered=!1,this.registerer?this.registerer.unregister(e).then(()=>{}):(this.logger.warn("No registerer to unregister."),Promise.resolve())}async call(e,t,i){this.logger.log("Beginning Session...");const s=this.options.maxSimultaneousSessions;if(s!==0&&this.managedSessions.length>s)return Promise.reject(new Error("Maximum number of sessions already exists."));const r=G.makeURI(e);if(!r)return Promise.reject(new Error(`Failed to create a valid URI from "${e}"`));if(t||(t={}),t.sessionDescriptionHandlerOptions||(t.sessionDescriptionHandlerOptions={}),t.sessionDescriptionHandlerOptions.constraints||(t.sessionDescriptionHandlerOptions.constraints=this.constraints),t.earlyMedia){i=i||{},i.requestDelegate=i.requestDelegate||{};const o=i.requestDelegate.onProgress;i.requestDelegate.onProgress=d=>{d.message.statusCode===183&&this.setupRemoteMedia(n),o&&o(d)}}this.options.iceStopWaitingOnServerReflexive&&(t.delegate=t.delegate||{},t.delegate.onSessionDescriptionHandler=o=>{if(!(o instanceof O))throw new Error("Session description handler not instance of SessionDescriptionHandler");o.peerConnectionDelegate={onicecandidate:d=>{var p;((p=d.candidate)===null||p===void 0?void 0:p.type)==="srflx"&&(this.logger.log(`[${n.id}] Found srflx ICE candidate, stop waiting...`),o.iceGatheringComplete())}}});const n=new Re(this.userAgent,r,t);return this.sendInvite(n,t,i).then(()=>n)}async hangup(e){return this.logger.log(`[${e.id}] Hangup...`),this.sessionExists(e)?this.terminate(e):Promise.reject(new Error("Session does not exist."))}async answer(e,t){return this.logger.log(`[${e.id}] Accepting Invitation...`),this.sessionExists(e)?e instanceof de?(t||(t={}),t.sessionDescriptionHandlerOptions||(t.sessionDescriptionHandlerOptions={}),t.sessionDescriptionHandlerOptions.constraints||(t.sessionDescriptionHandlerOptions.constraints=this.constraints),e.accept(t)):Promise.reject(new Error("Session not instance of Invitation.")):Promise.reject(new Error("Session does not exist."))}async decline(e){return this.logger.log(`[${e.id}] Rejecting Invitation...`),this.sessionExists(e)?e instanceof de?e.reject():Promise.reject(new Error("Session not instance of Invitation.")):Promise.reject(new Error("Session does not exist."))}async hold(e){return this.logger.log(`[${e.id}] Holding session...`),this.setHold(e,!0)}async unhold(e){return this.logger.log(`[${e.id}] Unholding session...`),this.setHold(e,!1)}isHeld(e){const t=this.sessionManaged(e);return t?t.held:!1}mute(e){this.logger.log(`[${e.id}] Disabling media tracks...`),this.setMute(e,!0)}unmute(e){this.logger.log(`[${e.id}] Enabling media tracks...`),this.setMute(e,!1)}isMuted(e){const t=this.sessionManaged(e);return t?t.muted:!1}async sendDTMF(e,t){if(this.logger.log(`[${e.id}] Sending DTMF...`),!/^[0-9A-D#*,]$/.exec(t))return Promise.reject(new Error("Invalid DTMF tone."));if(!this.sessionExists(e))return Promise.reject(new Error("Session does not exist."));if(this.logger.log(`[${e.id}] Sending DTMF tone: ${t}`),this.options.sendDTMFUsingSessionDescriptionHandler)return e.sessionDescriptionHandler?e.sessionDescriptionHandler.sendDtmf(t)?Promise.resolve():Promise.reject(new Error("Failed to send DTMF")):Promise.reject(new Error("Session desciption handler undefined."));{const n={body:{contentDisposition:"render",contentType:"application/dtmf-relay",content:"Signal="+t+`\r
|
|
64
64
|
Duration=`+2e3}};return e.info({requestOptions:n}).then(()=>{})}}async transfer(e,t,i){if(this.logger.log(`[${e.id}] Referring session...`),t instanceof ge)return e.refer(t,i).then(()=>{});const s=G.makeURI(t);return s?e.refer(s,i).then(()=>{}):Promise.reject(new Error(`Failed to create a valid URI from "${t}"`))}async message(e,t){this.logger.log("Sending message...");const i=G.makeURI(e);return i?new Pt(this.userAgent,i,t).message():Promise.reject(new Error(`Failed to create a valid URI from "${e}"`))}get constraints(){let e={audio:!0,video:!1};return this.options.media.constraints&&(e=Object.assign({},this.options.media.constraints)),e}attemptReconnection(e=1){const t=this.options.reconnectionAttempts,i=this.options.reconnectionDelay;if(!this.shouldBeConnected){this.logger.log("Should not be connected currently");return}if(this.attemptingReconnection&&this.logger.log("Reconnection attempt already in progress"),e>t){this.logger.log("Reconnection maximum attempts reached");return}e===1?this.logger.log(`Reconnection attempt ${e} of ${t} - trying`):this.logger.log(`Reconnection attempt ${e} of ${t} - trying in ${i} seconds`),this.attemptingReconnection=!0,setTimeout(()=>{if(!this.shouldBeConnected){this.logger.log(`Reconnection attempt ${e} of ${t} - aborted`),this.attemptingReconnection=!1;return}this.userAgent.reconnect().then(()=>{this.logger.log(`Reconnection attempt ${e} of ${t} - succeeded`),this.attemptingReconnection=!1}).catch(s=>{this.logger.log(`Reconnection attempt ${e} of ${t} - failed`),this.logger.error(s.message),this.attemptingReconnection=!1,this.attemptReconnection(++e)})},e===1?0:i*1e3)}attemptRegistration(e=!1){if(this.logger.log(`Registration attempt ${e?"without delay":""}`),!this.shouldBeRegistered)return this.logger.log("Should not be registered currently"),Promise.resolve();if(this.registrationAttemptTimeout!==void 0)return this.logger.log("Registration attempt already in progress"),Promise.resolve();const t=()=>this.registerer?this.isConnected()?this.userAgent.state===k.Stopped?(this.logger.log("User agent stopped"),Promise.resolve()):this.options.registerGuard?this.options.registerGuard().catch(s=>{throw this.logger.log("Register guard rejected will making registration attempt"),s}).then(s=>s||!this.registerer?Promise.resolve():this.registerer.register(this.registererRegisterOptions).then(()=>{})):this.registerer.register(this.registererRegisterOptions).then(()=>{}):(this.logger.log("User agent not connected"),Promise.resolve()):(this.logger.log("Registerer undefined"),Promise.resolve()),i=s=>{const r=s*2;return 1e3*(Math.random()*(r-s)+s)};return new Promise((s,r)=>{this.registrationAttemptTimeout=setTimeout(()=>{t().then(()=>{this.registrationAttemptTimeout=void 0,s()}).catch(n=>{this.registrationAttemptTimeout=void 0,n instanceof pe?s():r(n)})},e?0:i(this.options.registrationRetryInterval))})}cleanupMedia(e){const t=this.sessionManaged(e);if(!t)throw new Error("Managed session does not exist.");t.mediaLocal&&t.mediaLocal.video&&(t.mediaLocal.video.srcObject=null,t.mediaLocal.video.pause()),t.mediaRemote&&(t.mediaRemote.audio&&(t.mediaRemote.audio.srcObject=null,t.mediaRemote.audio.pause()),t.mediaRemote.video&&(t.mediaRemote.video.srcObject=null,t.mediaRemote.video.pause()))}enableReceiverTracks(e,t){if(!this.sessionExists(e))throw new Error("Session does not exist.");const i=e.sessionDescriptionHandler;if(!(i instanceof O))throw new Error("Session's session description handler not instance of SessionDescriptionHandler.");i.enableReceiverTracks(t)}enableSenderTracks(e,t){if(!this.sessionExists(e))throw new Error("Session does not exist.");const i=e.sessionDescriptionHandler;if(!(i instanceof O))throw new Error("Session's session description handler not instance of SessionDescriptionHandler.");i.enableSenderTracks(t)}initSession(e,t){this.sessionAdd(e),this.delegate&&this.delegate.onCallCreated&&this.delegate.onCallCreated(e),e.stateChange.addListener(i=>{switch(this.logger.log(`[${e.id}] Session state changed to ${i}`),i){case m.Initial:break;case m.Establishing:break;case m.Established:this.setupLocalMedia(e),this.setupRemoteMedia(e),this.delegate&&this.delegate.onCallAnswered&&this.delegate.onCallAnswered(e);break;case m.Terminating:case m.Terminated:this.sessionExists(e)&&(this.cleanupMedia(e),this.sessionRemove(e),this.delegate&&this.delegate.onCallHangup&&this.delegate.onCallHangup(e));break;default:throw new Error("Unknown session state.")}}),e.delegate=e.delegate||{},e.delegate.onInfo=i=>{var s;if(((s=this.delegate)===null||s===void 0?void 0:s.onCallDTMFReceived)===void 0){i.reject();return}const r=i.request.getHeader("content-type");if(!r||!/^application\/dtmf-relay/i.exec(r)){i.reject();return}const n=i.request.body.split(`\r
|
|
65
|
-
`,2);if(n.length!==2){i.reject();return}let o;const d=/^(Signal\s*?=\s*?)([0-9A-D#*]{1})(\s)?.*/;if(n[0]!==void 0&&d.test(n[0])&&(o=n[0].replace(d,"$2")),!o){i.reject();return}let p;const g=/^(Duration\s?=\s?)([0-9]{1,4})(\s)?.*/;if(n[1]!==void 0&&g.test(n[1])&&(p=parseInt(n[1].replace(g,"$2"),10)),!p){i.reject();return}i.accept().then(()=>{if(this.delegate&&this.delegate.onCallDTMFReceived){if(!o||!p)throw new Error("Tone or duration undefined.");this.delegate.onCallDTMFReceived(e,o,p)}}).catch(w=>{this.logger.error(w.message)})},e.delegate.onRefer=i=>{i.accept().then(()=>this.sendInvite(i.makeInviter(t),t)).catch(s=>{this.logger.error(s.message)})}}optionsPingRun(e,t,i){if(this.options.optionsPingInterval<1)throw new Error("Invalid options ping interval.");this.optionsPingRunning||(this.optionsPingRunning=!0,this.optionsPingTimeout=setTimeout(()=>{this.optionsPingTimeout=void 0;const s=()=>{this.optionsPingFailure=!1,this.optionsPingRunning&&(this.optionsPingRunning=!1,this.optionsPingRun(e,t,i))},r=()=>{this.logger.error("OPTIONS ping failed"),this.optionsPingFailure=!0,this.optionsPingRunning=!1,this.userAgent.transport.disconnect().catch(d=>this.logger.error(d))},n=this.userAgent.userAgentCore,o=n.makeOutgoingRequestMessage("OPTIONS",e,t,i,{});this.optionsPingRequest=n.request(o,{onAccept:()=>{this.optionsPingRequest=void 0,s()},onReject:d=>{this.optionsPingRequest=void 0,d.message.statusCode===408||d.message.statusCode===503?r():s()}})},this.options.optionsPingInterval*1e3))}optionsPingStart(){this.logger.log("OPTIONS pings started");let e,t,i;if(this.options.optionsPingRequestURI){if(e=G.makeURI(this.options.optionsPingRequestURI),!e)throw new Error("Failed to create Request URI.");t=this.userAgent.contact.uri.clone(),i=this.userAgent.contact.uri.clone()}else if(this.options.aor){const s=G.makeURI(this.options.aor);if(!s)throw new Error("Failed to create URI.");e=s.clone(),e.user=void 0,t=s.clone(),i=s.clone()}else{this.logger.error("You have enabled sending OPTIONS pings and as such you must provide either a) an AOR to register, or b) an RURI to use for the target of the OPTIONS ping requests. ");return}this.optionsPingRun(e,t,i)}optionsPingStop(){this.logger.log("OPTIONS pings stopped"),this.optionsPingRunning=!1,this.optionsPingFailure=!1,this.optionsPingRequest&&(this.optionsPingRequest.dispose(),this.optionsPingRequest=void 0),this.optionsPingTimeout&&(clearTimeout(this.optionsPingTimeout),this.optionsPingTimeout=void 0)}async sendInvite(e,t,i){return this.initSession(e,t),e.invite(i).then(()=>{this.logger.log(`[${e.id}] Sent INVITE`)})}sessionAdd(e){const t=this.options.managedSessionFactory(this,e);this.managedSessions.push(t)}sessionExists(e){return this.sessionManaged(e)!==void 0}sessionManaged(e){return this.managedSessions.find(t=>t.session.id===e.id)}sessionRemove(e){this.managedSessions=this.managedSessions.filter(t=>t.session.id!==e.id)}async setHold(e,t){if(!this.sessionExists(e))return Promise.reject(new Error("Session does not exist."));if(this.isHeld(e)===t)return Promise.resolve();if(!(e.sessionDescriptionHandler instanceof O))throw new Error("Session's session description handler not instance of SessionDescriptionHandler.");const s={requestDelegate:{onAccept:()=>{const o=this.sessionManaged(e);o!==void 0&&(o.held=t,this.enableReceiverTracks(e,!o.held),this.enableSenderTracks(e,!o.held&&!o.muted),this.delegate&&this.delegate.onCallHold&&this.delegate.onCallHold(e,o.held))},onReject:()=>{this.logger.warn(`[${e.id}] Re-invite request was rejected`);const o=this.sessionManaged(e);o!==void 0&&(o.held=!t,this.enableReceiverTracks(e,!o.held),this.enableSenderTracks(e,!o.held&&!o.muted),this.delegate&&this.delegate.onCallHold&&this.delegate.onCallHold(e,o.held))}}},r=e.sessionDescriptionHandlerOptionsReInvite;r.hold=t,e.sessionDescriptionHandlerOptionsReInvite=r;const n=this.sessionManaged(e);if(!n)throw new Error("Managed session is undefiend.");return n.held=t,e.invite(s).then(()=>{const o=this.sessionManaged(e);o!==void 0&&(this.enableReceiverTracks(e,!o.held),this.enableSenderTracks(e,!o.held&&!o.muted))}).catch(o=>{throw n.held=!t,o instanceof pe&&this.logger.error(`[${e.id}] A hold request is already in progress.`),o})}setMute(e,t){if(!this.sessionExists(e)){this.logger.warn(`[${e.id}] A session is required to enabled/disable media tracks`);return}if(e.state!==m.Established){this.logger.warn(`[${e.id}] An established session is required to enable/disable media tracks`);return}const i=this.sessionManaged(e);i!==void 0&&(i.muted=t,this.enableSenderTracks(e,!i.held&&!i.muted))}setupLocalMedia(e){const t=this.sessionManaged(e);if(!t)throw new Error("Managed session does not exist.");const i=typeof this.options.media.local=="function"?this.options.media.local(e):this.options.media.local;t.mediaLocal=i;const s=i?.video;if(s){const r=this.getLocalMediaStream(e);if(!r)throw new Error("Local media stream undefiend.");s.srcObject=r,s.volume=0,s.play().catch(n=>{this.logger.error(`[${e.id}] Failed to play local media`),this.logger.error(n.message)})}}setupRemoteMedia(e){const t=this.sessionManaged(e);if(!t)throw new Error("Managed session does not exist.");const i=typeof this.options.media.remote=="function"?this.options.media.remote(e):this.options.media.remote;t.mediaRemote=i;const s=i?.video||i?.audio;if(s){const r=this.getRemoteMediaStream(e);if(!r)throw new Error("Remote media stream undefiend.");s.autoplay=!0,s.srcObject=r,s.play().catch(n=>{this.logger.error(`[${e.id}] Failed to play remote media`),this.logger.error(n.message)}),r.onaddtrack=()=>{this.logger.log("Remote media onaddtrack"),s.load(),s.play().catch(n=>{this.logger.error(`[${e.id}] Failed to play remote media`),this.logger.error(n.message)})}}}async terminate(e){switch(this.logger.log(`[${e.id}] Terminating...`),e.state){case m.Initial:if(e instanceof Re)return e.cancel().then(()=>{this.logger.log(`[${e.id}] Inviter never sent INVITE (canceled)`)});if(e instanceof de)return e.reject().then(()=>{this.logger.log(`[${e.id}] Invitation rejected (sent 480)`)});throw new Error("Unknown session type.");case m.Establishing:if(e instanceof Re)return e.cancel().then(()=>{this.logger.log(`[${e.id}] Inviter canceled (sent CANCEL)`)});if(e instanceof de)return e.reject().then(()=>{this.logger.log(`[${e.id}] Invitation rejected (sent 480)`)});throw new Error("Unknown session type.");case m.Established:return e.bye().then(()=>{this.logger.log(`[${e.id}] Session ended (sent BYE)`)});case m.Terminating:break;case m.Terminated:break;default:throw new Error("Unknown state")}return this.logger.log(`[${e.id}] Terminating in state ${e.state}, no action taken`),Promise.resolve()}}class ai{constructor(e,t={}){this.session=void 0,this.delegate=t.delegate,this.options=Object.assign({},t);const i={aor:this.options.aor,delegate:{onCallAnswered:()=>{var s,r;return(r=(s=this.delegate)===null||s===void 0?void 0:s.onCallAnswered)===null||r===void 0?void 0:r.call(s)},onCallCreated:s=>{var r,n;this.session=s,(n=(r=this.delegate)===null||r===void 0?void 0:r.onCallCreated)===null||n===void 0||n.call(r)},onCallReceived:()=>{var s,r;return(r=(s=this.delegate)===null||s===void 0?void 0:s.onCallReceived)===null||r===void 0?void 0:r.call(s)},onCallHangup:()=>{var s,r;this.session=void 0,!((s=this.delegate)===null||s===void 0)&&s.onCallHangup&&((r=this.delegate)===null||r===void 0||r.onCallHangup())},onCallHold:(s,r)=>{var n,o;return(o=(n=this.delegate)===null||n===void 0?void 0:n.onCallHold)===null||o===void 0?void 0:o.call(n,r)},onCallDTMFReceived:(s,r,n)=>{var o,d;return(d=(o=this.delegate)===null||o===void 0?void 0:o.onCallDTMFReceived)===null||d===void 0?void 0:d.call(o,r,n)},onMessageReceived:s=>{var r,n;return(n=(r=this.delegate)===null||r===void 0?void 0:r.onMessageReceived)===null||n===void 0?void 0:n.call(r,s.request.body)},onRegistered:()=>{var s,r;return(r=(s=this.delegate)===null||s===void 0?void 0:s.onRegistered)===null||r===void 0?void 0:r.call(s)},onUnregistered:()=>{var s,r;return(r=(s=this.delegate)===null||s===void 0?void 0:s.onUnregistered)===null||r===void 0?void 0:r.call(s)},onServerConnect:()=>{var s,r;return(r=(s=this.delegate)===null||s===void 0?void 0:s.onServerConnect)===null||r===void 0?void 0:r.call(s)},onServerDisconnect:()=>{var s,r;return(r=(s=this.delegate)===null||s===void 0?void 0:s.onServerDisconnect)===null||r===void 0?void 0:r.call(s)}},maxSimultaneousSessions:1,media:this.options.media,reconnectionAttempts:this.options.reconnectionAttempts,reconnectionDelay:this.options.reconnectionDelay,registererOptions:this.options.registererOptions,sendDTMFUsingSessionDescriptionHandler:this.options.sendDTMFUsingSessionDescriptionHandler,userAgentOptions:this.options.userAgentOptions};this.sessionManager=new Ne(e,i),this.logger=this.sessionManager.userAgent.getLogger("sip.SimpleUser")}get id(){return this.options.userAgentOptions&&this.options.userAgentOptions.displayName||"Anonymous"}get localMediaStream(){return this.session&&this.sessionManager.getLocalMediaStream(this.session)}get remoteMediaStream(){return this.session&&this.sessionManager.getRemoteMediaStream(this.session)}get localAudioTrack(){return this.session&&this.sessionManager.getLocalAudioTrack(this.session)}get localVideoTrack(){return this.session&&this.sessionManager.getLocalVideoTrack(this.session)}get remoteAudioTrack(){return this.session&&this.sessionManager.getRemoteAudioTrack(this.session)}get remoteVideoTrack(){return this.session&&this.sessionManager.getRemoteVideoTrack(this.session)}connect(){return this.logger.log(`[${this.id}] Connecting UserAgent...`),this.sessionManager.connect()}disconnect(){return this.logger.log(`[${this.id}] Disconnecting UserAgent...`),this.sessionManager.disconnect()}isConnected(){return this.sessionManager.isConnected()}register(e){return this.logger.log(`[${this.id}] Registering UserAgent...`),this.sessionManager.register(e)}unregister(e){return this.logger.log(`[${this.id}] Unregistering UserAgent...`),this.sessionManager.unregister(e)}call(e,t,i){return this.logger.log(`[${this.id}] Beginning Session...`),this.session?Promise.reject(new Error("Session already exists.")):this.sessionManager.call(e,t,i).then(()=>{})}hangup(){return this.logger.log(`[${this.id}] Hangup...`),this.session?this.sessionManager.hangup(this.session).then(()=>{this.session=void 0}):Promise.reject(new Error("Session does not exist."))}answer(e){return this.logger.log(`[${this.id}] Accepting Invitation...`),this.session?this.sessionManager.answer(this.session,e):Promise.reject(new Error("Session does not exist."))}decline(){return this.logger.log(`[${this.id}] rejecting Invitation...`),this.session?this.sessionManager.decline(this.session):Promise.reject(new Error("Session does not exist."))}hold(){return this.logger.log(`[${this.id}] holding session...`),this.session?this.sessionManager.hold(this.session):Promise.reject(new Error("Session does not exist."))}unhold(){return this.logger.log(`[${this.id}] unholding session...`),this.session?this.sessionManager.unhold(this.session):Promise.reject(new Error("Session does not exist."))}isHeld(){return this.session?this.sessionManager.isHeld(this.session):!1}mute(){return this.logger.log(`[${this.id}] disabling media tracks...`),this.session&&this.sessionManager.mute(this.session)}unmute(){return this.logger.log(`[${this.id}] enabling media tracks...`),this.session&&this.sessionManager.unmute(this.session)}isMuted(){return this.session?this.sessionManager.isMuted(this.session):!1}sendDTMF(e){return this.logger.log(`[${this.id}] sending DTMF...`),this.session?this.sessionManager.sendDTMF(this.session,e):Promise.reject(new Error("Session does not exist."))}message(e,t){return this.logger.log(`[${this.id}] sending message...`),this.sessionManager.message(e,t)}}const oi=15e3,ci=15e3,Ue=(a,e,t)=>{const i=String(a?.name||a?.code||"").trim(),s=new Error(e);return s.name=i||"MediaAccessError",s.code=t,s.cause=a,s},Le=a=>{const e=new Error(a);return e.name="SipClientLifecycleCancelledError",e.code="sip_client_replaced",e},Be=async(a,e="call")=>{const t=String(a?.name||a?.code||"").trim(),i=String(a?.message||"").trim(),s=`${t} ${i}`.toLowerCase();let r="";try{if(typeof navigator<"u"&&navigator.permissions?.query){const n=await navigator.permissions.query({name:"microphone"});r=String(n?.state||"").trim().toLowerCase()}}catch{r=""}return r==="denied"||t==="NotAllowedError"||t==="SecurityError"||s.includes("notallowederror")||s.includes("permission denied")||s.includes("permission dismissed")||s.includes("microphone permission")||s.includes("getusermedia() no permission")?Ue(a,`浏览器未授予麦克风权限,无法${e==="answer"?"接听来电":"发起通话"}。请点击地址栏的麦克风权限并选择“允许”,然后刷新页面重试。`,"media_permission_denied"):t==="NotFoundError"||t==="DevicesNotFoundError"||s.includes("notfounderror")||s.includes("requested device not found")||s.includes("no audio input device")?Ue(a,"未检测到可用的麦克风设备,无法建立语音通话。请连接耳麦或启用系统麦克风后重试。","media_device_not_found"):t==="NotReadableError"||t==="TrackStartError"||s.includes("notreadableerror")||s.includes("could not start audio source")?Ue(a,"麦克风当前被系统或其他应用占用,无法建立语音通话。请关闭占用麦克风的程序后重试。","media_device_unavailable"):a},di=async(a="call")=>{if(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia)return null;let e=null;try{return e=await navigator.mediaDevices.getUserMedia({audio:!0,video:!1}),e}catch(t){throw await Be(t,a)}};class li{constructor(e,t={}){this.account={...e},this.remoteAudioElementId=t.remoteAudioElementId||"sipRemoteAudio",this.remoteAudioElement=t.remoteAudioElement||null,this.onStateChange=t.onStateChange,this.onError=t.onError,this.onAudioLevel=t.onAudioLevel,this.onAudioFrame=t.onAudioFrame,this.simpleUser=null,this.connected=!1,this.registered=!1,this.active=!1,this.lastFailureMeta=null,this.audioContext=null,this.audioMonitors={local:null,remote:null},this.audioTrackMeta={local:null,remote:null},this.ringbackAudio=null,this.ringbackPlaying=!1,this.incomingAudio=null,this.incomingPlaying=!1,this.hangupInitiatedByLocal=!1,this.ringbackToneUrl=this._resolveToneUrl(t.ringbackTone||"ringback1.wav"),this.incomingToneUrl=this._resolveToneUrl(t.incomingTone||"ring1.wav"),this.pendingPlaybackTasks=new Map,this.audioUnlockHandler=null,this.lastSessionMeta=null,this.ensureReadyPromise=null,this.connectionWaiters=new Set,this.registrationWaiters=new Set,this.connectionConfirmTimeoutMs=Number.isFinite(Number(t.connectionConfirmTimeoutMs))?Number(t.connectionConfirmTimeoutMs):oi,this.registrationConfirmTimeoutMs=Number.isFinite(Number(t.registrationConfirmTimeoutMs))?Number(t.registrationConfirmTimeoutMs):ci,this.peerConnectionDebug={attached:!1,events:[],iceCandidates:[],iceCandidateErrors:[]}}getAudioTrackMeta(e){return e&&this.audioTrackMeta?.[e]?{...this.audioTrackMeta[e]}:null}matchesAccount(e){return this.account?this.account.sip_username===e.sip_username&&this.account.sip_domain===e.sip_domain&&(this.account.port||"443")===(e.port||"443")&&(this.account.webrtc_url||"")===(e.webrtc_url||"")&&JSON.stringify(this.account.ice_servers||[])===JSON.stringify(e.ice_servers||[]):!1}_getRemoteAudioElement(){if(this.remoteAudioElement instanceof HTMLAudioElement)return this.remoteAudioElement.autoplay=!0,this.remoteAudioElement.playsInline=!0,this.remoteAudioElement.muted=!1,this.remoteAudioElement.volume=1,this.remoteAudioElement;const e=document.getElementById(this.remoteAudioElementId);if(!(e instanceof HTMLAudioElement))throw new Error("找不到用于播放远端音频的 audio 元素");return e.autoplay=!0,e.playsInline=!0,e.muted=!1,e.volume=1,e}_getPeerConnection(){const t=this.simpleUser?.session?.sessionDescriptionHandler;return t?.peerConnection||t?._peerConnection||null}_summarizeSessionDescription(e){if(!e?.sdp)return null;const t=String(e.sdp).split(/\r?\n/).map(s=>s.trim()).filter(Boolean),i=["o=","c=","m=","a=ice-","a=candidate","a=setup:","a=fingerprint:","a=rtcp-mux","a=mid:"];return{type:e.type||"",lineCount:t.length,importantLines:t.filter(s=>i.some(r=>s.startsWith(r))),candidateLines:t.filter(s=>s.startsWith("a=candidate")),iceLines:t.filter(s=>s.startsWith("a=ice-"))}}async getMediaDiagnostics(){const e=this._getPeerConnection();this._ensurePeerConnectionDebug(e);const t=document.getElementById(this.remoteAudioElementId),i={hasPeerConnection:!!e,signalingState:e?.signalingState||"",connectionState:e?.connectionState||"",iceConnectionState:e?.iceConnectionState||"",iceGatheringState:e?.iceGatheringState||"",peerConnectionEvents:this.peerConnectionDebug.events.slice(-20),iceCandidateErrors:this.peerConnectionDebug.iceCandidateErrors.slice(-20),localDescription:this._summarizeSessionDescription(e?.localDescription),remoteDescription:this._summarizeSessionDescription(e?.remoteDescription),remoteAudio:t instanceof HTMLAudioElement?{paused:t.paused,muted:t.muted,volume:t.volume,readyState:t.readyState,currentTime:t.currentTime,srcObjectTracks:t.srcObject?.getTracks?.().map(n=>({kind:n.kind,enabled:n.enabled,muted:n.muted,readyState:n.readyState,id:n.id}))||[]}:null};if(!e||typeof e.getStats!="function")return i;const s=await e.getStats(),r={inboundAudio:null,outboundAudio:null,selectedCandidatePair:null,localCandidate:null,remoteCandidate:null,localCandidates:[],remoteCandidates:[],candidatePairs:[]};for(const n of s.values()){if(n.type==="inbound-rtp"&&n.kind==="audio"&&!n.isRemote&&(r.inboundAudio={packetsReceived:n.packetsReceived||0,bytesReceived:n.bytesReceived||0,packetsLost:n.packetsLost||0,jitter:n.jitter||0,codecId:n.codecId||"",transportId:n.transportId||""}),n.type==="outbound-rtp"&&n.kind==="audio"&&!n.isRemote&&(r.outboundAudio={packetsSent:n.packetsSent||0,bytesSent:n.bytesSent||0,retransmittedPacketsSent:n.retransmittedPacketsSent||0,codecId:n.codecId||"",transportId:n.transportId||""}),n.type==="transport"&&n.selectedCandidatePairId&&!r.selectedCandidatePair){const o=s.get(n.selectedCandidatePairId);o&&(r.selectedCandidatePair={state:o.state||"",localCandidateId:o.localCandidateId||"",remoteCandidateId:o.remoteCandidateId||"",bytesSent:o.bytesSent||0,bytesReceived:o.bytesReceived||0,currentRoundTripTime:o.currentRoundTripTime||0},r.localCandidate=s.get(o.localCandidateId)||null,r.remoteCandidate=s.get(o.remoteCandidateId)||null)}n.type==="local-candidate"&&r.localCandidates.push({id:n.id||"",candidateType:n.candidateType||"",protocol:n.protocol||"",address:n.address||n.ip||"",port:n.port||0,relayProtocol:n.relayProtocol||"",url:n.url||"",networkType:n.networkType||""}),n.type==="remote-candidate"&&r.remoteCandidates.push({id:n.id||"",candidateType:n.candidateType||"",protocol:n.protocol||"",address:n.address||n.ip||"",port:n.port||0,relayProtocol:n.relayProtocol||"",url:n.url||""}),n.type==="candidate-pair"&&r.candidatePairs.push({id:n.id||"",state:n.state||"",nominated:!!n.nominated,writable:!!n.writable,readable:!!n.readable,requestsSent:n.requestsSent||0,requestsReceived:n.requestsReceived||0,responsesSent:n.responsesSent||0,responsesReceived:n.responsesReceived||0,bytesSent:n.bytesSent||0,bytesReceived:n.bytesReceived||0,currentRoundTripTime:n.currentRoundTripTime||0,localCandidateId:n.localCandidateId||"",remoteCandidateId:n.remoteCandidateId||""})}return{...i,...r}}_ensurePeerConnectionDebug(e){if(!e||this.peerConnectionDebug.attached)return;this.peerConnectionDebug.attached=!0;const t=(i,s={})=>{this.peerConnectionDebug.events.push({type:i,timestamp:new Date().toISOString(),...s}),this.peerConnectionDebug.events.length>100&&(this.peerConnectionDebug.events=this.peerConnectionDebug.events.slice(-100))};e.addEventListener("connectionstatechange",()=>{t("connectionstatechange",{connectionState:e.connectionState||""})}),e.addEventListener("iceconnectionstatechange",()=>{t("iceconnectionstatechange",{iceConnectionState:e.iceConnectionState||""})}),e.addEventListener("icegatheringstatechange",()=>{t("icegatheringstatechange",{iceGatheringState:e.iceGatheringState||""})}),e.addEventListener("signalingstatechange",()=>{t("signalingstatechange",{signalingState:e.signalingState||""})}),e.addEventListener("icecandidate",i=>{const s=i?.candidate;if(!s){t("icecandidate-complete");return}const r={candidate:s.candidate||"",address:s.address||"",port:s.port||0,protocol:s.protocol||"",type:s.type||"",foundation:s.foundation||"",relatedAddress:s.relatedAddress||"",relatedPort:s.relatedPort||0,sdpMid:s.sdpMid||"",sdpMLineIndex:s.sdpMLineIndex??null};this.peerConnectionDebug.iceCandidates.push(r),this.peerConnectionDebug.iceCandidates.length>100&&(this.peerConnectionDebug.iceCandidates=this.peerConnectionDebug.iceCandidates.slice(-100)),t("icecandidate",r)}),e.addEventListener("icecandidateerror",i=>{const s={address:i?.address||"",port:i?.port||0,url:i?.url||"",errorCode:i?.errorCode||0,errorText:i?.errorText||""};this.peerConnectionDebug.iceCandidateErrors.push({timestamp:new Date().toISOString(),...s}),this.peerConnectionDebug.iceCandidateErrors.length>100&&(this.peerConnectionDebug.iceCandidateErrors=this.peerConnectionDebug.iceCandidateErrors.slice(-100)),t("icecandidateerror",s)}),e.addEventListener("track",i=>{t("track",{streams:Array.isArray(i?.streams)?i.streams.map(s=>({id:s.id,audioTracks:s.getAudioTracks().map(r=>({id:r.id,enabled:r.enabled,muted:r.muted,readyState:r.readyState}))})):[]})})}_createSimpleUser(){const{sip_username:e,sip_password:t,sip_domain:i,port:s,webrtc_url:r,ice_servers:n}=this.account,o=s||"443",d=r||(/^wss?:\/\//.test(i)?i:`wss://${i}:${o}`),g={aor:`sip:${e}@${i}`,media:{constraints:{audio:{echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0},video:!1},remote:{audio:this._getRemoteAudioElement()}},userAgentOptions:{authorizationUsername:e,authorizationPassword:t,sessionDescriptionHandlerFactoryOptions:{peerConnectionConfiguration:n?.length?{iceServers:n,iceTransportPolicy:"all"}:void 0}},registererOptions:{expires:1800}},w=new ai(d,g);return w.delegate={onServerConnect:()=>{this.connected=!0,this._resolveConnectionWaiters(),this.onStateChange&&this.onStateChange("connected",this.getConnectionSnapshot())},onServerDisconnect:S=>{this.connected=!1,this.registered=!1,this.active=!1,this._rejectConnectionWaiters(S||new Error("呼叫服务器已断开")),this._rejectRegistrationWaiters(S||new Error("呼叫服务器已断开")),this.onError&&this.onError(S||new Error("呼叫服务器已断开")),this.onStateChange&&this.onStateChange("disconnected",this.getConnectionSnapshot()),this._stopRingback(),this._stopIncomingTone()},onRegistered:()=>{this.registered=!0,this._resolveRegistrationWaiters(),this.onStateChange&&this.onStateChange("registered",this.getConnectionSnapshot())},onUnregistered:()=>{this.registered=!1,this._rejectRegistrationWaiters(new Error("SIP 注册已被取消或失效。")),this.onStateChange&&this.onStateChange("unregistered",this.getConnectionSnapshot())},onCallCreated:()=>{const S=this._isIncomingSession();this.lastSessionMeta=this._buildIncomingCallMeta(),!S&&this.onStateChange&&(this.onStateChange("dialing"),this._scheduleLocalMonitor())},onCallReceived:()=>{this.lastSessionMeta=this._buildIncomingCallMeta(),this._playIncomingTone(),this.onStateChange&&this.onStateChange("incoming",this._buildIncomingCallMeta())},onCallAnswered:()=>{this.active=!0,this.lastSessionMeta=this._buildIncomingCallMeta(),this.onStateChange&&this.onStateChange("answered",this._buildIncomingCallMeta()),this._stopRingback(),this._stopIncomingTone(),this._scheduleRemoteMonitor()},onCallHangup:()=>{const S=this.active;if(this.active=!1,this.onStateChange){const I=this.lastFailureMeta?{...this.lastFailureMeta}:{};I.hangupSource=this.hangupInitiatedByLocal?"local":S?"remote":"unknown";const _=this.lastSessionMeta||this._buildIncomingCallMeta();_&&Object.keys(_).length>0&&(I.callMeta=_),this.onStateChange("terminated",I)}this.hangupInitiatedByLocal=!1,this._stopAllAudioMonitors(),this._reportAudioSilence(),this._stopRingback(),this._stopIncomingTone(),this.lastFailureMeta=null,this.lastSessionMeta=null}},w}_encodeSipUserPart(e){if(!e)return"";let t=e;try{t=decodeURIComponent(e)}catch{t=e}return encodeURIComponent(t).replace(/%2B/g,"+")}_normalizeSipDestination(e){const t=(e||"").trim();if(!t)throw new Error("呼叫目标地址不能为空");const i=t.match(/^(sips?:)/i),s=i?i[1].toLowerCase():"sip:",r=i?t.slice(i[1].length):t,n=r.indexOf("@");if(n===-1)return`${s}${this._encodeSipUserPart(r)}`;const o=r.slice(0,n),d=r.slice(n+1),p=this._encodeSipUserPart(o);return`${s}${p}@${d}`}_isIncomingSession(){if(!this.simpleUser||!this.simpleUser.session)return!1;if(typeof de<"u"&&this.simpleUser.session instanceof de)return!0;const e=this.simpleUser.session;return!!(e&&e.incomingInviteRequest)}_buildIncomingCallMeta(){const e=this.simpleUser?.session;if(!e||!e.remoteIdentity)return{};const t=e.remoteIdentity,i=t?.uri;return{displayName:t?.displayName||t?.friendlyName||"",uri:typeof t?.toString=="function"?t.toString():i?.toString?.()||"",user:i?.user||"",host:i?.host||"",direction:this._isIncomingSession()?"inbound":"outbound"}}_assertSimpleUserIsCurrent(e,t="当前操作"){if(!(e&&this.simpleUser===e&&this.account))throw Le(`SIP 客户端已重置,已取消${t}。`)}_resolveRegistrationWaiters(){if(!this.registrationWaiters.size)return;const e=Array.from(this.registrationWaiters);this.registrationWaiters.clear(),e.forEach(t=>{try{t.resolve()}catch{}})}_rejectRegistrationWaiters(e){if(!this.registrationWaiters.size)return;const t=Array.from(this.registrationWaiters);this.registrationWaiters.clear();const i=e instanceof Error?e:new Error("SIP 注册未完成即已中断。");t.forEach(s=>{try{s.reject(i)}catch{}})}_resolveConnectionWaiters(){if(!this.connectionWaiters.size)return;const e=Array.from(this.connectionWaiters);this.connectionWaiters.clear(),e.forEach(t=>{try{t.resolve()}catch{}})}_rejectConnectionWaiters(e){if(!this.connectionWaiters.size)return;const t=Array.from(this.connectionWaiters);this.connectionWaiters.clear();const i=e instanceof Error?e:new Error("SIP 连接未完成即已中断。");t.forEach(s=>{try{s.reject(i)}catch{}})}_waitForConnectionConfirmation(e=this.connectionConfirmTimeoutMs){return this.connected?Promise.resolve():new Promise((t,i)=>{const s={resolve:()=>{clearTimeout(r),t()},reject:n=>{clearTimeout(r),i(n)}},r=setTimeout(()=>{this.connectionWaiters.delete(s);const n=this.account?.internal_number||this.account?.account_key||this.account?.sip_username||"当前账号";i(new Error(`${n} 浏览器连接超时,未收到 SIP 服务器连接确认。`))},e);this.connectionWaiters.add(s)})}_waitForRegistrationConfirmation(e=this.registrationConfirmTimeoutMs){return this.registered?Promise.resolve():new Promise((t,i)=>{const s={resolve:()=>{clearTimeout(r),t()},reject:n=>{clearTimeout(r),i(n)}},r=setTimeout(()=>{this.registrationWaiters.delete(s);const n=this.account?.internal_number||this.account?.account_key||this.account?.sip_username||"当前账号";i(new Error(`${n} 浏览器注册超时,信令已连接但未收到注册确认。`))},e);this.registrationWaiters.add(s)})}_captureRegistrationDiagnostics(){return{snapshot:this.getConnectionSnapshot(),shouldBeRegistered:!!this.simpleUser?.sessionManager?.shouldBeRegistered,hasRegisterer:!!this.simpleUser?.sessionManager?.registerer}}_shouldRecoverFromConnectedWithoutRegistration(e){return!!(e?.snapshot?.connected&&!e?.snapshot?.registered&&!e?.shouldBeRegistered&&!e?.hasRegisterer)}async _ensureRegistration(e){try{this._assertSimpleUserIsCurrent(e,"注册流程"),await e.register(),this._assertSimpleUserIsCurrent(e,"注册流程"),this.registered||(await this._waitForRegistrationConfirmation(),this._assertSimpleUserIsCurrent(e,"注册确认"))}catch(t){throw this.onError&&this.onError(t),t}}async ensureReady(){if(this.ensureReadyPromise)return this.ensureReadyPromise;const e=(async()=>{let t=this.simpleUser;if(t||(t=this._createSimpleUser(),this.simpleUser=t),!this.connected){let i=null;const s=t.connect().catch(r=>(i=r,null));if(this.connected||await Promise.race([s,this._waitForConnectionConfirmation()]),this.connected||await s,i)throw i;this.connected||await this._waitForConnectionConfirmation(),this._assertSimpleUserIsCurrent(t,"连接流程"),this.connected=!0,this.onStateChange&&this.onStateChange("connected",this.getConnectionSnapshot())}if(!this.registered){let i=null;try{await this._ensureRegistration(t)}catch(r){i=r}let s=this._captureRegistrationDiagnostics();if(i&&this._shouldRecoverFromConnectedWithoutRegistration(s)){console.warn("SIP 客户端首次建连后未真正进入注册流程,开始立即补注册。",{accountKey:this.account?.internal_number||this.account?.account_key||this.account?.sip_username||null,snapshot:s.snapshot}),await new Promise(r=>window.setTimeout(r,200));try{await this._ensureRegistration(t)}catch(r){i=i||r}s=this._captureRegistrationDiagnostics()}if(i&&!(s?.snapshot?.connected&&s?.snapshot?.registered))throw i}})().finally(()=>{this.ensureReadyPromise===e&&(this.ensureReadyPromise=null)});return this.ensureReadyPromise=e,e}getConnectionSnapshot(){const e=this.simpleUser,t=typeof e?.isConnected=="function"?e.isConnected():!!this.connected,i=typeof e?.isRegistered=="function"?e.isRegistered():!!this.registered;return{connected:t,registered:i,active:!!this.active,sipDomain:this.account?.sip_domain||"",transportUrl:this.account?.webrtc_url||""}}async disconnect(e={}){const{unregister:t=!0}=e||{};if(!this.simpleUser){this.connected=!1,this.registered=!1,this.onStateChange&&this.onStateChange("disconnected",this.getConnectionSnapshot());return}const i=[];t&&this.registered&&i.push(this.simpleUser.unregister().catch(s=>{this.onError&&this.onError(s)})),this.connected&&i.push(this.simpleUser.disconnect().catch(s=>{this.onError&&this.onError(s)})),await Promise.all(i),this.connected=!1,this.registered=!1,this.active=!1,this._stopAllAudioMonitors(),this._reportAudioSilence(),this._stopRingback(),this._stopIncomingTone(),this._clearAutoplayUnlockTasks(),this.onStateChange&&this.onStateChange("disconnected",this.getConnectionSnapshot())}async reconnect(){await this.disconnect(),await this.ensureReady()}async call(e,t){if(!e)throw new Error("电话号码不能为空");this.lastFailureMeta=null,this.hangupInitiatedByLocal=!1,await this.ensureReady();const i=String(e).trim();let s;/^sips?:/i.test(i)?s=i:i.includes("@")?s=`sip:${i}`:s=`sip:${i}@${this.account.sip_domain}`,s=this._normalizeSipDestination(s);const r=this._createInviteRequestDelegate(t),n=this._mergeInviteOptions(t,r);try{await this._preflightAudioAccess("call"),await this.simpleUser.call(s,void 0,n),this._scheduleLocalMonitor(),this._stopIncomingTone()}catch(o){const d=await this._normalizeMediaAccessError(o,"call");throw this.onError&&this.onError(d),this.lastFailureMeta={source:"invite-exception",code:d?.code||null,message:d?.message||"呼叫请求失败",timestamp:new Date().toISOString()},this.onStateChange&&this.onStateChange("failed",{...this.lastFailureMeta}),d}}async _preflightAudioAccess(e="call"){let t=null;try{t=await di(e)}catch(i){const s=await Be(i,e);throw this.onError&&this.onError(s),this.lastFailureMeta={source:"media-preflight",code:s?.code||null,message:s?.message||"媒体设备检查失败",timestamp:new Date().toISOString()},this.onStateChange&&this.onStateChange("failed",{...this.lastFailureMeta}),s}finally{t?.getTracks?.().forEach(i=>{try{i.stop()}catch{}})}}_mergeInviteOptions(e,t){const i={...e||{}};return i.requestDelegate=t.requestDelegate,i}_handleInviteProgress(e){const t=e?.message;if(!t)return;const i=t.statusCode||0;if(i>=180&&i<200){const s=this._extractResponseMeta("progress",e);console.info("[CALL] 呼叫收到临时响应",s),this._isIncomingSession()||(this.onStateChange&&this.onStateChange(i===180?"ringing":"dialing",s),i===180&&this._playRingback())}}_handleInviteFailure(e,t){const i=this._extractResponseMeta(e,t);this.lastFailureMeta=i,console.warn("[CALL] 呼叫失败",i),this._stopRingback(),this._stopIncomingTone(),this._stopAllAudioMonitors(),this._reportAudioSilence(),this.onStateChange&&this.onStateChange("failed",{...i}),this.onError&&i.message&&this.onError(new Error(i.message))}_extractResponseMeta(e,t){const i=t?.message,s=i?.statusCode||null,r=(i?.reasonPhrase||"").trim(),n=this._getHeaderValue(i,"Warning"),o=this._getHeaderValue(i,"Reason"),d=this._getHeaderValue(i,"Retry-After"),p=typeof i?.body=="string"?i.body.trim():"",w=[s,r].filter(Boolean).join(" ").trim()||n||o||p||"呼叫未建立";return{source:"invite-response",stage:e,status:s,reason:r,warning:n,cause:o,retryAfter:d,body:p,message:w,timestamp:new Date().toISOString()}}_getHeaderValue(e,t){if(!e||typeof e.getHeader!="function")return"";try{const i=e.getHeader(t);return i?String(i).trim():""}catch{return""}}async _normalizeMediaAccessError(e,t="call"){return Be(e,t)}_createInviteRequestDelegate(e){const t=(s,...r)=>{if(typeof s=="function")try{s(...r)}catch(n){console.warn("SIP 邀请回调执行异常",n)}},i=e?.requestDelegate||{};return{requestDelegate:{onAccept:(...s)=>{this.lastFailureMeta=null,t(i.onAccept,...s)},onRedirect:(s,...r)=>{this._handleInviteFailure("redirect",s),t(i.onRedirect,s,...r)},onReject:(s,...r)=>{this._handleInviteFailure("reject",s),t(i.onReject,s,...r)},onProgress:(s,...r)=>{this._handleInviteProgress(s),t(i.onProgress,s,...r)},onTrying:(...s)=>{t(i.onTrying,...s)}}}}async answer(e){if(!this.simpleUser)throw new Error("呼叫客户端未初始化");try{await this._preflightAudioAccess("answer"),this._stopIncomingTone(),this._stopRingback(),await this.simpleUser.answer(e),this._scheduleLocalMonitor()}catch(t){const i=await this._normalizeMediaAccessError(t,"answer");throw this.onError&&this.onError(i),i}}async decline(){if(!this.simpleUser){this._stopAllAudioMonitors(),this._reportAudioSilence(),this._stopRingback(),this._stopIncomingTone();return}let e=null;try{await this.simpleUser.decline()}catch(t){e=t,this.onError&&this.onError(t)}finally{this._stopAllAudioMonitors(),this._reportAudioSilence(),this._stopRingback(),this._stopIncomingTone()}if(e)throw e}async hangup(){if(!this.simpleUser){this._stopAllAudioMonitors(),this._reportAudioSilence(),this._stopRingback(),this._stopIncomingTone();return}let e=null;try{this.hangupInitiatedByLocal=!0,await this.simpleUser.hangup()}catch(t){e=t,this.onError&&this.onError(t)}finally{this._stopAllAudioMonitors(),this._reportAudioSilence(),this._stopRingback(),this._stopIncomingTone()}if(e)throw e}mute(){if(this.simpleUser)try{this.simpleUser.mute()}catch(e){throw this.onError&&this.onError(e),e}}unmute(){if(this.simpleUser)try{this.simpleUser.unmute()}catch(e){throw this.onError&&this.onError(e),e}}async hold(){if(this.simpleUser)try{await this.simpleUser.hold()}catch(e){throw this.onError&&this.onError(e),e}}async unhold(){if(this.simpleUser)try{await this.simpleUser.unhold()}catch(e){throw this.onError&&this.onError(e),e}}async destroy(){this._rejectConnectionWaiters(Le("SIP 客户端已销毁,已取消连接等待。")),this._rejectRegistrationWaiters(Le("SIP 客户端已销毁,已取消注册等待。")),this.ensureReadyPromise=null;const e=this.simpleUser;if(e)try{this.registered&&await e.unregister(),this.connected&&await e.disconnect()}catch(t){this.onError&&this.onError(t)}this._stopAllAudioMonitors(),this._closeAudioContext(),this._stopRingback(),this._stopIncomingTone(),this._clearAutoplayUnlockTasks(),this.simpleUser=null,this.connected=!1,this.registered=!1,this.active=!1,this.account=null,this.audioTrackMeta={local:null,remote:null},this.peerConnectionDebug={attached:!1,events:[],iceCandidates:[],iceCandidateErrors:[]}}_scheduleLocalMonitor(e=0){if(!this.onAudioLevel)return;const t=this.simpleUser?.localMediaStream;if(t instanceof MediaStream&&t.getAudioTracks().length>0){this._startAudioLevelMonitor("local",t);return}e>10||setTimeout(()=>this._scheduleLocalMonitor(e+1),200*(e+1))}_scheduleRemoteMonitor(e=0){if(!this.onAudioLevel||!this.simpleUser)return;const t=this.simpleUser.remoteMediaStream||this._getRemoteAudioElement()?.srcObject;if(t instanceof MediaStream&&t.getAudioTracks&&t.getAudioTracks().length>0){this._startAudioLevelMonitor("remote",t);return}e>10||setTimeout(()=>this._scheduleRemoteMonitor(e+1),300*(e+1))}_startAudioLevelMonitor(e,t){if(!(!this.onAudioLevel&&!this.onAudioFrame)&&t instanceof MediaStream&&!(t.getAudioTracks&&t.getAudioTracks().length===0)&&this._ensureAudioContext()){try{if(t.getAudioTracks&&typeof t.getAudioTracks=="function"){const[i]=t.getAudioTracks();if(i){const s={track_id:i.id||null,label:i.label||null};try{s.settings=i.getSettings?i.getSettings():void 0}catch{s.settings=void 0}try{s.constraints=i.getConstraints?i.getConstraints():void 0}catch{s.constraints=void 0}this.audioTrackMeta=this.audioTrackMeta||{},this.audioTrackMeta[e]=s}}}catch{}this._stopAudioLevelMonitor(e);try{const i=this.audioContext.createMediaStreamSource(t),s=this.audioContext.createAnalyser();s.fftSize=2048;const r=new Uint8Array(s.fftSize);i.connect(s);const n=typeof this.onAudioFrame=="function",o=()=>{s.getByteTimeDomainData(r);let p=0;const g=n?new Float32Array(r.length):null;for(let _=0;_<r.length;_+=1){const E=(r[_]-128)/128;p+=E*E,g&&(g[_]=E)}const w=Math.sqrt(p/r.length),S=Math.min(1,w*2);if(this.onAudioLevel&&this.onAudioLevel(e,S),g&&this.audioContext&&this.onAudioFrame)try{this.onAudioFrame(e,{samples:g,sampleRate:this.audioContext.sampleRate||48e3,timestamp:Date.now()})}catch{}const I=this.audioMonitors[e];I&&(I.rafId=window.requestAnimationFrame(o))},d={source:i,analyser:s,dataArray:r,rafId:window.requestAnimationFrame(o)};this.audioMonitors[e]=d}catch(i){this.onError&&this.onError(i)}}}_stopAudioLevelMonitor(e){const t=this.audioMonitors[e];if(t){t.rafId&&window.cancelAnimationFrame(t.rafId);try{t.source&&t.source.disconnect()}catch{}delete this.audioMonitors[e]}}_stopAllAudioMonitors(){this._stopAudioLevelMonitor("local"),this._stopAudioLevelMonitor("remote"),!this.audioMonitors.local&&!this.audioMonitors.remote&&this._closeAudioContext()}_resolveToneUrl(e){return e?e.startsWith("http://")||e.startsWith("https://")||e.startsWith("/")?e:`/assets/${e}`:null}_normalizeAudioUrl(e){if(!e)return"";if(typeof window>"u")return e;try{const t=window.location?.origin||window.location?.href;return t?new URL(e,t).href:e}catch{return e}}_getAudioSourceMeta(e){return e?e._sipToneSrc?e._sipToneSrc:e.dataset&&e.dataset.sipToneSrc?e.dataset.sipToneSrc:this._normalizeAudioUrl(e.src||""):""}_markAudioSourceMeta(e,t){e&&(e.dataset&&(e.dataset.sipToneSrc=t),e._sipToneSrc=t)}_loadAudio(e,t){if(!e||typeof window>"u")return null;const i=this._normalizeAudioUrl(e);if(t){if(this._getAudioSourceMeta(t)===i)return t;try{t.pause(),t.currentTime=0}catch(r){console.warn("停止旧音频失败:",r)}}try{const s=new Audio(i);return s.loop=!0,s.preload="auto",s.volume=.6,s.playsInline=!0,s.autoplay=!0,this._markAudioSourceMeta(s,i),s}catch(s){return console.warn("创建音频失败:",s),null}}_shouldUnlock(e){if(!e)return!1;const t=e.name||e.code||"";return t==="NotAllowedError"||t==="SecurityError"}_attemptPlayAudio(e,{onSuccess:t,onError:i,unlockKey:s}){if(!e)return!1;this._ensureAudioContext();const r=()=>{typeof t=="function"&&t(),s&&this._removePendingPlaybackTask(s)},n=o=>{if(this._shouldUnlock(o)&&s){this._requestAutoplayUnlock(s,()=>{this._attemptPlayAudio(e,{onSuccess:t,onError:i,unlockKey:s})});return}typeof i=="function"&&i(o)};try{const o=e.play();o&&typeof o.then=="function"?o.then(()=>r()).catch(d=>n(d)):r()}catch(o){n(o)}return!0}_requestAutoplayUnlock(e,t){!e||typeof t!="function"||(this.pendingPlaybackTasks instanceof Map||(this.pendingPlaybackTasks=new Map),this.pendingPlaybackTasks.has(e)||this.pendingPlaybackTasks.set(e,t),!this.audioUnlockHandler&&typeof document<"u"&&(this.audioUnlockHandler=()=>{this._flushAutoplayTasks()},document.addEventListener("click",this.audioUnlockHandler,!0),document.addEventListener("touchstart",this.audioUnlockHandler,!0),document.addEventListener("keydown",this.audioUnlockHandler,!0)))}_flushAutoplayTasks(){if(!(this.pendingPlaybackTasks instanceof Map)||this.pendingPlaybackTasks.size===0){this._removeAudioUnlockHandler();return}const e=Array.from(this.pendingPlaybackTasks.entries());this.pendingPlaybackTasks.clear(),e.forEach(([t,i])=>{try{i()}catch(s){console.warn("触发音频播放失败:",s),this.pendingPlaybackTasks instanceof Map&&this.pendingPlaybackTasks.set(t,i)}}),(!(this.pendingPlaybackTasks instanceof Map)||this.pendingPlaybackTasks.size===0)&&this._removeAudioUnlockHandler()}_removeAudioUnlockHandler(){!this.audioUnlockHandler||typeof document>"u"||(document.removeEventListener("click",this.audioUnlockHandler,!0),document.removeEventListener("touchstart",this.audioUnlockHandler,!0),document.removeEventListener("keydown",this.audioUnlockHandler,!0),this.audioUnlockHandler=null)}_removePendingPlaybackTask(e){this.pendingPlaybackTasks instanceof Map&&(this.pendingPlaybackTasks.delete(e),this.pendingPlaybackTasks.size===0&&this._removeAudioUnlockHandler())}_clearAutoplayUnlockTasks(){this.pendingPlaybackTasks instanceof Map&&this.pendingPlaybackTasks.clear(),this._removeAudioUnlockHandler()}_playRingback(){this.ringbackPlaying||(this.ringbackAudio=this._loadAudio(this.ringbackToneUrl,this.ringbackAudio),this.ringbackAudio&&this._attemptPlayAudio(this.ringbackAudio,{unlockKey:"ringback",onSuccess:()=>{this.ringbackPlaying=!0},onError:e=>{this.ringbackPlaying=!1,console.warn("播放回铃音异常:",e)}}))}_stopRingback(){if(this.ringbackAudio){try{this.ringbackAudio.pause(),this.ringbackAudio.currentTime=0}catch(e){console.warn("停止回铃音异常:",e)}this.ringbackPlaying=!1,this._removePendingPlaybackTask("ringback")}}_playIncomingTone(){this.incomingPlaying||(this.incomingAudio=this._loadAudio(this.incomingToneUrl,this.incomingAudio),this.incomingAudio&&this._attemptPlayAudio(this.incomingAudio,{unlockKey:"incoming",onSuccess:()=>{this.incomingPlaying=!0},onError:e=>{this.incomingPlaying=!1,console.warn("播放振铃音异常:",e)}}))}_stopIncomingTone(){if(this.incomingAudio){try{this.incomingAudio.pause(),this.incomingAudio.currentTime=0}catch(e){console.warn("停止振铃音异常:",e)}this.incomingPlaying=!1,this._removePendingPlaybackTask("incoming")}}updateToneOptions({ringbackTone:e,incomingTone:t}){if(e){const i=this._resolveToneUrl(e);i&&i!==this.ringbackToneUrl&&(this._stopRingback(),this.ringbackToneUrl=i,this.ringbackAudio=null)}if(t){const i=this._resolveToneUrl(t);i&&i!==this.incomingToneUrl&&(this._stopIncomingTone(),this.incomingToneUrl=i,this.incomingAudio=null)}}silenceIncomingTone(){this._stopIncomingTone()}_reportAudioSilence(){this.onAudioLevel&&(this.onAudioLevel("local",0),this.onAudioLevel("remote",0))}_ensureAudioContext(){if(typeof window>"u"||!window.AudioContext&&!window.webkitAudioContext)return!1;if(!this.audioContext){const e=window.AudioContext||window.webkitAudioContext;this.audioContext=new e}return this.audioContext.state==="suspended"&&this.audioContext.resume().catch(()=>{}),!0}_closeAudioContext(){if(this.audioContext)try{this.audioContext.close()}catch{}this.audioContext=null}}const hi=/^[a-z][a-z0-9+.-]*:\/\//i;function ui(a,e=""){const t=String(a||e||"").trim();if(!t)throw new Error("缺少统一服务基址");return t}function dt(a,e="https://"){return hi.test(a)?a:`${e}${a.replace(/^\/+/,"")}`}function lt(a="/"){return a.endsWith("/")?a:`${a}/`}function ht(a="/",e=""){const t=lt(a||"/"),i=String(e||"").replace(/^\/+/,"");return i?`${t}${i}`:t}function $e(a,e=""){const t=dt(ui(a,e)),i=new URL(t);return i.search="",i.hash="",i.pathname=lt(i.pathname||"/"),i}function gi(a,e=""){return $e(a,e).pathname!=="/"}function J(a,e,t=""){const i=$e(a,t);return i.pathname=ht(i.pathname,e),i.href}function je(a,e="ws",t=""){const i=$e(a,t);return i.protocol==="https:"?i.protocol="wss:":i.protocol==="http:"?i.protocol="ws:":i.protocol!=="ws:"&&i.protocol!=="wss:"&&(i.protocol="wss:"),i.pathname=ht(i.pathname,e),i.href}function ut(a,e=""){if(!String(a||"").trim())return String(e||"").replace(/^\/+/,"");const t=new URL(dt(String(a).trim())),i=String(t.pathname||"/").replace(/^\/+/,""),s=String(t.search||"");return`${i}${s}`||String(e||"").replace(/^\/+/,"")}function gt({apiBaseUrl:a,transportBaseUrl:e="",transportUrl:t="",fallbackPath:i="ws"}){const s=String(e||"").trim();return s?je(s,ut(t,i)):gi(a)?je(a,ut(t,i)):String(t||"").trim()?String(t).trim():je(a,i)}const fi=`
|
|
65
|
+
`,2);if(n.length!==2){i.reject();return}let o;const d=/^(Signal\s*?=\s*?)([0-9A-D#*]{1})(\s)?.*/;if(n[0]!==void 0&&d.test(n[0])&&(o=n[0].replace(d,"$2")),!o){i.reject();return}let p;const g=/^(Duration\s?=\s?)([0-9]{1,4})(\s)?.*/;if(n[1]!==void 0&&g.test(n[1])&&(p=parseInt(n[1].replace(g,"$2"),10)),!p){i.reject();return}i.accept().then(()=>{if(this.delegate&&this.delegate.onCallDTMFReceived){if(!o||!p)throw new Error("Tone or duration undefined.");this.delegate.onCallDTMFReceived(e,o,p)}}).catch(w=>{this.logger.error(w.message)})},e.delegate.onRefer=i=>{i.accept().then(()=>this.sendInvite(i.makeInviter(t),t)).catch(s=>{this.logger.error(s.message)})}}optionsPingRun(e,t,i){if(this.options.optionsPingInterval<1)throw new Error("Invalid options ping interval.");this.optionsPingRunning||(this.optionsPingRunning=!0,this.optionsPingTimeout=setTimeout(()=>{this.optionsPingTimeout=void 0;const s=()=>{this.optionsPingFailure=!1,this.optionsPingRunning&&(this.optionsPingRunning=!1,this.optionsPingRun(e,t,i))},r=()=>{this.logger.error("OPTIONS ping failed"),this.optionsPingFailure=!0,this.optionsPingRunning=!1,this.userAgent.transport.disconnect().catch(d=>this.logger.error(d))},n=this.userAgent.userAgentCore,o=n.makeOutgoingRequestMessage("OPTIONS",e,t,i,{});this.optionsPingRequest=n.request(o,{onAccept:()=>{this.optionsPingRequest=void 0,s()},onReject:d=>{this.optionsPingRequest=void 0,d.message.statusCode===408||d.message.statusCode===503?r():s()}})},this.options.optionsPingInterval*1e3))}optionsPingStart(){this.logger.log("OPTIONS pings started");let e,t,i;if(this.options.optionsPingRequestURI){if(e=G.makeURI(this.options.optionsPingRequestURI),!e)throw new Error("Failed to create Request URI.");t=this.userAgent.contact.uri.clone(),i=this.userAgent.contact.uri.clone()}else if(this.options.aor){const s=G.makeURI(this.options.aor);if(!s)throw new Error("Failed to create URI.");e=s.clone(),e.user=void 0,t=s.clone(),i=s.clone()}else{this.logger.error("You have enabled sending OPTIONS pings and as such you must provide either a) an AOR to register, or b) an RURI to use for the target of the OPTIONS ping requests. ");return}this.optionsPingRun(e,t,i)}optionsPingStop(){this.logger.log("OPTIONS pings stopped"),this.optionsPingRunning=!1,this.optionsPingFailure=!1,this.optionsPingRequest&&(this.optionsPingRequest.dispose(),this.optionsPingRequest=void 0),this.optionsPingTimeout&&(clearTimeout(this.optionsPingTimeout),this.optionsPingTimeout=void 0)}async sendInvite(e,t,i){return this.initSession(e,t),e.invite(i).then(()=>{this.logger.log(`[${e.id}] Sent INVITE`)})}sessionAdd(e){const t=this.options.managedSessionFactory(this,e);this.managedSessions.push(t)}sessionExists(e){return this.sessionManaged(e)!==void 0}sessionManaged(e){return this.managedSessions.find(t=>t.session.id===e.id)}sessionRemove(e){this.managedSessions=this.managedSessions.filter(t=>t.session.id!==e.id)}async setHold(e,t){if(!this.sessionExists(e))return Promise.reject(new Error("Session does not exist."));if(this.isHeld(e)===t)return Promise.resolve();if(!(e.sessionDescriptionHandler instanceof O))throw new Error("Session's session description handler not instance of SessionDescriptionHandler.");const s={requestDelegate:{onAccept:()=>{const o=this.sessionManaged(e);o!==void 0&&(o.held=t,this.enableReceiverTracks(e,!o.held),this.enableSenderTracks(e,!o.held&&!o.muted),this.delegate&&this.delegate.onCallHold&&this.delegate.onCallHold(e,o.held))},onReject:()=>{this.logger.warn(`[${e.id}] Re-invite request was rejected`);const o=this.sessionManaged(e);o!==void 0&&(o.held=!t,this.enableReceiverTracks(e,!o.held),this.enableSenderTracks(e,!o.held&&!o.muted),this.delegate&&this.delegate.onCallHold&&this.delegate.onCallHold(e,o.held))}}},r=e.sessionDescriptionHandlerOptionsReInvite;r.hold=t,e.sessionDescriptionHandlerOptionsReInvite=r;const n=this.sessionManaged(e);if(!n)throw new Error("Managed session is undefiend.");return n.held=t,e.invite(s).then(()=>{const o=this.sessionManaged(e);o!==void 0&&(this.enableReceiverTracks(e,!o.held),this.enableSenderTracks(e,!o.held&&!o.muted))}).catch(o=>{throw n.held=!t,o instanceof pe&&this.logger.error(`[${e.id}] A hold request is already in progress.`),o})}setMute(e,t){if(!this.sessionExists(e)){this.logger.warn(`[${e.id}] A session is required to enabled/disable media tracks`);return}if(e.state!==m.Established){this.logger.warn(`[${e.id}] An established session is required to enable/disable media tracks`);return}const i=this.sessionManaged(e);i!==void 0&&(i.muted=t,this.enableSenderTracks(e,!i.held&&!i.muted))}setupLocalMedia(e){const t=this.sessionManaged(e);if(!t)throw new Error("Managed session does not exist.");const i=typeof this.options.media.local=="function"?this.options.media.local(e):this.options.media.local;t.mediaLocal=i;const s=i?.video;if(s){const r=this.getLocalMediaStream(e);if(!r)throw new Error("Local media stream undefiend.");s.srcObject=r,s.volume=0,s.play().catch(n=>{this.logger.error(`[${e.id}] Failed to play local media`),this.logger.error(n.message)})}}setupRemoteMedia(e){const t=this.sessionManaged(e);if(!t)throw new Error("Managed session does not exist.");const i=typeof this.options.media.remote=="function"?this.options.media.remote(e):this.options.media.remote;t.mediaRemote=i;const s=i?.video||i?.audio;if(s){const r=this.getRemoteMediaStream(e);if(!r)throw new Error("Remote media stream undefiend.");s.autoplay=!0,s.srcObject=r,s.play().catch(n=>{this.logger.error(`[${e.id}] Failed to play remote media`),this.logger.error(n.message)}),r.onaddtrack=()=>{this.logger.log("Remote media onaddtrack"),s.load(),s.play().catch(n=>{this.logger.error(`[${e.id}] Failed to play remote media`),this.logger.error(n.message)})}}}async terminate(e){switch(this.logger.log(`[${e.id}] Terminating...`),e.state){case m.Initial:if(e instanceof Re)return e.cancel().then(()=>{this.logger.log(`[${e.id}] Inviter never sent INVITE (canceled)`)});if(e instanceof de)return e.reject().then(()=>{this.logger.log(`[${e.id}] Invitation rejected (sent 480)`)});throw new Error("Unknown session type.");case m.Establishing:if(e instanceof Re)return e.cancel().then(()=>{this.logger.log(`[${e.id}] Inviter canceled (sent CANCEL)`)});if(e instanceof de)return e.reject().then(()=>{this.logger.log(`[${e.id}] Invitation rejected (sent 480)`)});throw new Error("Unknown session type.");case m.Established:return e.bye().then(()=>{this.logger.log(`[${e.id}] Session ended (sent BYE)`)});case m.Terminating:break;case m.Terminated:break;default:throw new Error("Unknown state")}return this.logger.log(`[${e.id}] Terminating in state ${e.state}, no action taken`),Promise.resolve()}}class ai{constructor(e,t={}){this.session=void 0,this.delegate=t.delegate,this.options=Object.assign({},t);const i={aor:this.options.aor,delegate:{onCallAnswered:()=>{var s,r;return(r=(s=this.delegate)===null||s===void 0?void 0:s.onCallAnswered)===null||r===void 0?void 0:r.call(s)},onCallCreated:s=>{var r,n;this.session=s,(n=(r=this.delegate)===null||r===void 0?void 0:r.onCallCreated)===null||n===void 0||n.call(r)},onCallReceived:()=>{var s,r;return(r=(s=this.delegate)===null||s===void 0?void 0:s.onCallReceived)===null||r===void 0?void 0:r.call(s)},onCallHangup:()=>{var s,r;this.session=void 0,!((s=this.delegate)===null||s===void 0)&&s.onCallHangup&&((r=this.delegate)===null||r===void 0||r.onCallHangup())},onCallHold:(s,r)=>{var n,o;return(o=(n=this.delegate)===null||n===void 0?void 0:n.onCallHold)===null||o===void 0?void 0:o.call(n,r)},onCallDTMFReceived:(s,r,n)=>{var o,d;return(d=(o=this.delegate)===null||o===void 0?void 0:o.onCallDTMFReceived)===null||d===void 0?void 0:d.call(o,r,n)},onMessageReceived:s=>{var r,n;return(n=(r=this.delegate)===null||r===void 0?void 0:r.onMessageReceived)===null||n===void 0?void 0:n.call(r,s.request.body)},onRegistered:()=>{var s,r;return(r=(s=this.delegate)===null||s===void 0?void 0:s.onRegistered)===null||r===void 0?void 0:r.call(s)},onUnregistered:()=>{var s,r;return(r=(s=this.delegate)===null||s===void 0?void 0:s.onUnregistered)===null||r===void 0?void 0:r.call(s)},onServerConnect:()=>{var s,r;return(r=(s=this.delegate)===null||s===void 0?void 0:s.onServerConnect)===null||r===void 0?void 0:r.call(s)},onServerDisconnect:()=>{var s,r;return(r=(s=this.delegate)===null||s===void 0?void 0:s.onServerDisconnect)===null||r===void 0?void 0:r.call(s)}},maxSimultaneousSessions:1,media:this.options.media,reconnectionAttempts:this.options.reconnectionAttempts,reconnectionDelay:this.options.reconnectionDelay,registererOptions:this.options.registererOptions,sendDTMFUsingSessionDescriptionHandler:this.options.sendDTMFUsingSessionDescriptionHandler,userAgentOptions:this.options.userAgentOptions};this.sessionManager=new Ne(e,i),this.logger=this.sessionManager.userAgent.getLogger("sip.SimpleUser")}get id(){return this.options.userAgentOptions&&this.options.userAgentOptions.displayName||"Anonymous"}get localMediaStream(){return this.session&&this.sessionManager.getLocalMediaStream(this.session)}get remoteMediaStream(){return this.session&&this.sessionManager.getRemoteMediaStream(this.session)}get localAudioTrack(){return this.session&&this.sessionManager.getLocalAudioTrack(this.session)}get localVideoTrack(){return this.session&&this.sessionManager.getLocalVideoTrack(this.session)}get remoteAudioTrack(){return this.session&&this.sessionManager.getRemoteAudioTrack(this.session)}get remoteVideoTrack(){return this.session&&this.sessionManager.getRemoteVideoTrack(this.session)}connect(){return this.logger.log(`[${this.id}] Connecting UserAgent...`),this.sessionManager.connect()}disconnect(){return this.logger.log(`[${this.id}] Disconnecting UserAgent...`),this.sessionManager.disconnect()}isConnected(){return this.sessionManager.isConnected()}register(e){return this.logger.log(`[${this.id}] Registering UserAgent...`),this.sessionManager.register(e)}unregister(e){return this.logger.log(`[${this.id}] Unregistering UserAgent...`),this.sessionManager.unregister(e)}call(e,t,i){return this.logger.log(`[${this.id}] Beginning Session...`),this.session?Promise.reject(new Error("Session already exists.")):this.sessionManager.call(e,t,i).then(()=>{})}hangup(){return this.logger.log(`[${this.id}] Hangup...`),this.session?this.sessionManager.hangup(this.session).then(()=>{this.session=void 0}):Promise.reject(new Error("Session does not exist."))}answer(e){return this.logger.log(`[${this.id}] Accepting Invitation...`),this.session?this.sessionManager.answer(this.session,e):Promise.reject(new Error("Session does not exist."))}decline(){return this.logger.log(`[${this.id}] rejecting Invitation...`),this.session?this.sessionManager.decline(this.session):Promise.reject(new Error("Session does not exist."))}hold(){return this.logger.log(`[${this.id}] holding session...`),this.session?this.sessionManager.hold(this.session):Promise.reject(new Error("Session does not exist."))}unhold(){return this.logger.log(`[${this.id}] unholding session...`),this.session?this.sessionManager.unhold(this.session):Promise.reject(new Error("Session does not exist."))}isHeld(){return this.session?this.sessionManager.isHeld(this.session):!1}mute(){return this.logger.log(`[${this.id}] disabling media tracks...`),this.session&&this.sessionManager.mute(this.session)}unmute(){return this.logger.log(`[${this.id}] enabling media tracks...`),this.session&&this.sessionManager.unmute(this.session)}isMuted(){return this.session?this.sessionManager.isMuted(this.session):!1}sendDTMF(e){return this.logger.log(`[${this.id}] sending DTMF...`),this.session?this.sessionManager.sendDTMF(this.session,e):Promise.reject(new Error("Session does not exist."))}message(e,t){return this.logger.log(`[${this.id}] sending message...`),this.sessionManager.message(e,t)}}const oi=15e3,ci=15e3,Ue=(a,e,t)=>{const i=String(a?.name||a?.code||"").trim(),s=new Error(e);return s.name=i||"MediaAccessError",s.code=t,s.cause=a,s},Le=a=>{const e=new Error(a);return e.name="SipClientLifecycleCancelledError",e.code="sip_client_replaced",e},Be=async(a,e="call")=>{const t=String(a?.name||a?.code||"").trim(),i=String(a?.message||"").trim(),s=`${t} ${i}`.toLowerCase();let r="";try{if(typeof navigator<"u"&&navigator.permissions?.query){const n=await navigator.permissions.query({name:"microphone"});r=String(n?.state||"").trim().toLowerCase()}}catch{r=""}return r==="denied"||t==="NotAllowedError"||t==="SecurityError"||s.includes("notallowederror")||s.includes("permission denied")||s.includes("permission dismissed")||s.includes("microphone permission")||s.includes("getusermedia() no permission")?Ue(a,`浏览器未授予麦克风权限,无法${e==="answer"?"接听来电":"发起通话"}。请点击地址栏的麦克风权限并选择“允许”,然后刷新页面重试。`,"media_permission_denied"):t==="NotFoundError"||t==="DevicesNotFoundError"||s.includes("notfounderror")||s.includes("requested device not found")||s.includes("no audio input device")?Ue(a,"未检测到可用的麦克风设备,无法建立语音通话。请连接耳麦或启用系统麦克风后重试。","media_device_not_found"):t==="NotReadableError"||t==="TrackStartError"||s.includes("notreadableerror")||s.includes("could not start audio source")?Ue(a,"麦克风当前被系统或其他应用占用,无法建立语音通话。请关闭占用麦克风的程序后重试。","media_device_unavailable"):a},di=async(a="call")=>{if(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia)return null;let e=null;try{return e=await navigator.mediaDevices.getUserMedia({audio:!0,video:!1}),e}catch(t){throw await Be(t,a)}};class li{constructor(e,t={}){this.account={...e},this.remoteAudioElementId=t.remoteAudioElementId||"sipRemoteAudio",this.remoteAudioElement=t.remoteAudioElement||null,this.onStateChange=t.onStateChange,this.onError=t.onError,this.onAudioLevel=t.onAudioLevel,this.onAudioFrame=t.onAudioFrame,this.simpleUser=null,this.connected=!1,this.registered=!1,this.active=!1,this.lastFailureMeta=null,this.audioContext=null,this.audioMonitors={local:null,remote:null},this.audioTrackMeta={local:null,remote:null},this.ringbackAudio=null,this.ringbackPlaying=!1,this.incomingAudio=null,this.incomingPlaying=!1,this.hangupInitiatedByLocal=!1,this.ringbackToneUrl=this._resolveToneUrl(t.ringbackTone||"ringback1.wav"),this.incomingToneUrl=this._resolveToneUrl(t.incomingTone||"ring1.wav"),this.pendingPlaybackTasks=new Map,this.audioUnlockHandler=null,this.lastSessionMeta=null,this.ensureReadyPromise=null,this.connectionWaiters=new Set,this.registrationWaiters=new Set,this.connectionConfirmTimeoutMs=Number.isFinite(Number(t.connectionConfirmTimeoutMs))?Number(t.connectionConfirmTimeoutMs):oi,this.registrationConfirmTimeoutMs=Number.isFinite(Number(t.registrationConfirmTimeoutMs))?Number(t.registrationConfirmTimeoutMs):ci,this.peerConnectionDebug={attached:!1,events:[],iceCandidates:[],iceCandidateErrors:[]}}getAudioTrackMeta(e){return e&&this.audioTrackMeta?.[e]?{...this.audioTrackMeta[e]}:null}matchesAccount(e){return this.account?this.account.sip_username===e.sip_username&&this.account.sip_domain===e.sip_domain&&(this.account.port||"443")===(e.port||"443")&&(this.account.webrtc_url||"")===(e.webrtc_url||"")&&JSON.stringify(this.account.ice_servers||[])===JSON.stringify(e.ice_servers||[]):!1}_getRemoteAudioElement(){if(this.remoteAudioElement instanceof HTMLAudioElement)return this.remoteAudioElement.autoplay=!0,this.remoteAudioElement.playsInline=!0,this.remoteAudioElement.muted=!1,this.remoteAudioElement.volume=1,this.remoteAudioElement;const e=document.getElementById(this.remoteAudioElementId);if(!(e instanceof HTMLAudioElement))throw new Error("找不到用于播放远端音频的 audio 元素");return e.autoplay=!0,e.playsInline=!0,e.muted=!1,e.volume=1,e}_getPeerConnection(){const t=this.simpleUser?.session?.sessionDescriptionHandler;return t?.peerConnection||t?._peerConnection||null}_summarizeSessionDescription(e){if(!e?.sdp)return null;const t=String(e.sdp).split(/\r?\n/).map(s=>s.trim()).filter(Boolean),i=["o=","c=","m=","a=ice-","a=candidate","a=setup:","a=fingerprint:","a=rtcp-mux","a=mid:"];return{type:e.type||"",lineCount:t.length,importantLines:t.filter(s=>i.some(r=>s.startsWith(r))),candidateLines:t.filter(s=>s.startsWith("a=candidate")),iceLines:t.filter(s=>s.startsWith("a=ice-"))}}async getMediaDiagnostics(){const e=this._getPeerConnection();this._ensurePeerConnectionDebug(e);const t=document.getElementById(this.remoteAudioElementId),i={hasPeerConnection:!!e,signalingState:e?.signalingState||"",connectionState:e?.connectionState||"",iceConnectionState:e?.iceConnectionState||"",iceGatheringState:e?.iceGatheringState||"",peerConnectionEvents:this.peerConnectionDebug.events.slice(-20),iceCandidateErrors:this.peerConnectionDebug.iceCandidateErrors.slice(-20),localDescription:this._summarizeSessionDescription(e?.localDescription),remoteDescription:this._summarizeSessionDescription(e?.remoteDescription),remoteAudio:t instanceof HTMLAudioElement?{paused:t.paused,muted:t.muted,volume:t.volume,readyState:t.readyState,currentTime:t.currentTime,srcObjectTracks:t.srcObject?.getTracks?.().map(n=>({kind:n.kind,enabled:n.enabled,muted:n.muted,readyState:n.readyState,id:n.id}))||[]}:null};if(!e||typeof e.getStats!="function")return i;const s=await e.getStats(),r={inboundAudio:null,outboundAudio:null,selectedCandidatePair:null,localCandidate:null,remoteCandidate:null,localCandidates:[],remoteCandidates:[],candidatePairs:[]};for(const n of s.values()){if(n.type==="inbound-rtp"&&n.kind==="audio"&&!n.isRemote&&(r.inboundAudio={packetsReceived:n.packetsReceived||0,bytesReceived:n.bytesReceived||0,packetsLost:n.packetsLost||0,jitter:n.jitter||0,codecId:n.codecId||"",transportId:n.transportId||""}),n.type==="outbound-rtp"&&n.kind==="audio"&&!n.isRemote&&(r.outboundAudio={packetsSent:n.packetsSent||0,bytesSent:n.bytesSent||0,retransmittedPacketsSent:n.retransmittedPacketsSent||0,codecId:n.codecId||"",transportId:n.transportId||""}),n.type==="transport"&&n.selectedCandidatePairId&&!r.selectedCandidatePair){const o=s.get(n.selectedCandidatePairId);o&&(r.selectedCandidatePair={state:o.state||"",localCandidateId:o.localCandidateId||"",remoteCandidateId:o.remoteCandidateId||"",bytesSent:o.bytesSent||0,bytesReceived:o.bytesReceived||0,currentRoundTripTime:o.currentRoundTripTime||0},r.localCandidate=s.get(o.localCandidateId)||null,r.remoteCandidate=s.get(o.remoteCandidateId)||null)}n.type==="local-candidate"&&r.localCandidates.push({id:n.id||"",candidateType:n.candidateType||"",protocol:n.protocol||"",address:n.address||n.ip||"",port:n.port||0,relayProtocol:n.relayProtocol||"",url:n.url||"",networkType:n.networkType||""}),n.type==="remote-candidate"&&r.remoteCandidates.push({id:n.id||"",candidateType:n.candidateType||"",protocol:n.protocol||"",address:n.address||n.ip||"",port:n.port||0,relayProtocol:n.relayProtocol||"",url:n.url||""}),n.type==="candidate-pair"&&r.candidatePairs.push({id:n.id||"",state:n.state||"",nominated:!!n.nominated,writable:!!n.writable,readable:!!n.readable,requestsSent:n.requestsSent||0,requestsReceived:n.requestsReceived||0,responsesSent:n.responsesSent||0,responsesReceived:n.responsesReceived||0,bytesSent:n.bytesSent||0,bytesReceived:n.bytesReceived||0,currentRoundTripTime:n.currentRoundTripTime||0,localCandidateId:n.localCandidateId||"",remoteCandidateId:n.remoteCandidateId||""})}return{...i,...r}}_ensurePeerConnectionDebug(e){if(!e||this.peerConnectionDebug.attached)return;this.peerConnectionDebug.attached=!0;const t=(i,s={})=>{this.peerConnectionDebug.events.push({type:i,timestamp:new Date().toISOString(),...s}),this.peerConnectionDebug.events.length>100&&(this.peerConnectionDebug.events=this.peerConnectionDebug.events.slice(-100))};e.addEventListener("connectionstatechange",()=>{t("connectionstatechange",{connectionState:e.connectionState||""})}),e.addEventListener("iceconnectionstatechange",()=>{t("iceconnectionstatechange",{iceConnectionState:e.iceConnectionState||""})}),e.addEventListener("icegatheringstatechange",()=>{t("icegatheringstatechange",{iceGatheringState:e.iceGatheringState||""})}),e.addEventListener("signalingstatechange",()=>{t("signalingstatechange",{signalingState:e.signalingState||""})}),e.addEventListener("icecandidate",i=>{const s=i?.candidate;if(!s){t("icecandidate-complete");return}const r={candidate:s.candidate||"",address:s.address||"",port:s.port||0,protocol:s.protocol||"",type:s.type||"",foundation:s.foundation||"",relatedAddress:s.relatedAddress||"",relatedPort:s.relatedPort||0,sdpMid:s.sdpMid||"",sdpMLineIndex:s.sdpMLineIndex??null};this.peerConnectionDebug.iceCandidates.push(r),this.peerConnectionDebug.iceCandidates.length>100&&(this.peerConnectionDebug.iceCandidates=this.peerConnectionDebug.iceCandidates.slice(-100)),t("icecandidate",r)}),e.addEventListener("icecandidateerror",i=>{const s={address:i?.address||"",port:i?.port||0,url:i?.url||"",errorCode:i?.errorCode||0,errorText:i?.errorText||""};this.peerConnectionDebug.iceCandidateErrors.push({timestamp:new Date().toISOString(),...s}),this.peerConnectionDebug.iceCandidateErrors.length>100&&(this.peerConnectionDebug.iceCandidateErrors=this.peerConnectionDebug.iceCandidateErrors.slice(-100)),t("icecandidateerror",s)}),e.addEventListener("track",i=>{t("track",{streams:Array.isArray(i?.streams)?i.streams.map(s=>({id:s.id,audioTracks:s.getAudioTracks().map(r=>({id:r.id,enabled:r.enabled,muted:r.muted,readyState:r.readyState}))})):[]})})}_createSimpleUser(){const{sip_username:e,sip_password:t,sip_domain:i,port:s,webrtc_url:r,ice_servers:n}=this.account,o=s||"443",d=r||(/^wss?:\/\//.test(i)?i:`wss://${i}:${o}`),g={aor:`sip:${e}@${i}`,media:{constraints:{audio:{echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0},video:!1},remote:{audio:this._getRemoteAudioElement()}},userAgentOptions:{authorizationUsername:e,authorizationPassword:t,sessionDescriptionHandlerFactoryOptions:{peerConnectionConfiguration:n?.length?{iceServers:n,iceTransportPolicy:"all"}:void 0}},registererOptions:{expires:1800}},w=new ai(d,g);return w.delegate={onServerConnect:()=>{this.connected=!0,this._resolveConnectionWaiters(),this.onStateChange&&this.onStateChange("connected",this.getConnectionSnapshot())},onServerDisconnect:S=>{this.connected=!1,this.registered=!1,this.active=!1,this._rejectConnectionWaiters(S||new Error("呼叫服务器已断开")),this._rejectRegistrationWaiters(S||new Error("呼叫服务器已断开")),this.onError&&this.onError(S||new Error("呼叫服务器已断开")),this.onStateChange&&this.onStateChange("disconnected",this.getConnectionSnapshot()),this._stopRingback(),this._stopIncomingTone()},onRegistered:()=>{this.registered=!0,this._resolveRegistrationWaiters(),this.onStateChange&&this.onStateChange("registered",this.getConnectionSnapshot())},onUnregistered:()=>{this.registered=!1,this._rejectRegistrationWaiters(new Error("SIP 注册已被取消或失效。")),this.onStateChange&&this.onStateChange("unregistered",this.getConnectionSnapshot())},onCallCreated:()=>{const S=this._isIncomingSession();this.lastSessionMeta=this._buildIncomingCallMeta(),!S&&this.onStateChange&&(this.onStateChange("dialing"),this._scheduleLocalMonitor())},onCallReceived:()=>{this.lastSessionMeta=this._buildIncomingCallMeta(),this._playIncomingTone(),this.onStateChange&&this.onStateChange("incoming",this._buildIncomingCallMeta())},onCallAnswered:()=>{this.active=!0,this.lastSessionMeta=this._buildIncomingCallMeta(),this.onStateChange&&this.onStateChange("answered",this._buildIncomingCallMeta()),this._stopRingback(),this._stopIncomingTone(),this._scheduleRemoteMonitor()},onCallHangup:()=>{const S=this.active;if(this.active=!1,this.onStateChange){const I=this.lastFailureMeta?{...this.lastFailureMeta}:{};I.hangupSource=this.hangupInitiatedByLocal?"local":S?"remote":"unknown";const _=this.lastSessionMeta||this._buildIncomingCallMeta();_&&Object.keys(_).length>0&&(I.callMeta=_),this.onStateChange("terminated",I)}this.hangupInitiatedByLocal=!1,this._stopAllAudioMonitors(),this._reportAudioSilence(),this._stopRingback(),this._stopIncomingTone(),this.lastFailureMeta=null,this.lastSessionMeta=null}},w}_encodeSipUserPart(e){if(!e)return"";let t=e;try{t=decodeURIComponent(e)}catch{t=e}return encodeURIComponent(t).replace(/%2B/g,"+")}_normalizeSipDestination(e){const t=(e||"").trim();if(!t)throw new Error("呼叫目标地址不能为空");const i=t.match(/^(sips?:)/i),s=i?i[1].toLowerCase():"sip:",r=i?t.slice(i[1].length):t,n=r.indexOf("@");if(n===-1)return`${s}${this._encodeSipUserPart(r)}`;const o=r.slice(0,n),d=r.slice(n+1),p=this._encodeSipUserPart(o);return`${s}${p}@${d}`}_isIncomingSession(){if(!this.simpleUser||!this.simpleUser.session)return!1;if(typeof de<"u"&&this.simpleUser.session instanceof de)return!0;const e=this.simpleUser.session;return!!(e&&e.incomingInviteRequest)}_buildIncomingCallMeta(){const e=this.simpleUser?.session;if(!e||!e.remoteIdentity)return{};const t=e.remoteIdentity,i=t?.uri;return{displayName:t?.displayName||t?.friendlyName||"",uri:typeof t?.toString=="function"?t.toString():i?.toString?.()||"",user:i?.user||"",host:i?.host||"",direction:this._isIncomingSession()?"inbound":"outbound"}}_assertSimpleUserIsCurrent(e,t="当前操作"){if(!(e&&this.simpleUser===e&&this.account))throw Le(`SIP 客户端已重置,已取消${t}。`)}_resolveRegistrationWaiters(){if(!this.registrationWaiters.size)return;const e=Array.from(this.registrationWaiters);this.registrationWaiters.clear(),e.forEach(t=>{try{t.resolve()}catch{}})}_rejectRegistrationWaiters(e){if(!this.registrationWaiters.size)return;const t=Array.from(this.registrationWaiters);this.registrationWaiters.clear();const i=e instanceof Error?e:new Error("SIP 注册未完成即已中断。");t.forEach(s=>{try{s.reject(i)}catch{}})}_resolveConnectionWaiters(){if(!this.connectionWaiters.size)return;const e=Array.from(this.connectionWaiters);this.connectionWaiters.clear(),e.forEach(t=>{try{t.resolve()}catch{}})}_rejectConnectionWaiters(e){if(!this.connectionWaiters.size)return;const t=Array.from(this.connectionWaiters);this.connectionWaiters.clear();const i=e instanceof Error?e:new Error("SIP 连接未完成即已中断。");t.forEach(s=>{try{s.reject(i)}catch{}})}_waitForConnectionConfirmation(e=this.connectionConfirmTimeoutMs){return this.connected?Promise.resolve():new Promise((t,i)=>{const s={resolve:()=>{clearTimeout(r),t()},reject:n=>{clearTimeout(r),i(n)}},r=setTimeout(()=>{this.connectionWaiters.delete(s);const n=this.account?.internal_number||this.account?.account_key||this.account?.sip_username||"当前账号";i(new Error(`${n} 浏览器连接超时,未收到 SIP 服务器连接确认。`))},e);this.connectionWaiters.add(s)})}_waitForRegistrationConfirmation(e=this.registrationConfirmTimeoutMs){return this.registered?Promise.resolve():new Promise((t,i)=>{const s={resolve:()=>{clearTimeout(r),t()},reject:n=>{clearTimeout(r),i(n)}},r=setTimeout(()=>{this.registrationWaiters.delete(s);const n=this.account?.internal_number||this.account?.account_key||this.account?.sip_username||"当前账号";i(new Error(`${n} 浏览器注册超时,信令已连接但未收到注册确认。`))},e);this.registrationWaiters.add(s)})}_captureRegistrationDiagnostics(){return{snapshot:this.getConnectionSnapshot(),shouldBeRegistered:!!this.simpleUser?.sessionManager?.shouldBeRegistered,hasRegisterer:!!this.simpleUser?.sessionManager?.registerer}}_shouldRecoverFromConnectedWithoutRegistration(e){return!!(e?.snapshot?.connected&&!e?.snapshot?.registered&&!e?.shouldBeRegistered&&!e?.hasRegisterer)}async _ensureRegistration(e){try{this._assertSimpleUserIsCurrent(e,"注册流程"),await e.register(),this._assertSimpleUserIsCurrent(e,"注册流程"),this.registered||(await this._waitForRegistrationConfirmation(),this._assertSimpleUserIsCurrent(e,"注册确认"))}catch(t){throw this.onError&&this.onError(t),t}}async ensureReady(){if(this.ensureReadyPromise)return this.ensureReadyPromise;const e=(async()=>{let t=this.simpleUser;if(t||(t=this._createSimpleUser(),this.simpleUser=t),!this.connected){let i=null;const s=t.connect().catch(r=>(i=r,null));if(this.connected||await Promise.race([s,this._waitForConnectionConfirmation()]),this.connected||await s,i)throw i;this.connected||await this._waitForConnectionConfirmation(),this._assertSimpleUserIsCurrent(t,"连接流程"),this.connected=!0,this.onStateChange&&this.onStateChange("connected",this.getConnectionSnapshot())}if(!this.registered){let i=null;try{await this._ensureRegistration(t)}catch(r){i=r}let s=this._captureRegistrationDiagnostics();if(i&&this._shouldRecoverFromConnectedWithoutRegistration(s)){console.info("SIP 客户端首次建连后未真正进入注册流程,开始立即补注册。",{accountKey:this.account?.internal_number||this.account?.account_key||this.account?.sip_username||null,snapshot:s.snapshot}),await new Promise(r=>window.setTimeout(r,200));try{await this._ensureRegistration(t)}catch(r){i=i||r}s=this._captureRegistrationDiagnostics()}if(i&&!(s?.snapshot?.connected&&s?.snapshot?.registered))throw i}})().finally(()=>{this.ensureReadyPromise===e&&(this.ensureReadyPromise=null)});return this.ensureReadyPromise=e,e}getConnectionSnapshot(){const e=this.simpleUser,t=typeof e?.isConnected=="function"?e.isConnected():!!this.connected,i=typeof e?.isRegistered=="function"?e.isRegistered():!!this.registered;return{connected:t,registered:i,active:!!this.active,sipDomain:this.account?.sip_domain||"",transportUrl:this.account?.webrtc_url||""}}async disconnect(e={}){const{unregister:t=!0}=e||{};if(!this.simpleUser){this.connected=!1,this.registered=!1,this.onStateChange&&this.onStateChange("disconnected",this.getConnectionSnapshot());return}const i=[];t&&this.registered&&i.push(this.simpleUser.unregister().catch(s=>{this.onError&&this.onError(s)})),this.connected&&i.push(this.simpleUser.disconnect().catch(s=>{this.onError&&this.onError(s)})),await Promise.all(i),this.connected=!1,this.registered=!1,this.active=!1,this._stopAllAudioMonitors(),this._reportAudioSilence(),this._stopRingback(),this._stopIncomingTone(),this._clearAutoplayUnlockTasks(),this.onStateChange&&this.onStateChange("disconnected",this.getConnectionSnapshot())}async reconnect(){await this.disconnect(),await this.ensureReady()}async call(e,t){if(!e)throw new Error("电话号码不能为空");this.lastFailureMeta=null,this.hangupInitiatedByLocal=!1,await this.ensureReady();const i=String(e).trim();let s;/^sips?:/i.test(i)?s=i:i.includes("@")?s=`sip:${i}`:s=`sip:${i}@${this.account.sip_domain}`,s=this._normalizeSipDestination(s);const r=this._createInviteRequestDelegate(t),n=this._mergeInviteOptions(t,r);try{await this._preflightAudioAccess("call"),await this.simpleUser.call(s,void 0,n),this._scheduleLocalMonitor(),this._stopIncomingTone()}catch(o){const d=await this._normalizeMediaAccessError(o,"call");throw this.onError&&this.onError(d),this.lastFailureMeta={source:"invite-exception",code:d?.code||null,message:d?.message||"呼叫请求失败",timestamp:new Date().toISOString()},this.onStateChange&&this.onStateChange("failed",{...this.lastFailureMeta}),d}}async _preflightAudioAccess(e="call"){let t=null;try{t=await di(e)}catch(i){const s=await Be(i,e);throw this.onError&&this.onError(s),this.lastFailureMeta={source:"media-preflight",code:s?.code||null,message:s?.message||"媒体设备检查失败",timestamp:new Date().toISOString()},this.onStateChange&&this.onStateChange("failed",{...this.lastFailureMeta}),s}finally{t?.getTracks?.().forEach(i=>{try{i.stop()}catch{}})}}_mergeInviteOptions(e,t){const i={...e||{}};return i.requestDelegate=t.requestDelegate,i}_handleInviteProgress(e){const t=e?.message;if(!t)return;const i=t.statusCode||0;if(i>=180&&i<200){const s=this._extractResponseMeta("progress",e);console.info("[CALL] 呼叫收到临时响应",s),this._isIncomingSession()||(this.onStateChange&&this.onStateChange(i===180?"ringing":"dialing",s),i===180&&this._playRingback())}}_handleInviteFailure(e,t){const i=this._extractResponseMeta(e,t);this.lastFailureMeta=i,console.warn("[CALL] 呼叫失败",i),this._stopRingback(),this._stopIncomingTone(),this._stopAllAudioMonitors(),this._reportAudioSilence(),this.onStateChange&&this.onStateChange("failed",{...i}),this.onError&&i.message&&this.onError(new Error(i.message))}_extractResponseMeta(e,t){const i=t?.message,s=i?.statusCode||null,r=(i?.reasonPhrase||"").trim(),n=this._getHeaderValue(i,"Warning"),o=this._getHeaderValue(i,"Reason"),d=this._getHeaderValue(i,"Retry-After"),p=typeof i?.body=="string"?i.body.trim():"",w=[s,r].filter(Boolean).join(" ").trim()||n||o||p||"呼叫未建立";return{source:"invite-response",stage:e,status:s,reason:r,warning:n,cause:o,retryAfter:d,body:p,message:w,timestamp:new Date().toISOString()}}_getHeaderValue(e,t){if(!e||typeof e.getHeader!="function")return"";try{const i=e.getHeader(t);return i?String(i).trim():""}catch{return""}}async _normalizeMediaAccessError(e,t="call"){return Be(e,t)}_createInviteRequestDelegate(e){const t=(s,...r)=>{if(typeof s=="function")try{s(...r)}catch(n){console.warn("SIP 邀请回调执行异常",n)}},i=e?.requestDelegate||{};return{requestDelegate:{onAccept:(...s)=>{this.lastFailureMeta=null,t(i.onAccept,...s)},onRedirect:(s,...r)=>{this._handleInviteFailure("redirect",s),t(i.onRedirect,s,...r)},onReject:(s,...r)=>{this._handleInviteFailure("reject",s),t(i.onReject,s,...r)},onProgress:(s,...r)=>{this._handleInviteProgress(s),t(i.onProgress,s,...r)},onTrying:(...s)=>{t(i.onTrying,...s)}}}}async answer(e){if(!this.simpleUser)throw new Error("呼叫客户端未初始化");try{await this._preflightAudioAccess("answer"),this._stopIncomingTone(),this._stopRingback(),await this.simpleUser.answer(e),this._scheduleLocalMonitor()}catch(t){const i=await this._normalizeMediaAccessError(t,"answer");throw this.onError&&this.onError(i),i}}async decline(){if(!this.simpleUser){this._stopAllAudioMonitors(),this._reportAudioSilence(),this._stopRingback(),this._stopIncomingTone();return}let e=null;try{await this.simpleUser.decline()}catch(t){e=t,this.onError&&this.onError(t)}finally{this._stopAllAudioMonitors(),this._reportAudioSilence(),this._stopRingback(),this._stopIncomingTone()}if(e)throw e}async hangup(){if(!this.simpleUser){this._stopAllAudioMonitors(),this._reportAudioSilence(),this._stopRingback(),this._stopIncomingTone();return}let e=null;try{this.hangupInitiatedByLocal=!0,await this.simpleUser.hangup()}catch(t){e=t,this.onError&&this.onError(t)}finally{this._stopAllAudioMonitors(),this._reportAudioSilence(),this._stopRingback(),this._stopIncomingTone()}if(e)throw e}mute(){if(this.simpleUser)try{this.simpleUser.mute()}catch(e){throw this.onError&&this.onError(e),e}}unmute(){if(this.simpleUser)try{this.simpleUser.unmute()}catch(e){throw this.onError&&this.onError(e),e}}async hold(){if(this.simpleUser)try{await this.simpleUser.hold()}catch(e){throw this.onError&&this.onError(e),e}}async unhold(){if(this.simpleUser)try{await this.simpleUser.unhold()}catch(e){throw this.onError&&this.onError(e),e}}async destroy(){this._rejectConnectionWaiters(Le("SIP 客户端已销毁,已取消连接等待。")),this._rejectRegistrationWaiters(Le("SIP 客户端已销毁,已取消注册等待。")),this.ensureReadyPromise=null;const e=this.simpleUser;if(e)try{this.registered&&await e.unregister(),this.connected&&await e.disconnect()}catch(t){this.onError&&this.onError(t)}this._stopAllAudioMonitors(),this._closeAudioContext(),this._stopRingback(),this._stopIncomingTone(),this._clearAutoplayUnlockTasks(),this.simpleUser=null,this.connected=!1,this.registered=!1,this.active=!1,this.account=null,this.audioTrackMeta={local:null,remote:null},this.peerConnectionDebug={attached:!1,events:[],iceCandidates:[],iceCandidateErrors:[]}}_scheduleLocalMonitor(e=0){if(!this.onAudioLevel)return;const t=this.simpleUser?.localMediaStream;if(t instanceof MediaStream&&t.getAudioTracks().length>0){this._startAudioLevelMonitor("local",t);return}e>10||setTimeout(()=>this._scheduleLocalMonitor(e+1),200*(e+1))}_scheduleRemoteMonitor(e=0){if(!this.onAudioLevel||!this.simpleUser)return;const t=this.simpleUser.remoteMediaStream||this._getRemoteAudioElement()?.srcObject;if(t instanceof MediaStream&&t.getAudioTracks&&t.getAudioTracks().length>0){this._startAudioLevelMonitor("remote",t);return}e>10||setTimeout(()=>this._scheduleRemoteMonitor(e+1),300*(e+1))}_startAudioLevelMonitor(e,t){if(!(!this.onAudioLevel&&!this.onAudioFrame)&&t instanceof MediaStream&&!(t.getAudioTracks&&t.getAudioTracks().length===0)&&this._ensureAudioContext()){try{if(t.getAudioTracks&&typeof t.getAudioTracks=="function"){const[i]=t.getAudioTracks();if(i){const s={track_id:i.id||null,label:i.label||null};try{s.settings=i.getSettings?i.getSettings():void 0}catch{s.settings=void 0}try{s.constraints=i.getConstraints?i.getConstraints():void 0}catch{s.constraints=void 0}this.audioTrackMeta=this.audioTrackMeta||{},this.audioTrackMeta[e]=s}}}catch{}this._stopAudioLevelMonitor(e);try{const i=this.audioContext.createMediaStreamSource(t),s=this.audioContext.createAnalyser();s.fftSize=2048;const r=new Uint8Array(s.fftSize);i.connect(s);const n=typeof this.onAudioFrame=="function",o=()=>{s.getByteTimeDomainData(r);let p=0;const g=n?new Float32Array(r.length):null;for(let _=0;_<r.length;_+=1){const E=(r[_]-128)/128;p+=E*E,g&&(g[_]=E)}const w=Math.sqrt(p/r.length),S=Math.min(1,w*2);if(this.onAudioLevel&&this.onAudioLevel(e,S),g&&this.audioContext&&this.onAudioFrame)try{this.onAudioFrame(e,{samples:g,sampleRate:this.audioContext.sampleRate||48e3,timestamp:Date.now()})}catch{}const I=this.audioMonitors[e];I&&(I.rafId=window.requestAnimationFrame(o))},d={source:i,analyser:s,dataArray:r,rafId:window.requestAnimationFrame(o)};this.audioMonitors[e]=d}catch(i){this.onError&&this.onError(i)}}}_stopAudioLevelMonitor(e){const t=this.audioMonitors[e];if(t){t.rafId&&window.cancelAnimationFrame(t.rafId);try{t.source&&t.source.disconnect()}catch{}delete this.audioMonitors[e]}}_stopAllAudioMonitors(){this._stopAudioLevelMonitor("local"),this._stopAudioLevelMonitor("remote"),!this.audioMonitors.local&&!this.audioMonitors.remote&&this._closeAudioContext()}_resolveToneUrl(e){return e?e.startsWith("http://")||e.startsWith("https://")||e.startsWith("/")?e:`/assets/${e}`:null}_normalizeAudioUrl(e){if(!e)return"";if(typeof window>"u")return e;try{const t=window.location?.origin||window.location?.href;return t?new URL(e,t).href:e}catch{return e}}_getAudioSourceMeta(e){return e?e._sipToneSrc?e._sipToneSrc:e.dataset&&e.dataset.sipToneSrc?e.dataset.sipToneSrc:this._normalizeAudioUrl(e.src||""):""}_markAudioSourceMeta(e,t){e&&(e.dataset&&(e.dataset.sipToneSrc=t),e._sipToneSrc=t)}_loadAudio(e,t){if(!e||typeof window>"u")return null;const i=this._normalizeAudioUrl(e);if(t){if(this._getAudioSourceMeta(t)===i)return t;try{t.pause(),t.currentTime=0}catch(r){console.warn("停止旧音频失败:",r)}}try{const s=new Audio(i);return s.loop=!0,s.preload="auto",s.volume=.6,s.playsInline=!0,s.autoplay=!0,this._markAudioSourceMeta(s,i),s}catch(s){return console.warn("创建音频失败:",s),null}}_shouldUnlock(e){if(!e)return!1;const t=e.name||e.code||"";return t==="NotAllowedError"||t==="SecurityError"}_attemptPlayAudio(e,{onSuccess:t,onError:i,unlockKey:s}){if(!e)return!1;this._ensureAudioContext();const r=()=>{typeof t=="function"&&t(),s&&this._removePendingPlaybackTask(s)},n=o=>{if(this._shouldUnlock(o)&&s){this._requestAutoplayUnlock(s,()=>{this._attemptPlayAudio(e,{onSuccess:t,onError:i,unlockKey:s})});return}typeof i=="function"&&i(o)};try{const o=e.play();o&&typeof o.then=="function"?o.then(()=>r()).catch(d=>n(d)):r()}catch(o){n(o)}return!0}_requestAutoplayUnlock(e,t){!e||typeof t!="function"||(this.pendingPlaybackTasks instanceof Map||(this.pendingPlaybackTasks=new Map),this.pendingPlaybackTasks.has(e)||this.pendingPlaybackTasks.set(e,t),!this.audioUnlockHandler&&typeof document<"u"&&(this.audioUnlockHandler=()=>{this._flushAutoplayTasks()},document.addEventListener("click",this.audioUnlockHandler,!0),document.addEventListener("touchstart",this.audioUnlockHandler,!0),document.addEventListener("keydown",this.audioUnlockHandler,!0)))}_flushAutoplayTasks(){if(!(this.pendingPlaybackTasks instanceof Map)||this.pendingPlaybackTasks.size===0){this._removeAudioUnlockHandler();return}const e=Array.from(this.pendingPlaybackTasks.entries());this.pendingPlaybackTasks.clear(),e.forEach(([t,i])=>{try{i()}catch(s){console.warn("触发音频播放失败:",s),this.pendingPlaybackTasks instanceof Map&&this.pendingPlaybackTasks.set(t,i)}}),(!(this.pendingPlaybackTasks instanceof Map)||this.pendingPlaybackTasks.size===0)&&this._removeAudioUnlockHandler()}_removeAudioUnlockHandler(){!this.audioUnlockHandler||typeof document>"u"||(document.removeEventListener("click",this.audioUnlockHandler,!0),document.removeEventListener("touchstart",this.audioUnlockHandler,!0),document.removeEventListener("keydown",this.audioUnlockHandler,!0),this.audioUnlockHandler=null)}_removePendingPlaybackTask(e){this.pendingPlaybackTasks instanceof Map&&(this.pendingPlaybackTasks.delete(e),this.pendingPlaybackTasks.size===0&&this._removeAudioUnlockHandler())}_clearAutoplayUnlockTasks(){this.pendingPlaybackTasks instanceof Map&&this.pendingPlaybackTasks.clear(),this._removeAudioUnlockHandler()}_playRingback(){this.ringbackPlaying||(this.ringbackAudio=this._loadAudio(this.ringbackToneUrl,this.ringbackAudio),this.ringbackAudio&&this._attemptPlayAudio(this.ringbackAudio,{unlockKey:"ringback",onSuccess:()=>{this.ringbackPlaying=!0},onError:e=>{this.ringbackPlaying=!1,console.warn("播放回铃音异常:",e)}}))}_stopRingback(){if(this.ringbackAudio){try{this.ringbackAudio.pause(),this.ringbackAudio.currentTime=0}catch(e){console.warn("停止回铃音异常:",e)}this.ringbackPlaying=!1,this._removePendingPlaybackTask("ringback")}}_playIncomingTone(){this.incomingPlaying||(this.incomingAudio=this._loadAudio(this.incomingToneUrl,this.incomingAudio),this.incomingAudio&&this._attemptPlayAudio(this.incomingAudio,{unlockKey:"incoming",onSuccess:()=>{this.incomingPlaying=!0},onError:e=>{this.incomingPlaying=!1,console.warn("播放振铃音异常:",e)}}))}_stopIncomingTone(){if(this.incomingAudio){try{this.incomingAudio.pause(),this.incomingAudio.currentTime=0}catch(e){console.warn("停止振铃音异常:",e)}this.incomingPlaying=!1,this._removePendingPlaybackTask("incoming")}}updateToneOptions({ringbackTone:e,incomingTone:t}){if(e){const i=this._resolveToneUrl(e);i&&i!==this.ringbackToneUrl&&(this._stopRingback(),this.ringbackToneUrl=i,this.ringbackAudio=null)}if(t){const i=this._resolveToneUrl(t);i&&i!==this.incomingToneUrl&&(this._stopIncomingTone(),this.incomingToneUrl=i,this.incomingAudio=null)}}silenceIncomingTone(){this._stopIncomingTone()}_reportAudioSilence(){this.onAudioLevel&&(this.onAudioLevel("local",0),this.onAudioLevel("remote",0))}_ensureAudioContext(){if(typeof window>"u"||!window.AudioContext&&!window.webkitAudioContext)return!1;if(!this.audioContext){const e=window.AudioContext||window.webkitAudioContext;this.audioContext=new e}return this.audioContext.state==="suspended"&&this.audioContext.resume().catch(()=>{}),!0}_closeAudioContext(){if(this.audioContext)try{this.audioContext.close()}catch{}this.audioContext=null}}const hi=/^[a-z][a-z0-9+.-]*:\/\//i;function ui(a,e=""){const t=String(a||e||"").trim();if(!t)throw new Error("缺少统一服务基址");return t}function dt(a,e="https://"){return hi.test(a)?a:`${e}${a.replace(/^\/+/,"")}`}function lt(a="/"){return a.endsWith("/")?a:`${a}/`}function ht(a="/",e=""){const t=lt(a||"/"),i=String(e||"").replace(/^\/+/,"");return i?`${t}${i}`:t}function $e(a,e=""){const t=dt(ui(a,e)),i=new URL(t);return i.search="",i.hash="",i.pathname=lt(i.pathname||"/"),i}function gi(a,e=""){return $e(a,e).pathname!=="/"}function J(a,e,t=""){const i=$e(a,t);return i.pathname=ht(i.pathname,e),i.href}function je(a,e="ws",t=""){const i=$e(a,t);return i.protocol==="https:"?i.protocol="wss:":i.protocol==="http:"?i.protocol="ws:":i.protocol!=="ws:"&&i.protocol!=="wss:"&&(i.protocol="wss:"),i.pathname=ht(i.pathname,e),i.href}function ut(a,e=""){if(!String(a||"").trim())return String(e||"").replace(/^\/+/,"");const t=new URL(dt(String(a).trim())),i=String(t.pathname||"/").replace(/^\/+/,""),s=String(t.search||"");return`${i}${s}`||String(e||"").replace(/^\/+/,"")}function gt({apiBaseUrl:a,transportBaseUrl:e="",transportUrl:t="",fallbackPath:i="ws"}){const s=String(e||"").trim();return s?je(s,ut(t,i)):gi(a)?je(a,ut(t,i)):String(t||"").trim()?String(t).trim():je(a,i)}const fi=`
|
|
66
66
|
:host { all: initial; }
|
|
67
67
|
.widget { font-family: "Noto Sans SC", "PingFang SC", sans-serif; color: #16302b; }
|
|
68
68
|
.panel { width: min(360px, 92vw); border-radius: 24px; overflow: hidden; background: linear-gradient(180deg, #fffdf6, #f4efe4); box-shadow: 0 22px 54px rgba(17, 30, 28, 0.22); border: 1px solid rgba(28, 78, 67, 0.12); }
|
|
@@ -97,8 +97,8 @@ Duration=`+2e3}};return e.info({requestOptions:n}).then(()=>{})}}async transfer(
|
|
|
97
97
|
.btn { min-height: 44px; font-size: 15px; }
|
|
98
98
|
.logs { max-height: 140px; padding: 0 16px 16px; }
|
|
99
99
|
}
|
|
100
|
-
`,Q={idle:"网页电话已加载,正在准备待机能力。",requesting:"正在向服务端申请网页通话会话。",standbyRequesting:"正在向服务端申请 widget 待机会话。",registering:"临时账号已拿到,正在连接并执行 REGISTER。",standby:"REGISTER 已完成,widget 正在等待来电。",incoming:"收到新的来电,请选择接听或拒接。",calling:"已发起呼叫,正在等待对端振铃或接听。",connected:"通话已接通,媒体链路正在运行。",ended:"
|
|
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||
|
|
100
|
+
`,Q={idle:"网页电话已加载,正在准备待机能力。",requesting:"正在向服务端申请网页通话会话。",standbyRequesting:"正在向服务端申请 widget 待机会话。",registering:"临时账号已拿到,正在连接并执行 REGISTER。",standby:"REGISTER 已完成,widget 正在等待来电。",incoming:"收到新的来电,请选择接听或拒接。",calling:"已发起呼叫,正在等待对端振铃或接听。",connected:"通话已接通,媒体链路正在运行。",ended:"通话已结束,网页电话正在返回待机。",rejected:"本次来电已拒接,widget 仍保持待机。",missed:"本次来电未接听,widget 仍保持待机。",failed:"本次呼叫失败,请先看下方日志。"},pi="手机网页首次接入时,请允许麦克风权限;若切到后台后再回来,先确认页面仍保持前台活跃。",mi=5e3,wi=15e3,ft=2,Se=3e4,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||""}function xi(a){const e=new Error(a);return e.name="EmbeddedCallWidgetLifecycleCancelledError",e.code="widget_runtime_run_stale",e}function pt(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.beforeUnloadHandler=null,this.pageHideHandler=null,this.unloadCleanupStarted=!1,this.refs={}}async mount(){this.isMobileViewport=typeof window<"u"&&window.matchMedia("(max-width: 480px)").matches,this.shadowRoot.innerHTML=`
|
|
102
102
|
<style>${fi}</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),this.beforeUnloadHandler=()=>{this.flushUnloadCleanup("beforeunload")},this.pageHideHandler=e=>{e?.persisted||this.flushUnloadCleanup("pagehide")},window.addEventListener("beforeunload",this.beforeUnloadHandler),window.addEventListener("pagehide",this.pageHideHandler)))}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,pt(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(pt(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 li(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(J(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(J(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(J(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(J(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(J(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(()=>{})}postLifecycleBeacon(e,t){const i=JSON.stringify(t),s=J(this.apiBaseUrl,e);if(typeof navigator<"u"&&typeof navigator.sendBeacon=="function")try{const r=new Blob([i],{type:"application/json"});if(navigator.sendBeacon(s,r))return!0}catch{}return fetch(s,{method:"POST",credentials:"include",cache:"no-store",keepalive:!0,headers:{"Content-Type":"application/json"},body:i}).catch(()=>{}),!0}flushUnloadCleanup(e){if(this.unloadCleanupStarted||(this.unloadCleanupStarted=!0,this.stopSessionTouch(),this.stopPresenceTouch(),this.stopTransportDisconnectGrace(),!this.issuedSession?.session_id))return;const t=this.issuedSession.session_id;this.markRecentlyReleasedSession(t,e),this.postLifecycleBeacon("api/sip/release-call-session",{session_id:t,status:"released",reason:e,client_summary:this.buildClientEvidenceSummary("released",{releaseReason:e})}),this.issuedSession=null}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(J(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>=ft){const r=this.networkOnline===!1?"当前网络连接已断开,正在等待恢复。":"当前无法连接通话服务,正在自动尝试恢复。";this.setAvailabilityState("offline",r)}else this.setAvailabilityState("recovering","正在确认后台连接状态。");this.presenceFailureCount<=ft&&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(J(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(J(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(J(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)},mi))}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?pi:"桌面网页可直接使用;若宿主站点启用了来源白名单或接入令牌,请按站点配置提供对应参数。"}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.beforeUnloadHandler&&(window.removeEventListener("beforeunload",this.beforeUnloadHandler),this.beforeUnloadHandler=null),this.pageHideHandler&&(window.removeEventListener("pagehide",this.pageHideHandler),this.pageHideHandler=null)),this.e2eBridgeEnabled&&typeof window<"u"&&window.__embeddedCallWidgetE2E__&&delete window.__embeddedCallWidgetE2E__,this.shadowRoot.innerHTML=""}}function mt(a){return new Ii(a)}return typeof window<"u"&&(window.__EmbeddedCallWidgetRuntime__={createEmbeddedCallWidgetRuntime:mt}),ke.createEmbeddedCallWidgetRuntime=mt,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.issuedSession?.session_id&&this.touchIssuedSession(this.state,{trigger:"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),this.beforeUnloadHandler=()=>{this.flushUnloadCleanup("beforeunload")},this.pageHideHandler=e=>{e?.persisted||this.flushUnloadCleanup("pagehide")},window.addEventListener("beforeunload",this.beforeUnloadHandler),window.addEventListener("pagehide",this.pageHideHandler)))}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()),destroyWidget:async()=>(await this.destroy(),null)}}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,pt(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(pt(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 li(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("preparing","通话已结束,正在自动返回网页电话待机。"),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("preparing","网页电话已断开,系统正在自动返回待机。"),this.appendLog("连接已断开",{reason:e}),!this.legacyOutboundOnly&&this.networkOnline!==!1&&this.scheduleAutoStandbyRecovery(e))}async loadBootstrap(){if(this.bootstrap)return this.bootstrap;const e=await fetch(J(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(J(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(J(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(J(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(J(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(()=>{})}postLifecycleBeacon(e,t){const i=JSON.stringify(t),s=J(this.apiBaseUrl,e);if(typeof navigator<"u"&&typeof navigator.sendBeacon=="function")try{const r=new Blob([i],{type:"application/json"});if(navigator.sendBeacon(s,r))return!0}catch{}return fetch(s,{method:"POST",credentials:"include",cache:"no-store",keepalive:!0,headers:{"Content-Type":"application/json"},body:i}).catch(()=>{}),!0}flushUnloadCleanup(e){if(this.unloadCleanupStarted||(this.unloadCleanupStarted=!0,this.stopSessionTouch(),this.stopPresenceTouch(),this.stopTransportDisconnectGrace(),!this.issuedSession?.session_id))return;const t=this.issuedSession.session_id;this.markRecentlyReleasedSession(t,e),this.postLifecycleBeacon("api/sip/release-call-session",{session_id:t,status:"released",reason:e,client_summary:this.buildClientEvidenceSummary("released",{releaseReason:e})}),this.issuedSession=null}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&&["idle","ended","missed","rejected"].includes(this.state)){this.setAvailabilityState("preparing","页面仍在线,正在自动进入网页电话待机。"),this.scheduleAutoStandbyRecovery("missing-issued-session");return}if(!this.legacyOutboundOnly&&!this.issuedSession?.session_id&&!this.pendingLifecycleMode&&!["calling","connected","incoming"].includes(this.state)){this.setAvailabilityState("preparing","页面仍在线,正在重新建立网页电话待机。"),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(J(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>=ft){const r=this.networkOnline===!1?"当前网络连接已断开,正在等待恢复。":"当前无法连接通话服务,正在自动尝试恢复。";this.setAvailabilityState("offline",r)}else this.setAvailabilityState("recovering","正在确认后台连接状态。");this.presenceFailureCount<=ft&&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(J(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(J(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(J(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)},mi))}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?pi:"桌面网页可直接使用;若宿主站点启用了来源白名单或接入令牌,请按站点配置提供对应参数。"}async destroy(){this.clearAutoStandbyRecovery(),await this.disconnectAndCleanup("destroy-widget",{suppressStateReset:!0}),typeof document<"u"&&this.visibilityHandler&&(document.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null),this.stopSessionTouch(),this.stopPresenceTouch(),this.stopTransportDisconnectGrace(),this.pendingLifecycleMode=null,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.beforeUnloadHandler&&(window.removeEventListener("beforeunload",this.beforeUnloadHandler),this.beforeUnloadHandler=null),this.pageHideHandler&&(window.removeEventListener("pagehide",this.pageHideHandler),this.pageHideHandler=null)),this.e2eBridgeEnabled&&typeof window<"u"&&window.__embeddedCallWidgetE2E__&&delete window.__embeddedCallWidgetE2E__,this.shadowRoot.innerHTML=""}}function mt(a){return new Ii(a)}return typeof window<"u"&&(window.__EmbeddedCallWidgetRuntime__={createEmbeddedCallWidgetRuntime:mt}),ke.createEmbeddedCallWidgetRuntime=mt,Object.defineProperty(ke,Symbol.toStringTag,{value:"Module"}),ke})({});
|
|
@@ -40,7 +40,7 @@ class Et {
|
|
|
40
40
|
return this.incomingAckRequest.message;
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
-
class
|
|
43
|
+
class Rt {
|
|
44
44
|
/** @internal */
|
|
45
45
|
constructor(e) {
|
|
46
46
|
this.incomingByeRequest = e;
|
|
@@ -58,7 +58,7 @@ class xt {
|
|
|
58
58
|
return this.incomingByeRequest.reject(e), Promise.resolve();
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
class
|
|
61
|
+
class xt {
|
|
62
62
|
/** @internal */
|
|
63
63
|
constructor(e) {
|
|
64
64
|
this.incomingCancelRequest = e;
|
|
@@ -1873,7 +1873,7 @@ class de {
|
|
|
1873
1873
|
function ot(a) {
|
|
1874
1874
|
return a === "application/sdp" ? "session" : "render";
|
|
1875
1875
|
}
|
|
1876
|
-
function
|
|
1876
|
+
function Re(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
|
}
|
|
@@ -2497,7 +2497,7 @@ class ge {
|
|
|
2497
2497
|
return;
|
|
2498
2498
|
}
|
|
2499
2499
|
if (this.delegate && this.delegate.onBye) {
|
|
2500
|
-
const t = new
|
|
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) =>
|
|
2680
|
+
return t.getDescription(i, s).then((r) => Re(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) =>
|
|
2752
|
+
return i.setDescription(e.content, s, r).then(() => i.getDescription(s, r)).then((n) => Re(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 ?
|
|
3036
|
+
const r = e.body ? Re(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
|
/**
|
|
@@ -3049,7 +3049,7 @@ class ce extends ge {
|
|
|
3049
3049
|
return;
|
|
3050
3050
|
}
|
|
3051
3051
|
if (this.delegate && this.delegate.onCancel) {
|
|
3052
|
-
const t = new
|
|
3052
|
+
const t = new xt(e);
|
|
3053
3053
|
this.delegate.onCancel(t);
|
|
3054
3054
|
}
|
|
3055
3055
|
this.isCanceled = !0, this.incomingInviteRequest.reject({ statusCode: 487 }), this.stateTransition(m.Terminated);
|
|
@@ -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 ?
|
|
3132
|
+
const t = e.statusCode || 180, i = e.reasonPhrase, s = (e.extraHeaders || []).slice(), r = e.body ? Re(e.body) : void 0;
|
|
3133
3133
|
if (t === 183 && !r)
|
|
3134
3134
|
return this.sendProgressWithSDP(e);
|
|
3135
3135
|
try {
|
|
@@ -3747,10 +3747,10 @@ class Ht {
|
|
|
3747
3747
|
return this.userAgent.userAgentCore.request(this.request, e.requestDelegate), Promise.resolve();
|
|
3748
3748
|
}
|
|
3749
3749
|
}
|
|
3750
|
-
var
|
|
3750
|
+
var R;
|
|
3751
3751
|
(function(a) {
|
|
3752
3752
|
a.Initial = "Initial", a.Registered = "Registered", a.Unregistered = "Unregistered", a.Terminated = "Terminated";
|
|
3753
|
-
})(
|
|
3753
|
+
})(R = R || (R = {}));
|
|
3754
3754
|
class Y {
|
|
3755
3755
|
/**
|
|
3756
3756
|
* Constructs a new instance of the `Registerer` class.
|
|
@@ -3758,7 +3758,7 @@ class Y {
|
|
|
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 =
|
|
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;
|
|
3762
3762
|
const i = e.configuration.uri.clone();
|
|
3763
3763
|
if (i.user = void 0, this.options = Object.assign(Object.assign(Object.assign({}, Y.defaultOptions()), { registrar: i }), Y.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 Y {
|
|
|
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 ===
|
|
3854
|
+
if (!this.waiting && this._state === R.Registered) {
|
|
3855
3855
|
this.stateChange.addListener(() => {
|
|
3856
3856
|
this.terminated(), e();
|
|
3857
3857
|
}, { once: !0 }), this.unregister();
|
|
@@ -3871,7 +3871,7 @@ class Y {
|
|
|
3871
3871
|
* Rejects with `RequestPendingError` if a REGISTER request is already in progress.
|
|
3872
3872
|
*/
|
|
3873
3873
|
register(e = {}) {
|
|
3874
|
-
if (this.state ===
|
|
3874
|
+
if (this.state === R.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 Y {
|
|
|
3964
3964
|
* Rejects with `RequestPendingError` if a REGISTER request is already in progress.
|
|
3965
3965
|
*/
|
|
3966
3966
|
unregister(e = {}) {
|
|
3967
|
-
if (this.state ===
|
|
3967
|
+
if (this.state === R.Terminated)
|
|
3968
3968
|
throw this.stateError(), new Error("Registerer terminated. Unable to register.");
|
|
3969
|
-
if (this.disposed && this.state !==
|
|
3969
|
+
if (this.disposed && this.state !== R.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 !==
|
|
3976
|
+
this._state !== R.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 Y {
|
|
|
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 !==
|
|
4021
|
+
}, e * 1e3), this._state !== R.Registered && this.stateTransition(R.Registered);
|
|
4022
4022
|
}
|
|
4023
4023
|
/**
|
|
4024
4024
|
* Helper function, called when unregistered.
|
|
4025
4025
|
*/
|
|
4026
4026
|
unregistered() {
|
|
4027
|
-
this.clearTimers(), this._state !==
|
|
4027
|
+
this.clearTimers(), this._state !== R.Unregistered && this.stateTransition(R.Unregistered);
|
|
4028
4028
|
}
|
|
4029
4029
|
/**
|
|
4030
4030
|
* Helper function, called when terminated.
|
|
4031
4031
|
*/
|
|
4032
4032
|
terminated() {
|
|
4033
|
-
this.clearTimers(), this._state !==
|
|
4033
|
+
this.clearTimers(), this._state !== R.Terminated && this.stateTransition(R.Terminated);
|
|
4034
4034
|
}
|
|
4035
4035
|
/**
|
|
4036
4036
|
* Transition registration state.
|
|
@@ -4040,22 +4040,22 @@ class Y {
|
|
|
4040
4040
|
throw new Error(`Invalid state transition from ${this._state} to ${e}`);
|
|
4041
4041
|
};
|
|
4042
4042
|
switch (this._state) {
|
|
4043
|
-
case
|
|
4044
|
-
e !==
|
|
4043
|
+
case R.Initial:
|
|
4044
|
+
e !== R.Registered && e !== R.Unregistered && e !== R.Terminated && t();
|
|
4045
4045
|
break;
|
|
4046
|
-
case
|
|
4047
|
-
e !==
|
|
4046
|
+
case R.Registered:
|
|
4047
|
+
e !== R.Unregistered && e !== R.Terminated && t();
|
|
4048
4048
|
break;
|
|
4049
|
-
case
|
|
4050
|
-
e !==
|
|
4049
|
+
case R.Unregistered:
|
|
4050
|
+
e !== R.Registered && e !== R.Terminated && t();
|
|
4051
4051
|
break;
|
|
4052
|
-
case
|
|
4052
|
+
case R.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 ===
|
|
4058
|
+
this._state = e, this.logger.log(`Registration transitioned to state ${this._state}`), this._stateEventEmitter.emit(this._state), e === R.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 Y {
|
|
|
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 ===
|
|
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"}.`;
|
|
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
|
Y.defaultExpires = 600;
|
|
4088
4088
|
Y.defaultRefreshFrequency = 99;
|
|
4089
|
-
var
|
|
4089
|
+
var x;
|
|
4090
4090
|
(function(a) {
|
|
4091
4091
|
a.Initial = "Initial", a.NotifyWait = "NotifyWait", a.Pending = "Pending", a.Active = "Active", a.Terminated = "Terminated";
|
|
4092
|
-
})(
|
|
4092
|
+
})(x = x || (x = {}));
|
|
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 Lt extends q {
|
|
|
5977
5977
|
function Bt(a) {
|
|
5978
5978
|
return a.userAgentCore !== void 0;
|
|
5979
5979
|
}
|
|
5980
|
-
class
|
|
5980
|
+
class xe extends Z {
|
|
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
|
|
6421
|
+
const t = new xe(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 !==
|
|
6951
|
+
if (this.subscriptionState !== x.Pending && this.subscriptionState !== x.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 Qt(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(
|
|
6965
|
+
this.stateTransition(x.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(
|
|
6995
|
+
this.stateTransition(x.Pending, r);
|
|
6996
6996
|
break;
|
|
6997
6997
|
case "active":
|
|
6998
|
-
this.stateTransition(
|
|
6998
|
+
this.stateTransition(x.Active, r);
|
|
6999
6999
|
break;
|
|
7000
7000
|
case "terminated":
|
|
7001
|
-
this.stateTransition(
|
|
7001
|
+
this.stateTransition(x.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
|
|
7007
|
+
const n = new xe(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
|
|
7032
|
+
case x.Initial:
|
|
7033
7033
|
i();
|
|
7034
7034
|
return;
|
|
7035
|
-
case
|
|
7035
|
+
case x.NotifyWait:
|
|
7036
7036
|
i();
|
|
7037
7037
|
return;
|
|
7038
|
-
case
|
|
7039
|
-
if (this.subscriptionState !==
|
|
7038
|
+
case x.Pending:
|
|
7039
|
+
if (this.subscriptionState !== x.NotifyWait && this.subscriptionState !== x.Pending) {
|
|
7040
7040
|
i();
|
|
7041
7041
|
return;
|
|
7042
7042
|
}
|
|
7043
7043
|
break;
|
|
7044
|
-
case
|
|
7045
|
-
if (this.subscriptionState !==
|
|
7044
|
+
case x.Active:
|
|
7045
|
+
if (this.subscriptionState !== x.NotifyWait && this.subscriptionState !== x.Pending && this.subscriptionState !== x.Active) {
|
|
7046
7046
|
i();
|
|
7047
7047
|
return;
|
|
7048
7048
|
}
|
|
7049
7049
|
break;
|
|
7050
|
-
case
|
|
7051
|
-
if (this.subscriptionState !==
|
|
7050
|
+
case x.Terminated:
|
|
7051
|
+
if (this.subscriptionState !== x.NotifyWait && this.subscriptionState !== x.Pending && this.subscriptionState !== x.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 ===
|
|
7060
|
+
e === x.Pending && t && (this.subscriptionExpires = t), e === x.Active && t && (this.subscriptionExpires = t), e === x.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 !==
|
|
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());
|
|
7075
7075
|
}
|
|
7076
7076
|
}
|
|
7077
7077
|
class ei extends q {
|
|
@@ -7082,7 +7082,7 @@ class ei 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 =
|
|
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();
|
|
7086
7086
|
}
|
|
7087
7087
|
/**
|
|
7088
7088
|
* Destructor.
|
|
@@ -7132,18 +7132,18 @@ class ei 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 =
|
|
7135
|
+
this.subscriptionState = x.Pending;
|
|
7136
7136
|
break;
|
|
7137
7137
|
case "active":
|
|
7138
|
-
this.subscriptionState =
|
|
7138
|
+
this.subscriptionState = x.Active;
|
|
7139
7139
|
break;
|
|
7140
7140
|
case "terminated":
|
|
7141
|
-
this.subscriptionState =
|
|
7141
|
+
this.subscriptionState = x.Terminated;
|
|
7142
7142
|
break;
|
|
7143
7143
|
default:
|
|
7144
7144
|
throw new Error(`Unrecognized state ${s}.`);
|
|
7145
7145
|
}
|
|
7146
|
-
if (this.subscriptionState !==
|
|
7146
|
+
if (this.subscriptionState !== x.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 ii {
|
|
|
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
|
|
7431
|
+
const o = new xe(this, e);
|
|
7432
7432
|
n.onNotify(o);
|
|
7433
7433
|
return;
|
|
7434
7434
|
}
|
|
@@ -7480,7 +7480,7 @@ class ii {
|
|
|
7480
7480
|
break;
|
|
7481
7481
|
case v.NOTIFY:
|
|
7482
7482
|
{
|
|
7483
|
-
const t = new
|
|
7483
|
+
const t = new xe(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 Y(this.userAgent, this.registererOptions), this.registerer.stateChange.addListener((t) => {
|
|
9209
9209
|
switch (t) {
|
|
9210
|
-
case
|
|
9210
|
+
case R.Initial:
|
|
9211
9211
|
break;
|
|
9212
|
-
case
|
|
9212
|
+
case R.Registered:
|
|
9213
9213
|
this.delegate && this.delegate.onRegistered && this.delegate.onRegistered();
|
|
9214
9214
|
break;
|
|
9215
|
-
case
|
|
9215
|
+
case R.Unregistered:
|
|
9216
9216
|
this.delegate && this.delegate.onUnregistered && this.delegate.onUnregistered(), this.shouldBeRegistered && this.attemptRegistration();
|
|
9217
9217
|
break;
|
|
9218
|
-
case
|
|
9218
|
+
case R.Terminated:
|
|
9219
9219
|
break;
|
|
9220
9220
|
default:
|
|
9221
9221
|
throw new Error("Unknown registerer state.");
|
|
@@ -10527,7 +10527,7 @@ class hi {
|
|
|
10527
10527
|
}
|
|
10528
10528
|
let s = this._captureRegistrationDiagnostics();
|
|
10529
10529
|
if (i && this._shouldRecoverFromConnectedWithoutRegistration(s)) {
|
|
10530
|
-
console.
|
|
10530
|
+
console.info("SIP 客户端首次建连后未真正进入注册流程,开始立即补注册。", {
|
|
10531
10531
|
accountKey: this.account?.internal_number || this.account?.account_key || this.account?.sip_username || null,
|
|
10532
10532
|
snapshot: s.snapshot
|
|
10533
10533
|
}), await new Promise((r) => window.setTimeout(r, 200));
|
|
@@ -11143,7 +11143,7 @@ const pi = `
|
|
|
11143
11143
|
incoming: "收到新的来电,请选择接听或拒接。",
|
|
11144
11144
|
calling: "已发起呼叫,正在等待对端振铃或接听。",
|
|
11145
11145
|
connected: "通话已接通,媒体链路正在运行。",
|
|
11146
|
-
ended: "
|
|
11146
|
+
ended: "通话已结束,网页电话正在返回待机。",
|
|
11147
11147
|
rejected: "本次来电已拒接,widget 仍保持待机。",
|
|
11148
11148
|
missed: "本次来电未接听,widget 仍保持待机。",
|
|
11149
11149
|
failed: "本次呼叫失败,请先看下方日志。"
|
|
@@ -11164,10 +11164,10 @@ function Ci(a) {
|
|
|
11164
11164
|
function Ei(a) {
|
|
11165
11165
|
return typeof a == "function";
|
|
11166
11166
|
}
|
|
11167
|
-
function
|
|
11167
|
+
function Ri() {
|
|
11168
11168
|
return typeof window > "u" ? !1 : new URLSearchParams(window.location.search).get("embeddedCallWidgetE2E") === "1";
|
|
11169
11169
|
}
|
|
11170
|
-
function
|
|
11170
|
+
function xi() {
|
|
11171
11171
|
return typeof crypto < "u" && typeof crypto.randomUUID == "function" ? crypto.randomUUID() : `widget-${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
|
|
11172
11172
|
}
|
|
11173
11173
|
function Le(a) {
|
|
@@ -11179,7 +11179,7 @@ function te(a = {}, e = null) {
|
|
|
11179
11179
|
if (i)
|
|
11180
11180
|
return i;
|
|
11181
11181
|
const s = String(a?.businessKey ?? "").trim();
|
|
11182
|
-
return s ||
|
|
11182
|
+
return s || "";
|
|
11183
11183
|
}
|
|
11184
11184
|
function Ii(a) {
|
|
11185
11185
|
const e = new Error(a);
|
|
@@ -11190,7 +11190,7 @@ function rt(a) {
|
|
|
11190
11190
|
}
|
|
11191
11191
|
class _i {
|
|
11192
11192
|
constructor({ mount: e, shadowRoot: t, options: i }) {
|
|
11193
|
-
this.mountNode = e, this.shadowRoot = t, this.options = i, this.mode = Ci(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 =
|
|
11193
|
+
this.mountNode = e, this.shadowRoot = t, this.options = i, this.mode = Ci(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 = xi(), 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.beforeUnloadHandler = null, this.pageHideHandler = null, this.unloadCleanupStarted = !1, this.refs = {};
|
|
11194
11194
|
}
|
|
11195
11195
|
async mount() {
|
|
11196
11196
|
this.isMobileViewport = typeof window < "u" && window.matchMedia("(max-width: 480px)").matches, this.shadowRoot.innerHTML = `
|
|
@@ -11224,9 +11224,9 @@ class _i {
|
|
|
11224
11224
|
if (this.pageVisible = document.visibilityState === "visible", document.visibilityState === "hidden") {
|
|
11225
11225
|
this.reportIssuedSessionEvent("visibility-hidden", {
|
|
11226
11226
|
visible: !1
|
|
11227
|
-
}), this.touchWidgetPresence("visibility-hidden"), this.appendLog("页面已切到后台", {
|
|
11227
|
+
}), this.touchWidgetPresence("visibility-hidden"), this.issuedSession?.session_id && this.touchIssuedSession(this.state, { trigger: "visibility-hidden" }), this.appendLog("页面已切到后台", {
|
|
11228
11228
|
state: this.state,
|
|
11229
|
-
advice: "
|
|
11229
|
+
advice: "系统会继续自动保活;回到前台后请确认页面仍保持活动。"
|
|
11230
11230
|
}), this.isMobileViewport && ["requesting", "registering", "calling", "connected"].includes(this.state) && (this.refs.status.textContent = "页面已切到后台;手机网页回到前台后请确认通话仍在继续。");
|
|
11231
11231
|
return;
|
|
11232
11232
|
}
|
|
@@ -11318,7 +11318,8 @@ class _i {
|
|
|
11318
11318
|
},
|
|
11319
11319
|
answerIncomingCall: async () => (await this.answerIncomingCall(), this.getSnapshot()),
|
|
11320
11320
|
declineIncomingCall: async () => (await this.declineIncomingCall(), this.getSnapshot()),
|
|
11321
|
-
hangupCall: async () => (await this.hangupCall(), this.getSnapshot())
|
|
11321
|
+
hangupCall: async () => (await this.hangupCall(), this.getSnapshot()),
|
|
11322
|
+
destroyWidget: async () => (await this.destroy(), null)
|
|
11322
11323
|
};
|
|
11323
11324
|
}
|
|
11324
11325
|
async handlePrimaryAction() {
|
|
@@ -11481,7 +11482,7 @@ class _i {
|
|
|
11481
11482
|
}
|
|
11482
11483
|
if (this.state === "failed")
|
|
11483
11484
|
return;
|
|
11484
|
-
this.state !== "idle" && this.state !== "ended" && (this.pendingLifecycleMode = null, this.setState("ended"), this.setAvailabilityState("
|
|
11485
|
+
this.state !== "idle" && this.state !== "ended" && (this.pendingLifecycleMode = null, this.setState("ended"), this.setAvailabilityState("preparing", "通话已结束,正在自动返回网页电话待机。"), this.scheduleAutoStandbyRecovery(`client-${e}`));
|
|
11485
11486
|
}
|
|
11486
11487
|
}
|
|
11487
11488
|
stopTransportDisconnectGrace() {
|
|
@@ -11592,7 +11593,7 @@ class _i {
|
|
|
11592
11593
|
} = t;
|
|
11593
11594
|
i || this.beginLifecycleRun(), r && this.suppressExpectedClientTeardown(r), this.stopSessionTouch(), this.stopTransportDisconnectGrace(), this.reportIssuedSessionEvent("manual-disconnect", {
|
|
11594
11595
|
reason: e
|
|
11595
|
-
}, 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("
|
|
11596
|
+
}, 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("preparing", "网页电话已断开,系统正在自动返回待机。"), this.appendLog("连接已断开", { reason: e }), !this.legacyOutboundOnly && this.networkOnline !== !1 && this.scheduleAutoStandbyRecovery(e));
|
|
11596
11597
|
}
|
|
11597
11598
|
async loadBootstrap() {
|
|
11598
11599
|
if (this.bootstrap)
|
|
@@ -11823,7 +11824,7 @@ class _i {
|
|
|
11823
11824
|
}
|
|
11824
11825
|
scheduleAutoStandbyRecovery(e, t = bi) {
|
|
11825
11826
|
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(() => {
|
|
11826
|
-
this.autoStandbyRecoveryTimer = null, !(this.legacyOutboundOnly || this.networkOnline === !1) && (this.client || this.issuedSession?.session_id || this.finalizing || this.pendingLifecycleMode === "outbound" || (this.appendLog("
|
|
11827
|
+
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 })));
|
|
11827
11828
|
}, t));
|
|
11828
11829
|
}
|
|
11829
11830
|
pruneRecentlyReleasedSessions() {
|
|
@@ -11853,8 +11854,12 @@ class _i {
|
|
|
11853
11854
|
this.setAvailabilityState("preparing", "页面仍在线,正在建立网页电话连接。");
|
|
11854
11855
|
return;
|
|
11855
11856
|
}
|
|
11857
|
+
if (!this.legacyOutboundOnly && !this.issuedSession?.session_id && !this.pendingLifecycleMode && ["idle", "ended", "missed", "rejected"].includes(this.state)) {
|
|
11858
|
+
this.setAvailabilityState("preparing", "页面仍在线,正在自动进入网页电话待机。"), this.scheduleAutoStandbyRecovery("missing-issued-session");
|
|
11859
|
+
return;
|
|
11860
|
+
}
|
|
11856
11861
|
if (!this.legacyOutboundOnly && !this.issuedSession?.session_id && !this.pendingLifecycleMode && !["calling", "connected", "incoming"].includes(this.state)) {
|
|
11857
|
-
this.setAvailabilityState("
|
|
11862
|
+
this.setAvailabilityState("preparing", "页面仍在线,正在重新建立网页电话待机。"), this.scheduleAutoStandbyRecovery("missing-issued-session");
|
|
11858
11863
|
return;
|
|
11859
11864
|
}
|
|
11860
11865
|
this.setAvailabilityState("ready", "当前可被呼叫,也可由访客主动发起呼叫。");
|
|
@@ -12077,12 +12082,12 @@ class _i {
|
|
|
12077
12082
|
this.refs.status && (this.refs.status.className = [
|
|
12078
12083
|
"status",
|
|
12079
12084
|
this.availabilityState === "offline" ? "status--offline" : this.availabilityState === "recovering" ? "status--recovering" : ""
|
|
12080
|
-
].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" ? "
|
|
12085
|
+
].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" ? "挂断通话" : "断开电话");
|
|
12081
12086
|
const r = this.options.siteKey || this.bootstrap?.default_site_key || "等待服务端返回", n = this.issuedSession?.resolved_business_key || te(this.options, this.bootstrap) || "等待服务端返回";
|
|
12082
12087
|
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 : "桌面网页可直接使用;若宿主站点启用了来源白名单或接入令牌,请按站点配置提供对应参数。";
|
|
12083
12088
|
}
|
|
12084
12089
|
async destroy() {
|
|
12085
|
-
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.beforeUnloadHandler && (window.removeEventListener("beforeunload", this.beforeUnloadHandler), this.beforeUnloadHandler = null), this.pageHideHandler && (window.removeEventListener("pagehide", this.pageHideHandler), this.pageHideHandler = null)), this.e2eBridgeEnabled && typeof window < "u" && window.__embeddedCallWidgetE2E__ && delete window.__embeddedCallWidgetE2E__, this.shadowRoot.innerHTML = "";
|
|
12090
|
+
this.clearAutoStandbyRecovery(), await this.disconnectAndCleanup("destroy-widget", { suppressStateReset: !0 }), typeof document < "u" && this.visibilityHandler && (document.removeEventListener("visibilitychange", this.visibilityHandler), this.visibilityHandler = null), this.stopSessionTouch(), this.stopPresenceTouch(), this.stopTransportDisconnectGrace(), this.pendingLifecycleMode = null, 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.beforeUnloadHandler && (window.removeEventListener("beforeunload", this.beforeUnloadHandler), this.beforeUnloadHandler = null), this.pageHideHandler && (window.removeEventListener("pagehide", this.pageHideHandler), this.pageHideHandler = null)), this.e2eBridgeEnabled && typeof window < "u" && window.__embeddedCallWidgetE2E__ && delete window.__embeddedCallWidgetE2E__, this.shadowRoot.innerHTML = "";
|
|
12086
12091
|
}
|
|
12087
12092
|
}
|
|
12088
12093
|
function Ai(a) {
|
package/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"package_name": "@shenyin/embedded-call-widget",
|
|
3
|
-
"source_package_version": "2.6.
|
|
4
|
-
"built_at": "2026-04-
|
|
3
|
+
"source_package_version": "2.6.8",
|
|
4
|
+
"built_at": "2026-04-07T08:21:54.063Z",
|
|
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.
|
|
58
|
-
"git_tag_alias": "v2.6.
|
|
59
|
-
"cdn_version_path": "embedded-call-widget/2.6.
|
|
57
|
+
"git_tag": "embedded-call-widget-v2.6.8",
|
|
58
|
+
"git_tag_alias": "v2.6.8",
|
|
59
|
+
"cdn_version_path": "embedded-call-widget/2.6.8/",
|
|
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":
|
|
73
|
-
"sha256": "
|
|
74
|
-
"sri_sha384": "sha384-
|
|
72
|
+
"size_bytes": 518205,
|
|
73
|
+
"sha256": "6483380c886da72bef01cbe618a6aa029dd1e9e498043ab049ca9f14eebf2ff1",
|
|
74
|
+
"sri_sha384": "sha384-MEaVsfCmrGBGcYAZaPy3Yr2XqmHsPCYGIK0FqM1qgeeOWlLOV7mvZKBph9U8/lQ8"
|
|
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":
|
|
85
|
-
"sha256": "
|
|
86
|
-
"sri_sha384": "sha384-
|
|
84
|
+
"size_bytes": 334600,
|
|
85
|
+
"sha256": "07c282fa03f23eaba25be4a71771f2162136acc6af55370ca0b5e2ad3476c2f0",
|
|
86
|
+
"sri_sha384": "sha384-aDM+CtuNk88isgo1VqF/FG6J4DJFSOARzey8ONKROcF22H0YWu4Wg0Cfboje3cUx"
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
}
|