UNPKG

@observertc/client-monitor-js

Version:

ObserveRTC Client Integration Javascript Library

1 lines 8.78 kB
import{ClientEventTypes as e}from"../schema/ClientEventTypes";const t="ice-disconnected",n="ice-connection-failed",i="ice-transport-stalled",s="unstable-ice-path";export class IceConnectivityDetector{peerConnection;static DISCONNECTED_ISSUE_TYPE=t;static FAILED_ISSUE_TYPE=n;static STALLED_ISSUE_TYPE=i;static UNSTABLE_PATH_ISSUE_TYPE=s;name="ice-connectivity-detector";disabled=!1;_states=new Map;_unstablePaths=new Map;_establishmentRecommendedAt;_establishmentRecommendations=0;constructor(e){this.peerConnection=e}get config(){return this.peerConnection.parent.config.iceConnectivityDetector}update(){if(this.disabled)return;if(this.peerConnection.closed)return;const e=new Set;let t=!1;for(const n of this.peerConnection.iceTransports){e.add(n.id);const i=this._getState(n);this._checkIceState(n,i),this._checkInboundStall(n,i),this._checkIceRestart(n,i),t=this._checkRestartRecommendation(n,i)||t}this._checkPathStability(),this._checkEstablishmentRecommendation(t);for(const t of[...this._states.keys()])e.has(t)||(this._resolveAll(t,"ice transport is gone"),this._states.delete(t))}_checkPathStability(){const{pathSwitchWindowInMs:e,pathSwitchThreshold:t}=this.config,n=Date.now(),i=new Set;for(const o of this.peerConnection.selectedIcePaths){i.add(o.key);const d=o.getSwitchCountSince(n-e),a=this._unstablePaths.get(o.key);d<t?void 0!==a&&(this._resolveIssue(s,o.key,a,"ice path became stable"),this._unstablePaths.delete(o.key)):void 0===a&&(this._unstablePaths.set(o.key,n),this.peerConnection.parent.raiseIssue(this._issueKey(s,o.key),{type:s,payload:{peerConnectionId:this.peerConnection.peerConnectionId,pathKey:o.key,transportId:o.transportId,switches:d,windowInMs:e,kind:o.kind}}))}for(const[e,t]of[...this._unstablePaths.entries()])i.has(e)||(this._resolveIssue(s,e,t,"ice path is gone"),this._unstablePaths.delete(e))}_getState(e){let t=this._states.get(e.id);return t||(t={iceGeneration:0,usernameFragment:this._usernameFragmentOf(e),restartPending:!1,sawInboundTraffic:!1,restartRecommendations:0},this._states.set(e.id,t)),t}_checkIceRestart(e,s){const o=this._usernameFragmentOf(e);void 0!==o&&(void 0!==s.usernameFragment?s.usernameFragment!==o&&(s.usernameFragment=o,s.iceGeneration+=1,s.restartPending=!0,void 0!==s.disconnectRaisedAt&&this._resolveIssue(t,e.id,s.disconnectRaisedAt,"ice restarted"),void 0!==s.failedRaisedAt&&this._resolveIssue(n,e.id,s.failedRaisedAt,"ice restarted"),void 0!==s.stallRaisedAt&&this._resolveIssue(i,e.id,s.stallRaisedAt,"ice restarted"),s.disconnectedSince=void 0,s.disconnectRaisedAt=void 0,s.failedRaisedAt=void 0,s.stallRaisedAt=void 0,s.inboundStalledSince=void 0,s.sawInboundTraffic=!1,this._notifyRestart(e,s,"detected")):s.usernameFragment=o)}_usernameFragmentOf(e){return e.iceLocalUsernameFragment??e.getSelectedCandidatePair()?.getLocalCandidate()?.usernameFragment}_checkIceState(e,s){const o=e.iceState;switch(o){case"failed":if(s.disconnectedSince=void 0,void 0!==s.failedRaisedAt)return;return s.failedRaisedAt=Date.now(),s.restartPending&&(s.restartPending=!1,this._notifyRestart(e,s,"failed")),this.peerConnection.parent.raiseIssue(this._issueKey(n,e.id),{type:n,payload:{peerConnectionId:this.peerConnection.peerConnectionId,transportId:e.id,dtlsState:e.dtlsState,selectedCandidatePairId:e.selectedCandidatePairId,iceGeneration:s.iceGeneration}}),this._resolveIssue(i,e.id,s.stallRaisedAt,"ice connection failed"),s.stallRaisedAt=void 0,void(s.inboundStalledSince=void 0);case"disconnected":{void 0===s.disconnectedSince&&(s.disconnectedSince=Date.now());const n=Date.now()-s.disconnectedSince;if(n<this.config.disconnectedThresholdInMs)return;if(void 0!==s.disconnectRaisedAt)return;return s.disconnectRaisedAt=Date.now(),void this.peerConnection.parent.raiseIssue(this._issueKey(t,e.id),{type:t,payload:{peerConnectionId:this.peerConnection.peerConnectionId,transportId:e.id,iceState:o,dtlsState:e.dtlsState,selectedCandidatePairId:e.selectedCandidatePairId,disconnectedForMs:n,iceGeneration:s.iceGeneration}})}case"connected":case"completed":return s.disconnectedSince=void 0,void 0!==s.disconnectRaisedAt&&(this._resolveIssue(t,e.id,s.disconnectRaisedAt,"ice connection recovered"),s.disconnectRaisedAt=void 0),void 0!==s.failedRaisedAt&&(this._resolveIssue(n,e.id,s.failedRaisedAt,"ice connection recovered"),s.failedRaisedAt=void 0),s.restartRecommendedAt=void 0,void(s.restartPending&&(s.restartPending=!1,this._notifyRestart(e,s,"recovered")));default:return void(s.disconnectedSince=void 0)}}_checkInboundStall(e,t){const n=e.iceState,s=e.getSelectedCandidatePair();if("connected"!==n&&"completed"!==n||!s||"succeeded"!==s.state)return void(t.inboundStalledSince=void 0);const o=s.deltaBytesReceived,d=s.deltaBytesSent;if(void 0===o||void 0===d)return;if(0<o)return t.sawInboundTraffic=!0,t.inboundStalledSince=void 0,void(void 0!==t.stallRaisedAt&&(this._resolveIssue(i,e.id,t.stallRaisedAt,"inbound traffic resumed"),t.stallRaisedAt=void 0));if(!t.sawInboundTraffic||d<=0)return void(t.inboundStalledSince=void 0);void 0===t.inboundStalledSince&&(t.inboundStalledSince=Date.now());const a=Date.now()-t.inboundStalledSince;a<this.config.transportStallThresholdInMs||void 0===t.stallRaisedAt&&(t.stallRaisedAt=Date.now(),this.peerConnection.parent.raiseIssue(this._issueKey(i,e.id),{type:i,payload:{peerConnectionId:this.peerConnection.peerConnectionId,transportId:e.id,iceState:n,candidatePairState:s.state,selectedCandidatePairId:e.selectedCandidatePairId,direction:"inbound",stalledForMs:a,outboundBytesDelta:d,inboundBytesDelta:o,currentRoundTripTime:s.currentRoundTripTime,lastPacketReceivedTimestamp:s.lastPacketReceivedTimestamp,iceGeneration:t.iceGeneration}}))}_checkRestartRecommendation(e,t){const n=Date.now(),{iceRestartRecommendationThresholdInMs:i,iceRestartRecommendationCooldownInMs:s}=this.config;if(t.restartPending)return!1;let o,d=0;return"failed"===e.iceState?(o="ice-failed",d=void 0===t.failedRaisedAt?0:n-t.failedRaisedAt):void 0!==t.disconnectedSince&&i<=n-t.disconnectedSince?(o="ice-disconnected",d=n-t.disconnectedSince):void 0!==t.inboundStalledSince&&i<=n-t.inboundStalledSince&&(o="transport-stalled",d=n-t.inboundStalledSince),void 0===o?(t.restartRecommendedAt=void 0,!1):!(void 0!==t.restartRecommendedAt&&n-t.restartRecommendedAt<s)&&(t.restartRecommendedAt=n,t.restartRecommendations+=1,this._recommendRestart({peerConnectionId:this.peerConnection.peerConnectionId,transportId:e.id,reason:o,conditionDurationInMs:d,iceGeneration:t.iceGeneration,recommendationCount:t.restartRecommendations,iceState:e.iceState,dtlsState:e.dtlsState,selectedCandidatePairId:e.selectedCandidatePairId}),!0)}_checkEstablishmentRecommendation(e){const{connectionState:t,connectingStartedAt:n}=this.peerConnection,i=Date.now();if("connecting"!==t||void 0===n)return void(this._establishmentRecommendedAt=void 0);const s=i-n;if(s<this.config.iceRestartRecommendationThresholdInMs)return;if(e)return;for(const e of this._states.values())if(e.restartPending)return;if(void 0!==this._establishmentRecommendedAt&&i-this._establishmentRecommendedAt<this.config.iceRestartRecommendationCooldownInMs)return;this._establishmentRecommendedAt=i,this._establishmentRecommendations+=1;const[o]=this.peerConnection.iceTransports;this._recommendRestart({peerConnectionId:this.peerConnection.peerConnectionId,reason:"never-established",conditionDurationInMs:s,iceGeneration:this._states.get(o?.id??"")?.iceGeneration??0,recommendationCount:this._establishmentRecommendations,iceState:o?.iceState,dtlsState:o?.dtlsState})}_recommendRestart(t){const n=this.peerConnection.parent;n.emit("ice-restart-recommended",{clientMonitor:n,peerConnectionMonitor:this.peerConnection,...t}),this.config.createEvent&&n.addEvent({type:e.ICE_RESTART_RECOMMENDED,payload:{...t}})}_notifyRestart(t,n,i){const s=this.peerConnection.parent;s.emit("ice-restart",{clientMonitor:s,peerConnectionMonitor:this.peerConnection,transportId:t.id,iceGeneration:n.iceGeneration,outcome:i}),this.config.createEvent&&s.addEvent({type:e.ICE_RESTART,payload:{peerConnectionId:this.peerConnection.peerConnectionId,transportId:t.id,iceGeneration:n.iceGeneration,outcome:i,iceState:t.iceState,evidence:"ice-username-fragment-changed",timestamp:Date.now()}})}_resolveAll(e,s){const o=this._states.get(e);void 0!==o?.disconnectRaisedAt&&this._resolveIssue(t,e,o.disconnectRaisedAt,s),void 0!==o?.failedRaisedAt&&this._resolveIssue(n,e,o.failedRaisedAt,s),void 0!==o?.stallRaisedAt&&this._resolveIssue(i,e,o.stallRaisedAt,s)}_resolveIssue(e,t,n,i){const s=this.peerConnection.parent,o=this._issueKey(e,t),d=s.activeIssues.get(o);d&&s.resolveIssue(o,{comment:i,payload:{...d.payload,durationInMs:void 0!==n?Date.now()-n:void 0},resolvedAt:Date.now()})}_issueKey(e,t){return`${e}-pc-${this.peerConnection.peerConnectionId}-transport-${t}`}}