diff --git a/packages/js/README.md b/packages/js/README.md index 1e3b718b..66115871 100644 --- a/packages/js/README.md +++ b/packages/js/README.md @@ -163,15 +163,16 @@ const call = client.newCall({ // Destination is required and can be a phone number or SIP URI destinationNumber: '18004377950', callerNumber: '‬155531234567', + debug: true, + debugOutput: 'socket' // Possible values are 'socket' | 'file' }); -// Start the gathering of data -call.startDebugger(); - -// Stop the gathering of data -call.stopDebugger(); +// The debug dump is set to be sent to telnyx by default, if you want to save the debug data to disk +// You can change the debugOutput option to 'file' ``` + + --- ## Examples diff --git a/packages/js/src/Modules/Verto/messages/WebRTCStatsMessage.ts b/packages/js/src/Modules/Verto/messages/WebRTCStatsMessage.ts new file mode 100644 index 00000000..05d1ba83 --- /dev/null +++ b/packages/js/src/Modules/Verto/messages/WebRTCStatsMessage.ts @@ -0,0 +1,14 @@ +import BaseMessage from './BaseMessage'; +import { v4 } from 'uuid'; +export class WebRTCStatsMessage extends BaseMessage { + constructor(data: any) { + super(); + const reportId = v4(); + this.buildRequest({ + type: 'debug_final_report', + debug_final_report: btoa(JSON.stringify(data)), + debug_report_id: reportId, + }); + console.log(`WebRTC Debug Report generated: ${reportId}`); + } +} diff --git a/packages/js/src/Modules/Verto/util/interfaces.ts b/packages/js/src/Modules/Verto/util/interfaces.ts index da981ee8..510de9ad 100644 --- a/packages/js/src/Modules/Verto/util/interfaces.ts +++ b/packages/js/src/Modules/Verto/util/interfaces.ts @@ -15,11 +15,14 @@ export interface IVertoOptions { env?: Environment; iceServers?: RTCIceServer[]; /** - * autoReconnect: Determine if the SDK has to re-connect automatically when detecting a gateway connection failure. + * autoReconnect: Determine if the SDK has to re-connect automatically when detecting a gateway connection failure. * This is set to`true` as default * @type {boolean} */ autoReconnect?: boolean; + + debug?: boolean; + debugOutput?: 'socket' | 'file'; } export interface SubscribeParams { channels?: string[]; diff --git a/packages/js/src/Modules/Verto/webrtc/BaseCall.ts b/packages/js/src/Modules/Verto/webrtc/BaseCall.ts index b098f0e2..63b1303d 100644 --- a/packages/js/src/Modules/Verto/webrtc/BaseCall.ts +++ b/packages/js/src/Modules/Verto/webrtc/BaseCall.ts @@ -51,6 +51,7 @@ import { MCULayoutEventHandler } from './LayoutHandler'; import Call from './Call'; import pkg from '../../../../package.json'; import { WebRTCStats } from '@peermetrics/webrtc-stats'; +import { WebRTCStatsMessage } from '../messages/WebRTCStatsMessage'; const SDK_VERSION = pkg.version; /** @@ -296,7 +297,6 @@ export default abstract class BaseCall implements IWebRTCCall { this.direction = Direction.Outbound; this.peer = new Peer(PeerType.Offer, this.options); await this.peer.iceGatheringComplete.promise; - console.log('icegatheringcompleted') this._onIceSdp(this.peer.instance.localDescription); } @@ -374,12 +374,30 @@ export default abstract class BaseCall implements IWebRTCCall { this.setState(State.Hangup); const _close = () => { - this.peer ? this.peer.instance.close() : null; - this.setState(State.Destroy); + const debugData = this.peer?.close(); + if (!debugData) { + return this.setState(State.Destroy); + } + if (this.options.debugOutput === 'socket') { + this._execute(new WebRTCStatsMessage(debugData)); + } + if (this.options.debugOutput === 'file') { + // Save to file + const blob = new Blob([JSON.stringify(debugData)], { + type: 'application/json', + }); + const downloadUrl = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = downloadUrl; + link.download = `call_${this.options.id}.json`; + link.click(); + URL.revokeObjectURL(downloadUrl); + } + return this.setState(State.Destroy); }; this.stopRingtone(); - this.stopDebugger(); + this.stopDebugger(); if (execute) { const bye = new Bye({ sessid: this.session.sessionid, @@ -1470,7 +1488,6 @@ export default abstract class BaseCall implements IWebRTCCall { this.hangup(); }); console.timeEnd(TIME_CALL_INVITE); - } private _onIce(event: RTCPeerConnectionIceEvent) { diff --git a/packages/js/src/Modules/Verto/webrtc/Peer.ts b/packages/js/src/Modules/Verto/webrtc/Peer.ts index b113636d..9de5ec03 100644 --- a/packages/js/src/Modules/Verto/webrtc/Peer.ts +++ b/packages/js/src/Modules/Verto/webrtc/Peer.ts @@ -16,14 +16,10 @@ import { RTCPeerConnection, streamIsValid, } from '../util/webrtc'; -import { - DeferredPromise, - deferredPromise, - isFunction, -} from '../util/helpers'; +import { DeferredPromise, deferredPromise, isFunction } from '../util/helpers'; import { IVertoCallOptions } from './interfaces'; import { trigger } from '../services/Handler'; - +import { WebRTCStats } from '@peermetrics/webrtc-stats'; /** * @ignore Hide in docs output */ @@ -31,7 +27,7 @@ export default class Peer { public instance: RTCPeerConnection; public iceGatheringComplete: DeferredPromise; public onSdpReadyTwice: Function = null; - + private _webrtcStats: WebRTCStats; private _constraints: { offerToReceiveAudio: boolean; offerToReceiveVideo: boolean; @@ -56,6 +52,18 @@ export default class Peer { this.createPeerConnection = this.createPeerConnection.bind(this); this.iceGatheringComplete = deferredPromise({ debounceTime: 100 }); + if (this.options.debug) { + this._webrtcStats = new WebRTCStats({ + getStatsInterval: 1000, + rawStats: false, + statsObject: false, + filteredStats: false, + remote: true, + wrapGetUserMedia: true, + debug: false, + logLevel: 'warn', + }); + } this._init(); } @@ -144,7 +152,13 @@ export default class Peer { }; private async createPeerConnection() { this.instance = RTCPeerConnection(this._config()); - + if (this.options.debug) { + this._webrtcStats?.addConnection({ + pc: this.instance, + peerId: this.options.id, + connectionId: this.options.id, + }); + } this.instance.onsignalingstatechange = this.handleSignalingStateChangeEvent; this.instance.onnegotiationneeded = this.handleNegotiationNeededEvent; this.instance.ontrack = this.handleTrackEvent; @@ -233,6 +247,7 @@ export default class Peer { } else { this.startNegotiation(); } + this._logTransceivers(); } @@ -386,4 +401,14 @@ export default class Peer { logger.info('RTC config', config); return config; } + + public close() { + let data = null; + if (this._webrtcStats) { + data = this._webrtcStats.getTimeline('stats'); + this._webrtcStats.destroy(); + } + this.instance.close(); + return data; + } } diff --git a/packages/js/src/Modules/Verto/webrtc/VertoHandler.ts b/packages/js/src/Modules/Verto/webrtc/VertoHandler.ts index e5ab82d3..0bc889eb 100644 --- a/packages/js/src/Modules/Verto/webrtc/VertoHandler.ts +++ b/packages/js/src/Modules/Verto/webrtc/VertoHandler.ts @@ -76,6 +76,8 @@ class VertoHandler { callerNumber: params.callee_id_number, attach, mediaSettings: params.mediaSettings, + debug: session.options.debug ?? false, + debugOutput: session.options.debugOutput ?? 'socket', }; if (params.telnyx_call_control_id) { diff --git a/packages/js/src/Modules/Verto/webrtc/constants.ts b/packages/js/src/Modules/Verto/webrtc/constants.ts index b88a3456..c88ee41a 100644 --- a/packages/js/src/Modules/Verto/webrtc/constants.ts +++ b/packages/js/src/Modules/Verto/webrtc/constants.ts @@ -50,6 +50,8 @@ export const DEFAULT_CALL_OPTIONS: IVertoCallOptions = { audio: true, video: false, useStereo: false, + debug: false, + debugOutput: 'socket', attach: false, screenShare: false, userVariables: {}, diff --git a/packages/js/src/Modules/Verto/webrtc/interfaces.ts b/packages/js/src/Modules/Verto/webrtc/interfaces.ts index a464cf16..de6cd841 100644 --- a/packages/js/src/Modules/Verto/webrtc/interfaces.ts +++ b/packages/js/src/Modules/Verto/webrtc/interfaces.ts @@ -45,6 +45,8 @@ export interface IVertoCallOptions { negotiateVideo?: boolean; mediaSettings?: IMediaSettings; customHeaders?: Array<{ name: string; value: string }>; + debug?: boolean; + debugOutput?: 'socket' | 'file'; } export interface IStatsBinding {