2021-09-04 16:29:25 +00:00
import { useCallback , useEffect , useState } from "react" ;
2021-10-15 22:24:28 +00:00
import { NotFoundError , Routes , useMutation , useRouter } from "blitz" ;
2021-08-31 22:42:15 +00:00
import type { TwilioError } from "@twilio/voice-sdk" ;
import { Call , Device } from "@twilio/voice-sdk" ;
2021-09-04 16:29:25 +00:00
import getToken from "../mutations/get-token" ;
2021-10-15 22:24:28 +00:00
import appLogger from "integrations/logger" ;
2021-09-01 21:54:14 +00:00
const logger = appLogger . child ( { module : "use-device" } ) ;
2021-08-31 22:42:15 +00:00
export default function useDevice() {
2021-10-15 22:24:28 +00:00
const router = useRouter ( ) ;
2021-09-04 16:29:25 +00:00
const [ device , setDevice ] = useState < Device | null > ( null ) ;
const [ isDeviceReady , setIsDeviceReady ] = useState ( ( ) = > device ? . state === Device . State . Registered ) ;
2021-08-31 22:42:15 +00:00
const [ getTokenMutation ] = useMutation ( getToken ) ;
2021-09-01 21:54:14 +00:00
const refreshToken = useCallback ( async ( ) = > {
if ( ! device ) {
logger . error ( "Tried refreshing accessToken for an uninitialized device" ) ;
return ;
}
2021-10-15 22:24:28 +00:00
try {
const token = await getTokenMutation ( ) ;
device . updateToken ( token ) ;
} catch ( error ) {
if ( error instanceof NotFoundError ) {
throw router . push ( Routes . KeypadPage ( ) ) ;
}
throw error ;
}
} , [ device , getTokenMutation , router ] ) ;
2021-09-01 21:54:14 +00:00
useEffect ( ( ) = > {
2021-09-04 16:29:25 +00:00
const intervalId = setInterval ( ( ) = > {
return refreshToken ( ) ;
} , ( 3600 - 30 ) * 1000 ) ;
2021-09-01 21:54:14 +00:00
return ( ) = > clearInterval ( intervalId ) ;
2021-09-04 16:29:25 +00:00
} , [ refreshToken ] ) ;
2021-08-31 22:42:15 +00:00
useEffect ( ( ) = > {
( async ( ) = > {
2021-10-15 22:24:28 +00:00
try {
const token = await getTokenMutation ( ) ;
const device = new Device ( token , {
codecPreferences : [ Call . Codec . Opus , Call . Codec . PCMU ] ,
sounds : {
[ Device . SoundName . Disconnect ] : undefined , // TODO
} ,
} ) ;
device . register ( ) ;
setDevice ( device ) ;
} catch ( error ) {
if ( error instanceof NotFoundError ) {
throw router . push ( Routes . KeypadPage ( ) ) ;
}
throw error ;
}
2021-08-31 22:42:15 +00:00
} ) ( ) ;
2021-10-15 22:24:28 +00:00
} , [ getTokenMutation , setDevice , router ] ) ;
2021-08-31 22:42:15 +00:00
useEffect ( ( ) = > {
if ( ! device ) {
return ;
}
2021-09-04 16:29:25 +00:00
device . on ( "registered" , onDeviceRegistered ) ;
device . on ( "unregistered" , onDeviceUnregistered ) ;
2021-08-31 22:42:15 +00:00
device . on ( "error" , onDeviceError ) ;
device . on ( "incoming" , onDeviceIncoming ) ;
return ( ) = > {
2021-10-01 18:07:00 +00:00
// TODO: device.off is not a function
2021-09-04 16:29:25 +00:00
device . off ( "registered" , onDeviceRegistered ) ;
device . off ( "unregistered" , onDeviceUnregistered ) ;
2021-08-31 22:42:15 +00:00
device . off ( "error" , onDeviceError ) ;
device . off ( "incoming" , onDeviceIncoming ) ;
} ;
} , [ device ] ) ;
2021-09-01 21:54:14 +00:00
return {
device ,
isDeviceReady ,
refreshToken ,
} ;
2021-08-31 22:42:15 +00:00
2021-09-04 16:29:25 +00:00
function onDeviceRegistered() {
setIsDeviceReady ( true ) ;
}
function onDeviceUnregistered() {
setIsDeviceReady ( false ) ;
}
2021-08-31 22:42:15 +00:00
function onDeviceError ( error : TwilioError.TwilioError , call? : Call ) {
2021-09-04 16:29:25 +00:00
// we might have to change this if we instantiate the device on every page to receive calls
setDevice ( ( ) = > {
// hack to trigger the error boundary
throw error ;
} ) ;
2021-08-31 22:42:15 +00:00
}
function onDeviceIncoming ( call : Call ) {
// TODO show alert to accept/reject the incoming call /!\ it should persist between screens /!\ prevent making a new call when there is a pending incoming call
console . log ( "call" , call ) ;
console . log ( "Incoming connection from " + call . parameters . From ) ;
let archEnemyPhoneNumber = "+12093373517" ;
if ( call . parameters . From === archEnemyPhoneNumber ) {
call . reject ( ) ;
console . log ( "It's your nemesis. Rejected call." ) ;
} else {
// accept the incoming connection and start two-way audio
call . accept ( ) ;
}
}
}
2021-09-01 22:13:41 +00:00
let e = {
message :
"ConnectionError (53000): Raised whenever a signaling connection error occurs that is not covered by a more specific error code." ,
causes : [ ] ,
code : 53000 ,
description : "Signaling connection error" ,
explanation :
"Raised whenever a signaling connection error occurs that is not covered by a more specific error code." ,
name : "ConnectionError" ,
solutions : [ ] ,
} ;