From de4d4e63d558d8f2131c55ae10585d0d1f4d3a0f Mon Sep 17 00:00:00 2001 From: m5r Date: Fri, 13 Aug 2021 12:43:57 +0800 Subject: [PATCH] useMakeCall() hook --- app/phone-calls/hooks/use-make-call.ts | 119 ++++++++++++++++ .../pages/outgoing-call/[recipient].tsx | 130 +++++------------- 2 files changed, 150 insertions(+), 99 deletions(-) create mode 100644 app/phone-calls/hooks/use-make-call.ts diff --git a/app/phone-calls/hooks/use-make-call.ts b/app/phone-calls/hooks/use-make-call.ts new file mode 100644 index 0000000..a90d330 --- /dev/null +++ b/app/phone-calls/hooks/use-make-call.ts @@ -0,0 +1,119 @@ +import { useEffect, useState } from "react"; +import { useMutation } from "blitz"; +import { Call, Device, TwilioError } from "@twilio/voice-sdk"; + +import getToken from "../mutations/get-token"; + +export default function useMakeCall(recipient: string) { + const [outgoingConnection, setOutgoingConnection] = useState(null); + const [device, setDevice] = useState(null); + const [getTokenMutation] = useMutation(getToken); + const [state, setState] = useState("initial"); + + useEffect(() => { + (async () => { + 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); + })(); + }, [getTokenMutation]); + + useEffect(() => { + if (!device) { + return; + } + + device.on("error", onDeviceError); + device.on("registered", onDeviceRegistered); + device.on("incoming", onDeviceIncoming); + + return () => { + device.off("error", onDeviceError); + device.off("registered", onDeviceRegistered); + device.off("incoming", onDeviceIncoming); + }; + }, [device]); + + return { + makeCall, + sendDigits, + hangUp, + state, + }; + + async function makeCall() { + if (!device) { + console.error("no device initialized, can't make the call"); + return; + } + + if (state !== "ready") { + console.error("not a good time", state); + return; + } + + setState("calling"); + + const params = { To: recipient }; + const outgoingConnection = await device.connect({ params }); + setOutgoingConnection(outgoingConnection); + // @ts-ignore + window.ddd = outgoingConnection; + + outgoingConnection.on("cancel", () => setState("call_ended")); + outgoingConnection.on("disconnect", () => setState("call_ending")); + outgoingConnection.on("error", (error) => { + console.error("call error", error); + alert(error); + }); + } + + function sendDigits(digits: string) { + return outgoingConnection?.sendDigits(digits); + } + + function hangUp() { + setState("call_ending"); + + if (outgoingConnection) { + outgoingConnection.reject(); + } + + if (device) { + device.disconnectAll(); + // device.destroy(); + } + } + + function onDeviceError(error: TwilioError.TwilioError, call?: Call) { + console.error("device error", error); + alert(error); + } + + function onDeviceRegistered() { + setState("ready"); + } + + function onDeviceIncoming(call: Call) { + // TODO + 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(); + } + } +} + +type State = "initial" | "ready" | "calling" | "call_in_progress" | "call_ending" | "call_ended"; diff --git a/app/phone-calls/pages/outgoing-call/[recipient].tsx b/app/phone-calls/pages/outgoing-call/[recipient].tsx index 0df1e62..157c5c7 100644 --- a/app/phone-calls/pages/outgoing-call/[recipient].tsx +++ b/app/phone-calls/pages/outgoing-call/[recipient].tsx @@ -1,69 +1,25 @@ import type { BlitzPage } from "blitz"; -import { Routes, useMutation, useRouter } from "blitz"; -import { useEffect, useMemo, useState } from "react"; +import { Routes, useRouter } from "blitz"; +import { useEffect, useMemo } from "react"; import { atom, useAtom } from "jotai"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faPhoneAlt as faPhone } from "@fortawesome/pro-solid-svg-icons"; -import type { TwilioError } from "@twilio/voice-sdk"; -import { Device, Call } from "@twilio/voice-sdk"; -import getToken from "../../mutations/get-token"; import useRequireOnboarding from "../../../core/hooks/use-require-onboarding"; import Keypad from "../../components/keypad"; +import useMakeCall from "../../hooks/use-make-call"; const OutgoingCall: BlitzPage = () => { const router = useRouter(); const recipient = decodeURIComponent(router.params.recipient); - const [outgoingConnection, setOutgoingConnection] = useState(null); - const [device, setDevice] = useState(null); - const [getTokenMutation] = useMutation(getToken); - const [deviceState, setDeviceState] = useState("initial"); - - async function makeCall() { - const token = await getTokenMutation(); - console.log("token", token); - const device = new Device(token, { codecPreferences: [Call.Codec.Opus, Call.Codec.PCMU] }); - setDevice(device); - - const params = { To: recipient }; - const outgoingConnection = await device.connect({ params }); - // @ts-ignore - window.ddd = outgoingConnection; - - /*$("[id^=dial-]").click(function (event) { - console.log("send digit", event.target.innerText); - outgoingConnection.sendDigits(event.target.innerText); - })*/ - - outgoingConnection.on("ringing", () => { - console.log("Ringing..."); - }); - } + const call = useMakeCall(recipient); useEffect(() => { - // make call - }); - - useEffect(() => { - if (!device) { - return; + console.log("call.state", call.state); + if (call.state === "ready") { + call.makeCall(); } - - device.on("ready", onDeviceReady); - device.on("error", onDeviceError); - device.on("register", onDeviceRegistered); - device.on("unregister", onDeviceUnregistered); - device.on("incoming", onDeviceIncoming); - // device.audio?.on('deviceChange', updateAllDevices.bind(device)); - - return () => { - device.off("ready", onDeviceReady); - device.off("error", onDeviceError); - device.off("register", onDeviceRegistered); - device.off("unregister", onDeviceUnregistered); - device.off("incoming", onDeviceIncoming); - }; - }, [device]); + }, [call.state]); useRequireOnboarding(); const phoneNumber = useAtom(phoneNumberAtom)[0]; @@ -73,29 +29,19 @@ const OutgoingCall: BlitzPage = () => { onPress() { pressDigit(digit); - if (outgoingConnection) { - outgoingConnection.sendDigits(digit); - } + call.sendDigits(digit); }, }), - [outgoingConnection, pressDigit], + [call, pressDigit], ); const hangUp = useMemo( () => () => { - if (outgoingConnection) { - outgoingConnection.reject(); - } - - if (device) { - device.disconnectAll(); - device.destroy(); - device.unregister(); - } + call.hangUp(); // return router.replace(Routes.KeypadPage()); return router.push(Routes.KeypadPage()); }, - [device, outgoingConnection, router], + [call, router], ); return ( @@ -104,8 +50,9 @@ const OutgoingCall: BlitzPage = () => { {recipient} -
- {phoneNumber} +
+
{phoneNumber}
+
{translateState(call.state)}
@@ -118,9 +65,23 @@ const OutgoingCall: BlitzPage = () => {
); -}; -type DeviceState = "initial" | ""; + function translateState(state: typeof call.state): string { + switch (state) { + case "initial": + case "ready": + return "Connecting..."; + case "calling": + return "Calling..."; + case "call_in_progress": + return ""; // TODO display time elapsed + case "call_ending": + return "Call ending..."; + case "call_ended": + return "Call ended"; + } + } +}; const phoneNumberAtom = atom(""); const pressDigitAtom = atom(null, (get, set, digit: string) => { @@ -133,33 +94,4 @@ const pressDigitAtom = atom(null, (get, set, digit: string) => { OutgoingCall.authenticate = { redirectTo: Routes.SignIn() }; -function onDeviceReady(device: Device) { - console.log("device", device); -} - -function onDeviceError(error: TwilioError.TwilioError, call?: Call) { - console.log("error", error); -} - -function onDeviceRegistered(device: Device) { - console.log("ready to make calls"); - console.log("device", device); -} - -function onDeviceUnregistered() {} - -function onDeviceIncoming(call: 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(); - } -} - export default OutgoingCall;