import { MessageHandler } from '@wireapp/bot-api' ; import type { PayloadBundle } from '@wireapp/core/src/main/conversation/' ; import { CALL_TYPE , CONV_TYPE , ENV as AVS_ENV , getAvsInstance , LOG_LEVEL , REASON , Wcall , WcallClient , WcallParticipantChangedHandler , } from '@wireapp/avs' ; import axios from 'axios' ; import type { CallMessage } from '@wireapp/core/src/main/conversation/message/OtrMessage' ; import type { Call } from './Call' ; const wrtc = require ( 'wrtc' ) ; declare global { namespace NodeJS { interface Global { MediaStream : MediaStream ; MediaStreamTrack : MediaStreamTrack ; navigator : Navigator ; nonstandard : { i420ToRgba : unknown ; rgbaToI420 : unknown ; RTCAudioSink : unknown ; RTCAudioSource : unknown ; RTCVideoSink : unknown ; RTCVideoSource : unknown ; } ; RTCDataChannel : RTCDataChannel ; RTCDataChannelEvent : RTCDataChannelEvent ; RTCDtlsTransport : RTCDtlsTransport ; RTCIceCandidate : unknown ; RTCIceTransport : RTCIceTransport ; RTCPeerConnection : RTCPeerConnection ; RTCPeerConnectionIceEvent : RTCPeerConnectionIceEvent ; RTCRtpReceiver : RTCRtpReceiver ; RTCRtpSender : RTCRtpSender ; RTCRtpTransceiver : RTCRtpTransceiver ; RTCSctpTransport : RTCSctpTransport ; RTCSessionDescription : unknown ; } } } global . RTCPeerConnection = wrtc . RTCPeerConnection ; global . MediaStream = wrtc . MediaStream ; global . MediaStreamTrack = wrtc . MediaStreamTrack ; global . RTCRtpSender = wrtc . RTCRtpSender ; global . navigator = { ... global . navigator , mediaDevices : { ... ( global . navigator || { } ) . mediaDevices , getUserMedia : wrtc . getUserMedia , } , } ; interface InitOptions { callParticipantChangedHandler ? : WcallParticipantChangedHandler ; } interface SendMessageTarget { clients : WcallClient [ ] ; } export class AVSHandler extends MessageHandler { private wCall ? : Wcall ; private wUser ? : number ; private activeCalls : Record < string , Call > = { } ; private readonly mediaStream : MediaStream = new MediaStream ( ) ; async handleEvent ( payload ) { } async init ( options = { } ) { if ( ! this . account ) { throw new Error ( `No account found. Please login first.` ) ; } const { clientId , userId } = this . account ; const callingInstance = await getAvsInstance ( ) ; const wCall = this . configureCallingApi ( callingInstance ) ; const wUser = this . createWUser ( wCall , userId , clientId , options . callParticipantChangedHandler ) ; this . wUser = wUser ; this . wCall = wCall ; } onIncomingCallMessage = ( callMessage ) : void => { const callContent = callMessage . content ; if ( ! callMessage . fromClientId ) { throw new Error ( 'callMessage.fromClientId not found' ) ; } const avsResult = this . wCall ! . recvMsg ( this . wUser ! , callContent , callContent . length , new Date ( ) . getSeconds ( ) , new Date ( callMessage . timestamp ) . getSeconds ( ) , callMessage . conversation , callMessage . from , callMessage . fromClientId ) ; if ( avsResult !== 0 ) { throw new Error ( `recvMsg failed with code "${ avsResult }"` ) ; } this . answerCall ( callMessage ) ; } ; startCall = ( conversationId , conversationType , callType ) : void => { this . wCall ! . start ( this . wUser ! , conversationId , callType , conversationType , 0 ) ; } ; leaveCall = ( conversationId ) : void => { this . wCall ! . end ( this . wUser ! , conversationId ) ; } ; private readonly answerCall = ( call ) => { this . wCall ! . answer ( this . wUser ! , call . conversation , CALL_TYPE . NORMAL , 0 ) ; } ; private readonly configureCallingApi = ( wCall ) : Wcall => { wCall . setLogHandler ( ( level , message ) => { console . info ( `${ level }: ${ message }` ) ; } ) ; const avsEnv = AVS_ENV . DEFAULT ; wCall . init ( avsEnv ) ; wCall . setUserMediaHandler ( this . mediaHandler ) ; wCall . setMediaStreamHandler ( ( ) => { } ) ; setInterval ( ( ) => wCall . poll ( ) , 500 ) ; return wCall ; } ; private readonly mediaHandler = async ( ) : Promise < MediaStream > => { return this . mediaStream ; } ; private readonly createWUser = ( wCall , selfUserId , selfClientId , callParticipantChangedHandler ? ) : number => { const wUser = wCall . create ( selfUserId , selfClientId , ( ) => { } , this . onSendCallMessage , this . sendSFTRequest , this . onIncomingCall , ( ) => { } , ( ) => { } , ( ) => { } , this . callClosed , ( ) => { } , this . callConfigRequestHandler , ( ) => { } , ( ) => { } ) ; if ( callParticipantChangedHandler ) { wCall . setParticipantChangedHandler ( wUser , callParticipantChangedHandler ) ; } return wUser ; } ; private readonly callConfigRequestHandler = ( ) : number => { this . account ! . service ! . account . getCallConfig ( ) . then ( config => { this . wCall ! . configUpdate ( this . wUser ! , 0 , JSON . stringify ( config ) ) ; } ) . catch ( _ => { this . wCall ! . configUpdate ( this . wUser ! , 1 , '' ) ; } ) ; return 0 ; } ; private readonly sendSFTRequest = ( context , url , data , dataLength , _ ) : number => { void ( async ( ) => { try { const response = await axios . post ( url , data ) ; const { status , data : axiosData } = response ; const jsonData = JSON . stringify ( axiosData ) ; this . wCall ! . sftResp ( this . wUser ! , status , jsonData , jsonData . length , context ) ; } catch ( error ) { console . warn ( 'Failed to send SFT request' , error ) ; } } ) ( ) ; return 0 ; } ; private readonly onSendCallMessage = ( context , conversationId , userId , clientId , targets , unused , payload ) : number => { const userIds = [ ] ; if ( typeof targets === 'string' ) { const parsedTargets = JSON . parse ( targets ) ; for ( const target of parsedTargets . clients ) { userIds . push ( target . userid ) ; } } void ( async ( ) => { try { await this . sendCall ( conversationId , payload , userIds ) ; } catch ( error ) { console . error ( 'Failed to send targeted message' , error ) ; } } ) ( ) ; return 0 ; } ; private readonly onIncomingCall = ( conversationId , timestamp , userId , clientId , hasVideo , shouldRing ) => { const call = { clientId , conversationId , hasVideo , shouldRing , timestamp , userId , } ; this . storeCall ( call ) ; } ; private readonly callClosed = ( reason , conversationId ) : void => { this . removeCall ( conversationId ) ; } ; private readonly storeCall = ( call ) : void => { this . activeCalls [ call . conversationId ] = call ; } ; private readonly removeCall = ( conversationId ) : void => { delete this . activeCalls [ conversationId ] ; } ; }