diff --git a/public/index.html b/public/index.html index 28ce49e..9eb2137 100644 --- a/public/index.html +++ b/public/index.html @@ -4,12 +4,6 @@ - { this.emit(SipConstants.SESSION_ANSWERED, { status: SipConstants.SESSION_ANSWERED, - callSid: response.hasHeader("X-Call-Sid") + callSid: response?.hasHeader("X-Call-Sid") ? response.getHeader("X-Call-Sid") : null, }); diff --git a/src/storage/index.ts b/src/storage/index.ts index 2345993..c287e97 100644 --- a/src/storage/index.ts +++ b/src/storage/index.ts @@ -42,34 +42,24 @@ export const saveSettings = (settings: AppSettings) => { if (str) { const parsed = JSON.parse(str); - // const data: IAppSettings[] = parsed.map((el: saveSettingFormat) => { - // return { - // active: el.active, - // decoded: JSON.parse( - // Buffer.from(el.encoded, "base64").toString("utf-8") - // ), - // id: el.id, - // }; - // }); - - // const alreadyExists = data.filter( - // (el) => - // el.decoded.sipDomain === settings.sipDomain && - // el.decoded.sipServerAddress === settings.sipServerAddress - // ); - // if (!!alreadyExists.length) return; - - localStorage.setItem( - SETTINGS_KEY, - JSON.stringify([ - ...parsed, - { - encoded, - active: false, - id: parsed.length + 1, - }, - ]) - ); + if (parsed.length < 1) { + localStorage.setItem( + SETTINGS_KEY, + JSON.stringify([{ id: 1, encoded, active: true }]) + ); + } else { + localStorage.setItem( + SETTINGS_KEY, + JSON.stringify([ + ...parsed, + { + encoded, + active: false, + id: parsed.length + 1, + }, + ]) + ); + } } else { localStorage.setItem( SETTINGS_KEY, @@ -153,10 +143,7 @@ export const getSettings = (): IAppSettings[] => { }; }); return decoded; - // const planText = Buffer.from(str, "base64").toString("utf-8"); - // return JSON.parse(planText) as AppSettings; } - // return {} as AppSettings; return [] as IAppSettings[]; }; @@ -175,10 +162,7 @@ export const getActiveSettings = (): IAppSettings => { }; }); return decoded.find((el) => el.active) as IAppSettings; - // const planText = Buffer.from(str, "base64").toString("utf-8"); - // return JSON.parse(planText) as AppSettings; } - // return {} as AppSettings; return {} as IAppSettings; }; @@ -227,12 +211,8 @@ export const getAdvancedSettings = (): IAdvancedAppSettings[] => { }; }); return decoded; - // const planText = Buffer.from(str, "base64").toString("utf-8"); - // return JSON.parse(planText) as AppSettings; } - // return {} as AppSettings; return [] as IAdvancedAppSettings[]; - // return [] as IAppSettings[]; }; export const getActiveAdvancedSettings = (): IAdvancedAppSettings => { const str = localStorage.getItem(ADVANCED_SETTINGS_KET); @@ -250,16 +230,11 @@ export const getActiveAdvancedSettings = (): IAdvancedAppSettings => { }; }); return decoded.find((el) => el.active) as IAdvancedAppSettings; - // const planText = Buffer.from(str, "base64").toString("utf-8"); - // return JSON.parse(planText) as AppSettings; } - // return {} as AppSettings; return {} as IAdvancedAppSettings; - // return [] as IAppSettings[]; }; // Call History - const historyKey = "History"; const MAX_HISTORY_COUNT = 20; export const saveCallHistory = (username: string, call: CallHistory) => { diff --git a/src/styles.scss b/src/styles.scss index 8098917..3e092df 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -2,30 +2,10 @@ font-family: "Source Sans"; src: url("../public/fonts/SourceSans3-Regular.ttf") format("truetype"); } -// @font-face { -// font-family: 'FontName'; -// font-style: normal; -// font-weight: 300; -// src: local('FontName Light'), local('FontName-Light'), url(https://fonts.gstatic.com/some-url.woff2) format('woff2'); -// } - -// @font-face { -// font-family: 'FontName'; -// font-style: normal; -// font-weight: 700; -// src: local('FontName Bold'), local('FontName-Bold'), url(https://fonts.gstatic.com/some-url.woff2) format('woff2'); -// } -// * { -// font-family: "Source Sans 3", sans-serif; -// } -// body { -// font-family: "Source Sans 3", sans-serif; -// } .container { width: 280px; height: 480px; - // font-family: "Source Sans 3", sans-serif; margin: 0; padding: 0; } diff --git a/src/window/app.tsx b/src/window/app.tsx index 03a2f21..8f0e3a6 100644 --- a/src/window/app.tsx +++ b/src/window/app.tsx @@ -16,7 +16,6 @@ import { getActiveSettings, getCallHistories, getSettings } from "src/storage"; import CallHistories from "./history"; import { CallHistory, IAppSettings, SipClientStatus } from "src/common/types"; import Footer from "./footer/footer"; -import { SipUA } from "src/lib"; export const WindowApp = () => { const [sipDomain, setSipDomain] = useState(""); @@ -33,9 +32,24 @@ export const WindowApp = () => { const [advancedSettings, setAdvancedSettings] = useState( null ); - const sipUA = useRef(null); const [isSwitchingUserStatus, setIsSwitchingUserStatus] = useState(false); const [isOnline, setIsOnline] = useState(false); + const phoneSipAschildRef = useRef<{ + updateGoOffline: (x: string) => void; + } | null>(null); + + const handleGoOffline = (s: SipClientStatus) => { + if (phoneSipAschildRef.current) { + if (s === status) { + return; + } + if (s === "unregistered") { + phoneSipAschildRef.current.updateGoOffline("stop"); + } else { + phoneSipAschildRef.current.updateGoOffline("start"); + } + } + }; const loadSettings = () => { const settings = getSettings(); @@ -56,6 +70,7 @@ export const WindowApp = () => { title: "Dialer", content: ( { advancedSettings={advancedSettings} allSettings={allSettings} reload={loadSettings} - sipUA={sipUA} setIsSwitchingUserStatus={setIsSwitchingUserStatus} setIsOnline={setIsOnline} /> @@ -144,7 +158,7 @@ export const WindowApp = () => { setIsSwitchingUserStatus={setIsSwitchingUserStatus} isOnline={isOnline} setIsOnline={setIsOnline} - sipUA={sipUA} + onHandleGoOffline={handleGoOffline} /> ); diff --git a/src/window/footer/footer.tsx b/src/window/footer/footer.tsx index 520662f..bf3ab34 100644 --- a/src/window/footer/footer.tsx +++ b/src/window/footer/footer.tsx @@ -1,9 +1,7 @@ -import { HStack, Image, Text, useToast } from "@chakra-ui/react"; +import { HStack, Image, Text } from "@chakra-ui/react"; import jambonz from "src/imgs/jambonz.svg"; -import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react"; +import { Dispatch, SetStateAction, useEffect, useState } from "react"; import { SipClientStatus } from "src/common/types"; -import { SipConstants, SipUA } from "src/lib"; -import { DEFAULT_TOAST_DURATION } from "src/common/constants"; import JambonzSwitch from "src/components/switch"; import "./styles.scss"; @@ -19,7 +17,7 @@ function Footer({ setIsSwitchingUserStatus, isOnline, setIsOnline, - sipUA, + onHandleGoOffline, }: { status: string; setStatus: Dispatch>; @@ -32,135 +30,24 @@ function Footer({ setIsSwitchingUserStatus: React.Dispatch>; isOnline: boolean; setIsOnline: React.Dispatch>; - sipUA: React.MutableRefObject; + onHandleGoOffline: (s: SipClientStatus) => void; }) { const [isConfigured, setIsConfigured] = useState(false); - // const sipUA = useRef(null); - const sipUsernameRef = useRef(""); - const sipPasswordRef = useRef(""); - const sipServerAddressRef = useRef(""); - const sipDomainRef = useRef(""); - const sipDisplayNameRef = useRef(""); - const unregisteredReasonRef = useRef(""); - const isRestartRef = useRef(false); - - const toast = useToast(); - useEffect(() => { if (status === "registered" || status === "disconnected") { setIsSwitchingUserStatus(false); setIsOnline(status === "registered"); } - }, [status]); + }, [status, setIsSwitchingUserStatus, setIsOnline]); useEffect(() => { - sipDomainRef.current = sipDomain; - sipUsernameRef.current = sipUsername; - sipPasswordRef.current = sipPassword; - sipServerAddressRef.current = sipServerAddress; - sipDisplayNameRef.current = sipDisplayName; if (sipDomain && sipUsername && sipPassword && sipServerAddress) { - if (sipUA.current) { - if (sipUA.current.isConnected()) { - clientGoOffline(); - isRestartRef.current = true; - } else { - createSipClient(); - } - } else { - createSipClient(); - } setIsConfigured(true); } else { setIsConfigured(false); - clientGoOffline(); } - }, [sipDomain, sipUsername, sipPassword, sipServerAddress, sipDisplayName]); - - const clientGoOffline = () => { - if (sipUA.current) { - sipUA.current.stop(); - sipUA.current = null; - } - }; - - const handleGoOffline = (s: SipClientStatus) => { - if (s === status) { - return; - } - if (s === "unregistered") { - if (sipUA.current) { - sipUA.current.stop(); - } - } else { - if (sipUA.current) { - sipUA.current.start(); - } - } - }; - - const createSipClient = () => { - setIsSwitchingUserStatus(true); - const client = { - username: `${sipUsernameRef.current}@${sipDomainRef.current}`, - password: sipPasswordRef.current, - name: sipDisplayNameRef.current ?? sipUsernameRef.current, - }; - - const settings = { - pcConfig: { - iceServers: [{ urls: ["stun:stun.l.google.com:19302"] }], - }, - wsUri: sipServerAddressRef.current, - register: true, - }; - - const sipClient = new SipUA(client, settings); - - // UA Status - sipClient.on(SipConstants.UA_REGISTERED, (args) => { - setStatus("registered"); - }); - sipClient.on(SipConstants.UA_UNREGISTERED, (args) => { - setStatus("unregistered"); - if (sipUA.current) { - sipUA.current.stop(); - } - unregisteredReasonRef.current = `User is not registered${ - args.cause ? `, ${args.cause}` : "" - }`; - }); - - sipClient.on(SipConstants.UA_DISCONNECTED, (args) => { - if (unregisteredReasonRef.current) { - toast({ - title: unregisteredReasonRef.current, - status: "warning", - duration: DEFAULT_TOAST_DURATION, - isClosable: true, - }); - unregisteredReasonRef.current = ""; - } - setStatus("disconnected"); - if (isRestartRef.current) { - createSipClient(); - isRestartRef.current = false; - } - - if (args.error) { - toast({ - title: `Cannot connect to ${sipServerAddress}, ${args.reason}`, - status: "warning", - duration: DEFAULT_TOAST_DURATION, - isClosable: true, - }); - } - }); - - sipClient.start(); - sipUA.current = sipClient; - }; + }, [sipDomain, sipUsername, sipPassword, sipServerAddress]); return ( { setIsSwitchingUserStatus(true); - handleGoOffline(v ? "registered" : "unregistered"); + onHandleGoOffline(v ? "registered" : "unregistered"); }} /> You are {isOnline ? "online" : "offline"} diff --git a/src/window/phone/index.tsx b/src/window/phone/index.tsx index ac7304f..66e404b 100644 --- a/src/window/phone/index.tsx +++ b/src/window/phone/index.tsx @@ -13,7 +13,15 @@ import { VStack, useToast, } from "@chakra-ui/react"; -import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react"; +import { + Dispatch, + forwardRef, + SetStateAction, + useEffect, + useImperativeHandle, + useRef, + useState, +} from "react"; import { IAppSettings, SipCallDirection, @@ -79,7 +87,6 @@ type PhoneProbs = { stat: [string, Dispatch>]; allSettings: IAppSettings[]; reload: () => void; - sipUA: React.MutableRefObject; setIsSwitchingUserStatus: React.Dispatch>; setIsOnline: React.Dispatch>; }; @@ -91,793 +98,805 @@ enum PAGE_VIEW { JOIN_CONFERENCE, } -// add some basic details to advanced to match them, make basic compulsory to fill advanced. - -export const Phone = ({ - sipDomain, - sipServerAddress, - sipUsername, - sipPassword, - sipDisplayName, - stat: [status, setStatus], - calledNumber: [calledANumber, setCalledANumber], - calledName: [calledAName, setCalledAName], - advancedSettings, - allSettings, - reload, - // sipUA, - setIsSwitchingUserStatus, - setIsOnline, -}: PhoneProbs) => { - const [inputNumber, setInputNumber] = useState(""); - const [appName, setAppName] = useState(""); - // const [status, setStatus] = useState("stop"); - const [isConfigured, setIsConfigured] = useState(false); - const [callStatus, setCallStatus] = useState(SipConstants.SESSION_ENDED); - const [sessionDirection, setSessionDirection] = - useState(""); - const [seconds, setSeconds] = useState(0); - const [isCallButtonLoading, setIsCallButtonLoading] = useState(false); - const [isAdvanceMode, setIsAdvancedMode] = useState(false); - // const [isSwitchingUserStatus, setIsSwitchingUserStatus] = useState(true); - // const [isOnline, setIsOnline] = useState(false); - const [pageView, setPageView] = useState(PAGE_VIEW.DIAL_PAD); - const [registeredUser, setRegisteredUser] = useState>( +export const Phone = forwardRef( + ( { + sipDomain, + sipServerAddress, + sipUsername, + sipPassword, + sipDisplayName, + stat: [status, setStatus], + calledNumber: [calledANumber, setCalledANumber], + calledName: [calledAName, setCalledAName], + advancedSettings, + allSettings, + reload, + setIsSwitchingUserStatus, + setIsOnline, + }: PhoneProbs, + ref: any + ) => { + const [inputNumber, setInputNumber] = useState(""); + const [appName, setAppName] = useState(""); + const [isConfigured, setIsConfigured] = useState(false); + const [callStatus, setCallStatus] = useState(SipConstants.SESSION_ENDED); + const [sessionDirection, setSessionDirection] = + useState(""); + const [seconds, setSeconds] = useState(0); + const [isCallButtonLoading, setIsCallButtonLoading] = useState(false); + const [isAdvanceMode, setIsAdvancedMode] = useState(false); + const [pageView, setPageView] = useState(PAGE_VIEW.DIAL_PAD); + const [registeredUser, setRegisteredUser] = useState< + Partial + >({ allow_direct_app_calling: false, allow_direct_queue_calling: false, allow_direct_user_calling: false, - } - ); - const [selectedConference, setSelectedConference] = useState(""); - const [callSid, setCallSid] = useState(""); - const [showConference, setShowConference] = useState(false); + }); + const [selectedConference, setSelectedConference] = useState(""); + const [callSid, setCallSid] = useState(""); + const [showConference, setShowConference] = useState(false); - const [showAccounts, setShowAccounts] = useState(false); + const [showAccounts, setShowAccounts] = useState(false); - const inputNumberRef = useRef(inputNumber); - const sessionDirectionRef = useRef(sessionDirection); - const sipUA = useRef(null); - const timerRef = useRef(null); - const isRestartRef = useRef(false); - const sipDomainRef = useRef(""); - const sipUsernameRef = useRef(""); - const sipPasswordRef = useRef(""); - const sipServerAddressRef = useRef(""); - const sipDisplayNameRef = useRef(""); - const unregisteredReasonRef = useRef(""); - const isInputNumberFocusRef = useRef(false); - const secondsRef = useRef(seconds); - const accountsCardRef = useRef(null); + const inputNumberRef = useRef(inputNumber); + const sessionDirectionRef = useRef(sessionDirection); + const sipUA = useRef(null); + const timerRef = useRef(null); + const isRestartRef = useRef(false); + const sipDomainRef = useRef(""); + const sipUsernameRef = useRef(""); + const sipPasswordRef = useRef(""); + const sipServerAddressRef = useRef(""); + const sipDisplayNameRef = useRef(""); + const unregisteredReasonRef = useRef(""); + const isInputNumberFocusRef = useRef(false); + const secondsRef = useRef(seconds); + const accountsCardRef = useRef(null); - const toast = useToast(); + const toast = useToast(); - useEffect(() => { - sipDomainRef.current = sipDomain; - sipUsernameRef.current = sipUsername; - sipPasswordRef.current = sipPassword; - sipServerAddressRef.current = sipServerAddress; - sipDisplayNameRef.current = sipDisplayName; - if (sipDomain && sipUsername && sipPassword && sipServerAddress) { - if (sipUA.current) { - if (sipUA.current.isConnected()) { - clientGoOffline(); - isRestartRef.current = true; + useImperativeHandle(ref, () => ({ + updateGoOffline(newState: string) { + if (newState === "start") { + sipUA.current?.start(); + } else { + sipUA.current?.stop(); + } + }, + })); + + useEffect(() => { + sipDomainRef.current = sipDomain; + sipUsernameRef.current = sipUsername; + sipPasswordRef.current = sipPassword; + sipServerAddressRef.current = sipServerAddress; + sipDisplayNameRef.current = sipDisplayName; + if (sipDomain && sipUsername && sipPassword && sipServerAddress) { + if (sipUA.current) { + if (sipUA.current.isConnected()) { + clientGoOffline(); + isRestartRef.current = true; + } else { + createSipClient(); + } } else { createSipClient(); } + setIsConfigured(true); } else { - createSipClient(); + setIsConfigured(false); + clientGoOffline(); } - setIsConfigured(true); - } else { - setIsConfigured(false); - clientGoOffline(); - } - fetchRegisterUser(); - getConferences() - ?.then(() => { - setShowConference(true); - }) - .catch(() => { - setShowConference(false); - }); - }, [sipDomain, sipUsername, sipPassword, sipServerAddress, sipDisplayName]); - - useEffect(() => { - setIsAdvancedMode(!!advancedSettings?.decoded?.accountSid); - fetchRegisterUser(); - }, [advancedSettings]); - - useEffect(() => { - inputNumberRef.current = inputNumber; - sessionDirectionRef.current = sessionDirection; - secondsRef.current = seconds; - }, [inputNumber, seconds, sessionDirection]); - - useEffect(() => { - if (isSipClientIdle(callStatus) && isCallButtonLoading) { - setIsCallButtonLoading(false); - } - switch (callStatus) { - case SipConstants.SESSION_RINGING: - if (sessionDirection === "incoming") { - setPageView(PAGE_VIEW.INCOMING_CALL); - } else { - setPageView(PAGE_VIEW.OUTGOING_CALL); - } - break; - case SipConstants.SESSION_ANSWERED: - if (!!selectedConference) { - setPageView(PAGE_VIEW.JOIN_CONFERENCE); - } else { - setPageView(PAGE_VIEW.DIAL_PAD); - } - break; - case SipConstants.SESSION_ENDED: - case SipConstants.SESSION_FAILED: - setSelectedConference(""); - setPageView(PAGE_VIEW.DIAL_PAD); - break; - } - }, [callStatus]); - - useEffect(() => { - if (calledANumber) { - if ( - !( - calledANumber.startsWith("app-") || - calledANumber.startsWith("queue-") || - calledANumber.startsWith("conference-") - ) - ) { - setInputNumber(calledANumber); - } - - setAppName(calledAName); - makeOutboundCall(calledANumber, calledAName); - setCalledANumber(""); - setCalledAName(""); - } - }, [calledANumber]); - - useEffect(() => { - if (status === "registered" || status === "disconnected") { - setIsSwitchingUserStatus(false); - setIsOnline(status === "registered"); - } - }, [status]); - - useEffect(() => { - const timer = setInterval(() => { fetchRegisterUser(); - }, 10000); - return () => { - clearInterval(timer); - }; - }, []); - - useEffect(() => { - if (showAccounts) { - document.addEventListener("mousedown", handleClickOutside); - } else { - document.removeEventListener("mousedown", handleClickOutside); - } - - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, [showAccounts]); - - const fetchRegisterUser = () => { - getSelfRegisteredUser(sipUsernameRef.current) - .then(({ json }) => { - setRegisteredUser(json); - }) - .catch((err) => { - setRegisteredUser({ - allow_direct_app_calling: false, - allow_direct_queue_calling: false, - allow_direct_user_calling: false, + getConferences() + ?.then(() => { + setShowConference(true); + }) + .catch(() => { + setShowConference(false); }); + }, [sipDomain, sipUsername, sipPassword, sipServerAddress, sipDisplayName]); + + useEffect(() => { + setIsAdvancedMode(!!advancedSettings?.decoded?.accountSid); + fetchRegisterUser(); + }, [advancedSettings]); + + useEffect(() => { + inputNumberRef.current = inputNumber; + sessionDirectionRef.current = sessionDirection; + secondsRef.current = seconds; + }, [inputNumber, seconds, sessionDirection]); + + useEffect(() => { + if (isSipClientIdle(callStatus) && isCallButtonLoading) { + setIsCallButtonLoading(false); + } + switch (callStatus) { + case SipConstants.SESSION_RINGING: + if (sessionDirection === "incoming") { + setPageView(PAGE_VIEW.INCOMING_CALL); + } else { + setPageView(PAGE_VIEW.OUTGOING_CALL); + } + break; + case SipConstants.SESSION_ANSWERED: + if (!!selectedConference) { + setPageView(PAGE_VIEW.JOIN_CONFERENCE); + } else { + setPageView(PAGE_VIEW.DIAL_PAD); + } + break; + case SipConstants.SESSION_ENDED: + case SipConstants.SESSION_FAILED: + setSelectedConference(""); + setPageView(PAGE_VIEW.DIAL_PAD); + break; + } + }, [callStatus]); + + useEffect(() => { + if (calledANumber) { + if ( + !( + calledANumber.startsWith("app-") || + calledANumber.startsWith("queue-") || + calledANumber.startsWith("conference-") + ) + ) { + setInputNumber(calledANumber); + } + + setAppName(calledAName); + makeOutboundCall(calledANumber, calledAName); + setCalledANumber(""); + setCalledAName(""); + } + }, [calledANumber]); + + useEffect(() => { + if (status === "registered" || status === "disconnected") { + setIsSwitchingUserStatus(false); + setIsOnline(status === "registered"); + } + }, [status]); + + useEffect(() => { + const timer = setInterval(() => { + fetchRegisterUser(); + }, 10000); + return () => { + clearInterval(timer); + }; + }, []); + + useEffect(() => { + if (showAccounts) { + document.addEventListener("mousedown", handleClickOutside); + } else { + document.removeEventListener("mousedown", handleClickOutside); + } + + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [showAccounts]); + + const fetchRegisterUser = () => { + getSelfRegisteredUser(sipUsernameRef.current) + .then(({ json }) => { + setRegisteredUser(json); + }) + .catch((err) => { + setRegisteredUser({ + allow_direct_app_calling: false, + allow_direct_queue_calling: false, + allow_direct_user_calling: false, + }); + }); + }; + + const startCallDurationCounter = () => { + stopCallDurationCounter(); + timerRef.current = setInterval(() => { + setSeconds((seconds) => seconds + 1); + }, 1000); + }; + + const stopCallDurationCounter = () => { + if (timerRef.current) { + clearInterval(timerRef.current); + timerRef.current = null; + setSeconds(0); + } + }; + + const createSipClient = () => { + setIsSwitchingUserStatus(true); + const client = { + username: `${sipUsernameRef.current}@${sipDomainRef.current}`, + password: sipPasswordRef.current, + name: sipDisplayNameRef.current ?? sipUsernameRef.current, + }; + + const settings = { + pcConfig: { + iceServers: [{ urls: ["stun:stun.l.google.com:19302"] }], + }, + wsUri: sipServerAddressRef.current, + register: true, + }; + + const sipClient = new SipUA(client, settings); + + // UA Status + sipClient.on(SipConstants.UA_REGISTERED, (args) => { + setStatus("registered"); + }); + sipClient.on(SipConstants.UA_UNREGISTERED, (args) => { + setStatus("unregistered"); + if (sipUA.current) { + sipUA.current.stop(); + } + unregisteredReasonRef.current = `User is not registered${ + args.cause ? `, ${args.cause}` : "" + }`; }); - }; - const startCallDurationCounter = () => { - stopCallDurationCounter(); - timerRef.current = setInterval(() => { - setSeconds((seconds) => seconds + 1); - }, 1000); - }; + sipClient.on(SipConstants.UA_DISCONNECTED, (args) => { + if (unregisteredReasonRef.current) { + toast({ + title: unregisteredReasonRef.current, + status: "warning", + duration: DEFAULT_TOAST_DURATION, + isClosable: true, + }); + unregisteredReasonRef.current = ""; + } + setStatus("disconnected"); + if (isRestartRef.current) { + createSipClient(); + isRestartRef.current = false; + } - const stopCallDurationCounter = () => { - if (timerRef.current) { - clearInterval(timerRef.current); - timerRef.current = null; - setSeconds(0); + if (args.error) { + toast({ + title: `Cannot connect to ${sipServerAddress}, ${args.reason}`, + status: "warning", + duration: DEFAULT_TOAST_DURATION, + isClosable: true, + }); + } + }); + // Call Status + sipClient.on(SipConstants.SESSION_RINGING, (args) => { + if (args.session.direction === "incoming") { + saveCurrentCall({ + number: args.session.user, + direction: args.session.direction, + timeStamp: Date.now(), + duration: "0", + callSid: uuidv4(), + }); + } + setCallStatus(SipConstants.SESSION_RINGING); + setSessionDirection(args.session.direction); + setInputNumber(args.session.user); + }); + sipClient.on(SipConstants.SESSION_ANSWERED, (args) => { + setCallSid(args.callSid); + const currentCall = getCurrentCall(); + if (currentCall) { + currentCall.timeStamp = Date.now(); + saveCurrentCall(currentCall); + } + setCallStatus(SipConstants.SESSION_ANSWERED); + startCallDurationCounter(); + }); + sipClient.on(SipConstants.SESSION_ENDED, (args) => { + addCallHistory(); + setCallStatus(SipConstants.SESSION_ENDED); + setSessionDirection(""); + stopCallDurationCounter(); + }); + sipClient.on(SipConstants.SESSION_FAILED, (args) => { + addCallHistory(); + setCallStatus(SipConstants.SESSION_FAILED); + setSessionDirection(""); + stopCallDurationCounter(); + }); + + sipClient.start(); + sipUA.current = sipClient; + }; + + const addCallHistory = () => { + const call = getCurrentCall(); + if (call) { + saveCallHistory(sipUsername, { + number: call.number, + direction: call.direction, + duration: transform(Date.now(), call.timeStamp), + timeStamp: call.timeStamp, + callSid: call.callSid, + name: call.name, + }); + } + deleteCurrentCall(); + }; + + function transform(t1: number, t2: number) { + const diff = Math.abs(t1 - t2) / 1000; // Get the difference in seconds + + const hours = Math.floor(diff / 3600); + const minutes = Math.floor((diff % 3600) / 60); + const seconds = Math.floor(diff % 60); + + // Pad the values with a leading zero if they are less than 10 + const hours1 = hours < 10 ? "0" + hours : hours; + const minutes1 = minutes < 10 ? "0" + minutes : minutes; + const seconds1 = seconds < 10 ? "0" + seconds : seconds; + + return `${hours1}:${minutes1}:${seconds1}`; } - }; - const createSipClient = () => { - setIsSwitchingUserStatus(true); - const client = { - username: `${sipUsernameRef.current}@${sipDomainRef.current}`, - password: sipPasswordRef.current, - name: sipDisplayNameRef.current ?? sipUsernameRef.current, + const handleDialPadClick = (value: string, fromKeyboad: boolean) => { + if (!(isInputNumberFocusRef.current && fromKeyboad)) { + setInputNumber((prev) => prev + value); + } + + if (isSipClientAnswered(callStatus)) { + sipUA.current?.dtmf(value, undefined); + } }; - const settings = { - pcConfig: { - iceServers: [{ urls: ["stun:stun.l.google.com:19302"] }], - }, - wsUri: sipServerAddressRef.current, - register: true, + const handleCallButtion = () => { + makeOutboundCall(inputNumber); }; - const sipClient = new SipUA(client, settings); - - // UA Status - sipClient.on(SipConstants.UA_REGISTERED, (args) => { - setStatus("registered"); - }); - sipClient.on(SipConstants.UA_UNREGISTERED, (args) => { - setStatus("unregistered"); - if (sipUA.current) { - sipUA.current.stop(); - } - unregisteredReasonRef.current = `User is not registered${ - args.cause ? `, ${args.cause}` : "" - }`; - }); - - sipClient.on(SipConstants.UA_DISCONNECTED, (args) => { - if (unregisteredReasonRef.current) { - toast({ - title: unregisteredReasonRef.current, - status: "warning", - duration: DEFAULT_TOAST_DURATION, - isClosable: true, - }); - unregisteredReasonRef.current = ""; - } - setStatus("disconnected"); - if (isRestartRef.current) { - createSipClient(); - isRestartRef.current = false; - } - - if (args.error) { - toast({ - title: `Cannot connect to ${sipServerAddress}, ${args.reason}`, - status: "warning", - duration: DEFAULT_TOAST_DURATION, - isClosable: true, - }); - } - }); - // Call Status - sipClient.on(SipConstants.SESSION_RINGING, (args) => { - if (args.session.direction === "incoming") { + const makeOutboundCall = (number: string, name: string = "") => { + if (sipUA.current && number) { + setIsCallButtonLoading(true); + setCallStatus(SipConstants.SESSION_RINGING); + setSessionDirection("outgoing"); saveCurrentCall({ - number: args.session.user, - direction: args.session.direction, + number: number, + name, + direction: "outgoing", timeStamp: Date.now(), duration: "0", callSid: uuidv4(), }); + // Add custom header if this is special jambonz call + let customHeaders: string[] = []; + if (number.startsWith("app-")) { + customHeaders = [ + `X-Application-Sid: ${number.substring(4, number.length)}`, + ]; + } + sipUA.current.call(number, customHeaders); } - setCallStatus(SipConstants.SESSION_RINGING); - setSessionDirection(args.session.direction); - setInputNumber(args.session.user); - }); - sipClient.on(SipConstants.SESSION_ANSWERED, (args) => { - setCallSid(args.callSid); - const currentCall = getCurrentCall(); - if (currentCall) { - currentCall.timeStamp = Date.now(); - saveCurrentCall(currentCall); + }; + + const clientGoOffline = () => { + if (sipUA.current) { + sipUA.current.stop(); + sipUA.current = null; } - setCallStatus(SipConstants.SESSION_ANSWERED); - startCallDurationCounter(); - }); - sipClient.on(SipConstants.SESSION_ENDED, (args) => { - addCallHistory(); - setCallStatus(SipConstants.SESSION_ENDED); - setSessionDirection(""); - stopCallDurationCounter(); - }); - sipClient.on(SipConstants.SESSION_FAILED, (args) => { - addCallHistory(); - setCallStatus(SipConstants.SESSION_FAILED); - setSessionDirection(""); - stopCallDurationCounter(); - }); + }; - sipClient.start(); - sipUA.current = sipClient; - }; - - const addCallHistory = () => { - const call = getCurrentCall(); - if (call) { - saveCallHistory(sipUsername, { - number: call.number, - direction: call.direction, - duration: transform(Date.now(), call.timeStamp), - timeStamp: call.timeStamp, - callSid: call.callSid, - name: call.name, - }); - } - deleteCurrentCall(); - }; - - function transform(t1: number, t2: number) { - const diff = Math.abs(t1 - t2) / 1000; // Get the difference in seconds - - const hours = Math.floor(diff / 3600); - const minutes = Math.floor((diff % 3600) / 60); - const seconds = Math.floor(diff % 60); - - // Pad the values with a leading zero if they are less than 10 - const hours1 = hours < 10 ? "0" + hours : hours; - const minutes1 = minutes < 10 ? "0" + minutes : minutes; - const seconds1 = seconds < 10 ? "0" + seconds : seconds; - - return `${hours1}:${minutes1}:${seconds1}`; - } - - const handleDialPadClick = (value: string, fromKeyboad: boolean) => { - if (!(isInputNumberFocusRef.current && fromKeyboad)) { - setInputNumber((prev) => prev + value); - } - - if (isSipClientAnswered(callStatus)) { - sipUA.current?.dtmf(value, undefined); - } - }; - - const handleCallButtion = () => { - makeOutboundCall(inputNumber); - }; - - const makeOutboundCall = (number: string, name: string = "") => { - if (sipUA.current && number) { - setIsCallButtonLoading(true); - setCallStatus(SipConstants.SESSION_RINGING); - setSessionDirection("outgoing"); - saveCurrentCall({ - number: number, - name, - direction: "outgoing", - timeStamp: Date.now(), - duration: "0", - callSid: uuidv4(), - }); - // Add custom header if this is special jambonz call - let customHeaders: string[] = []; - if (number.startsWith("app-")) { - customHeaders = [ - `X-Application-Sid: ${number.substring(4, number.length)}`, - ]; + const handleHangup = () => { + if (isSipClientAnswered(callStatus) || isSipClientRinging(callStatus)) { + sipUA.current?.terminate(480, "Call Finished", undefined); } - sipUA.current.call(number, customHeaders); - } - }; + }; - const clientGoOffline = () => { - if (sipUA.current) { - sipUA.current.stop(); - sipUA.current = null; - } - }; - - const handleHangup = () => { - if (isSipClientAnswered(callStatus) || isSipClientRinging(callStatus)) { - sipUA.current?.terminate(480, "Call Finished", undefined); - } - }; - - const handleCallOnHold = () => { - if (isSipClientAnswered(callStatus)) { - if (sipUA.current?.isHolded(undefined)) { - sipUA.current?.unhold(undefined); - } else { - sipUA.current?.hold(undefined); + const handleCallOnHold = () => { + if (isSipClientAnswered(callStatus)) { + if (sipUA.current?.isHolded(undefined)) { + sipUA.current?.unhold(undefined); + } else { + sipUA.current?.hold(undefined); + } } - } - }; + }; - const handleCallMute = () => { - if (isSipClientAnswered(callStatus)) { - if (sipUA.current?.isMuted(undefined)) { - sipUA.current?.unmute(undefined); - } else { - sipUA.current?.mute(undefined); + const handleCallMute = () => { + if (isSipClientAnswered(callStatus)) { + if (sipUA.current?.isMuted(undefined)) { + sipUA.current?.unmute(undefined); + } else { + sipUA.current?.mute(undefined); + } } - } - }; + }; - const handleAnswer = () => { - if (isSipClientRinging(callStatus)) { - sipUA.current?.answer(undefined); - } - }; + const handleAnswer = () => { + if (isSipClientRinging(callStatus)) { + sipUA.current?.answer(undefined); + } + }; - const handleDecline = () => { - if (isSipClientRinging(callStatus)) { - sipUA.current?.terminate(486, "Busy here", undefined); - } - }; + const handleDecline = () => { + if (isSipClientRinging(callStatus)) { + sipUA.current?.terminate(486, "Busy here", undefined); + } + }; - const isStatusRegistered = () => { - return status === "registered"; - }; + const isStatusRegistered = () => { + return status === "registered"; + }; - const handleSetActive = (id: number) => { - setActiveSettings(id); - setShowAccounts(false); - // fetchRegisterUser(); - reload(); - }; - - const handleClickOutside = (event: Event) => { - const target = event.target as Node; - if (accountsCardRef.current && !accountsCardRef.current.contains(target)) { + const handleSetActive = (id: number) => { + setActiveSettings(id); setShowAccounts(false); - } - }; - return ( - - {allSettings.length >= 1 ? ( - <> - - Account - - - { - setShowAccounts(true)} - _hover={{ - cursor: "pointer", - }} - spacing={2} - boxShadow="md" - w="full" - borderRadius={5} - paddingY={2} - paddingX={3.5} - > - {sipUsername && sipDomain ? ( - <> - - - - - {sipDisplayName || sipUsername} - - - - - {`${sipUsername}@${sipDomain}`} - - + reload(); + }; - - - - - - ) : ( - Select Account + const handleClickOutside = (event: Event) => { + const target = event.target as Node; + if ( + accountsCardRef.current && + !accountsCardRef.current.contains(target) + ) { + setShowAccounts(false); + } + }; + + return ( + + {allSettings.length >= 1 ? ( + <> + + Account + + + { + setShowAccounts(true)} + _hover={{ + cursor: "pointer", + }} + spacing={2} + boxShadow="md" + w="full" + borderRadius={5} + paddingY={2} + paddingX={3.5} + > + {sipUsername && sipDomain ? ( + <> + + + + + {sipDisplayName || sipUsername} + + + + + {`${sipUsername}@${sipDomain}`} + + + + + + + + + ) : ( + Select Account + )} + + } + {showAccounts && ( + + + + )} + + + ) : ( + + Go to Settings to configure your account + + )} + {pageView === PAGE_VIEW.DIAL_PAD && ( + + {isAdvanceMode && isSipClientIdle(callStatus) && ( + + {registeredUser.allow_direct_user_calling && ( + } + tooltip="Call an online user" + noResultLabel="No one else is online" + onClick={(_, value) => { + setInputNumber(value); + makeOutboundCall(value); + }} + onOpen={() => { + return new Promise( + (resolve, reject) => { + getRegisteredUser() + .then(({ json }) => { + const sortedUsers = json.sort((a, b) => + a.localeCompare(b) + ); + resolve( + sortedUsers + .filter((u) => !u.includes(sipUsername)) + .map((u) => { + const uName = u.match(/(^.*)@.*/); + return { + name: uName ? uName[1] : u, + value: uName ? uName[1] : u, + }; + }) + ); + }) + .catch((err) => reject(err)); + } + ); + }} + /> + )} + + {registeredUser.allow_direct_queue_calling && ( + } + tooltip="Take a call from queue" + noResultLabel="No calls in queue" + onClick={(name, value) => { + setAppName(`Queue ${name}`); + const calledQueue = `queue-${value}`; + setInputNumber(""); + makeOutboundCall(calledQueue, `Queue ${name}`); + }} + onOpen={() => { + return new Promise( + (resolve, reject) => { + getQueues() + .then(({ json }) => { + const sortedQueues = json.sort((a, b) => + a.name.localeCompare(b.name) + ); + resolve( + sortedQueues.map((q) => ({ + name: `${q.name} (${q.length})`, + value: q.name, + })) + ); + }) + .catch((err) => reject(err)); + } + ); + }} + /> + )} + + {registeredUser.allow_direct_app_calling && ( + } + tooltip="Call an application" + noResultLabel="No applications" + onClick={(name, value) => { + setAppName(`App ${name}`); + const calledAppId = `app-${value}`; + setInputNumber(""); + makeOutboundCall(calledAppId, `App ${name}`); + }} + onOpen={() => { + return new Promise( + (resolve, reject) => { + getApplications() + .then(({ json }) => { + const sortedApps = json.sort((a, b) => + a.name.localeCompare(b.name) + ); + resolve( + sortedApps.map((a) => ({ + name: a.name, + value: a.application_sid, + })) + ); + }) + .catch((err) => reject(err)); + } + ); + }} + /> + )} + {registeredUser.allow_direct_app_calling && showConference && ( + } + tooltip="Join a conference" + noResultLabel="No conference" + onClick={(name, value) => { + setPageView(PAGE_VIEW.JOIN_CONFERENCE); + setSelectedConference( + value === PAGE_VIEW.JOIN_CONFERENCE.toString() + ? "" + : value + ); + }} + onOpen={() => { + return new Promise( + (resolve, reject) => { + getConferences() + .then(({ json }) => { + const sortedApps = json.sort((a, b) => + a.localeCompare(b) + ); + resolve([ + { + name: "Start new conference", + value: PAGE_VIEW.JOIN_CONFERENCE.toString(), + }, + ...sortedApps.map((a) => ({ + name: a, + value: a, + })), + ]); + }) + .catch((err) => reject(err)); + } + ); + }} + /> )} - } - {showAccounts && ( - - - )} - - - ) : ( - - Go to Settings to configure your account - - )} - {pageView === PAGE_VIEW.DIAL_PAD && ( - - {isAdvanceMode && isSipClientIdle(callStatus) && ( - - {registeredUser.allow_direct_user_calling && ( - } - tooltip="Call an online user" - noResultLabel="No one else is online" - onClick={(_, value) => { - setInputNumber(value); - makeOutboundCall(value); - }} - onOpen={() => { - return new Promise( - (resolve, reject) => { - getRegisteredUser() - .then(({ json }) => { - const sortedUsers = json.sort((a, b) => - a.localeCompare(b) - ); - resolve( - sortedUsers - .filter((u) => !u.includes(sipUsername)) - .map((u) => { - const uName = u.match(/(^.*)@.*/); - return { - name: uName ? uName[1] : u, - value: uName ? uName[1] : u, - }; - }) - ); - }) - .catch((err) => reject(err)); - } - ); - }} - /> - )} - {registeredUser.allow_direct_queue_calling && ( - } - tooltip="Take a call from queue" - noResultLabel="No calls in queue" - onClick={(name, value) => { - setAppName(`Queue ${name}`); - const calledQueue = `queue-${value}`; - setInputNumber(""); - makeOutboundCall(calledQueue, `Queue ${name}`); - }} - onOpen={() => { - return new Promise( - (resolve, reject) => { - getQueues() - .then(({ json }) => { - const sortedQueues = json.sort((a, b) => - a.name.localeCompare(b.name) - ); - resolve( - sortedQueues.map((q) => ({ - name: `${q.name} (${q.length})`, - value: q.name, - })) - ); - }) - .catch((err) => reject(err)); - } - ); - }} - /> - )} + { + setInputNumber(e.target.value); + }} + onFocus={() => { + isInputNumberFocusRef.current = true; + }} + onBlur={() => { + isInputNumberFocusRef.current = false; + }} + textAlign="center" + isReadOnly={!isSipClientIdle(callStatus)} + /> - {registeredUser.allow_direct_app_calling && ( - } - tooltip="Call an application" - noResultLabel="No applications" - onClick={(name, value) => { - setAppName(`App ${name}`); - const calledAppId = `app-${value}`; - setInputNumber(""); - makeOutboundCall(calledAppId, `App ${name}`); - }} - onOpen={() => { - return new Promise( - (resolve, reject) => { - getApplications() - .then(({ json }) => { - const sortedApps = json.sort((a, b) => - a.name.localeCompare(b.name) - ); - resolve( - sortedApps.map((a) => ({ - name: a.name, - value: a.application_sid, - })) - ); - }) - .catch((err) => reject(err)); - } - ); - }} - /> - )} - {registeredUser.allow_direct_app_calling && showConference && ( - } - tooltip="Join a conference" - noResultLabel="No conference" - onClick={(name, value) => { - setPageView(PAGE_VIEW.JOIN_CONFERENCE); - setSelectedConference( - value === PAGE_VIEW.JOIN_CONFERENCE.toString() - ? "" - : value - ); - }} - onOpen={() => { - return new Promise( - (resolve, reject) => { - getConferences() - .then(({ json }) => { - const sortedApps = json.sort((a, b) => - a.localeCompare(b) - ); - resolve([ - { - name: "Start new conference", - value: PAGE_VIEW.JOIN_CONFERENCE.toString(), - }, - ...sortedApps.map((a) => ({ - name: a, - value: a, - })), - ]); - }) - .catch((err) => reject(err)); - } - ); - }} - /> - )} - - )} + {!isSipClientIdle(callStatus) && seconds >= 0 && ( + + {new Date(seconds * 1000).toISOString().substr(11, 8)} + + )} - { - setInputNumber(e.target.value); - }} - onFocus={() => { - isInputNumberFocusRef.current = true; - }} - onBlur={() => { - isInputNumberFocusRef.current = false; - }} - textAlign="center" - isReadOnly={!isSipClientIdle(callStatus)} - /> + - {!isSipClientIdle(callStatus) && seconds >= 0 && ( - - {new Date(seconds * 1000).toISOString().substr(11, 8)} - - )} - - - - {isSipClientIdle(callStatus) ? ( - - ) : ( - - - - } - w="33%" - variant="unstyled" - display="flex" - alignItems="center" - justifyContent="center" - onClick={handleCallOnHold} - /> - - - - } - w="70px" - h="70px" - borderRadius="100%" + {isSipClientIdle(callStatus) ? ( + + ) : ( + + + + } + w="33%" + variant="unstyled" + display="flex" + alignItems="center" + justifyContent="center" + onClick={handleCallOnHold} + /> + + + - } - w="33%" - variant="unstyled" - display="flex" - alignItems="center" - justifyContent="center" - onClick={handleCallMute} + aria-label="Hangup" + icon={} + w="70px" + h="70px" + borderRadius="100%" + colorScheme="jambonz" + onClick={handleHangup} /> - - - )} - - )} - {pageView === PAGE_VIEW.INCOMING_CALL && ( - - )} - {pageView === PAGE_VIEW.OUTGOING_CALL && ( - - )} - {pageView === PAGE_VIEW.JOIN_CONFERENCE && ( - { - if (isSipClientAnswered(callStatus)) { - sipUA.current?.terminate(480, "Call Finished", undefined); - } - setPageView(PAGE_VIEW.DIAL_PAD); - }} - call={(name) => { - const conference = `conference-${name}`; - setSelectedConference(name); - setInputNumber(conference); - makeOutboundCall(conference, `Conference ${name}`); - }} - /> - )} - - ); -}; + + + + } + w="33%" + variant="unstyled" + display="flex" + alignItems="center" + justifyContent="center" + onClick={handleCallMute} + /> + + + )} + + )} + {pageView === PAGE_VIEW.INCOMING_CALL && ( + + )} + {pageView === PAGE_VIEW.OUTGOING_CALL && ( + + )} + {pageView === PAGE_VIEW.JOIN_CONFERENCE && ( + { + if (isSipClientAnswered(callStatus)) { + sipUA.current?.terminate(480, "Call Finished", undefined); + } + setPageView(PAGE_VIEW.DIAL_PAD); + }} + call={(name) => { + const conference = `conference-${name}`; + setSelectedConference(name); + setInputNumber(conference); + makeOutboundCall(conference, `Conference ${name}`); + }} + /> + )} + + ); + } +); export default Phone; diff --git a/src/window/settings/accordionList.tsx b/src/window/settings/accordionList.tsx index ac6b103..034ca2e 100644 --- a/src/window/settings/accordionList.tsx +++ b/src/window/settings/accordionList.tsx @@ -38,7 +38,6 @@ export function AccordionList({ if (isNewFormOpen) handleCloseNewForm(); //closes new form if open handleOpenFormInAccordion(); setOpenAcc(accIndex); - // onToggle(); onOpen(); } return ( diff --git a/src/window/settings/accountForm.tsx b/src/window/settings/accountForm.tsx index e11cee4..65ee232 100644 --- a/src/window/settings/accountForm.tsx +++ b/src/window/settings/accountForm.tsx @@ -17,9 +17,7 @@ import { AppSettings, IAppSettings } from "src/common/types"; import PasswordInput from "src/components/password-input"; import { deleteSettings, editSettings, saveSettings } from "src/storage"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faCheckCircle -} from "@fortawesome/free-solid-svg-icons"; +import { faCheckCircle } from "@fortawesome/free-solid-svg-icons"; import { normalizeUrl } from "src/utils"; import { getAdvancedValidation } from "src/api"; import Switch from "src/imgs/icons/Switch.svg"; @@ -81,7 +79,6 @@ function AccountForm({ ); const checkCredential = (apiServer: string, accountSid: string) => { - // getApplications() getAdvancedValidation(apiServer, accountSid) .then(() => { setIsCredentialOk(true); diff --git a/src/window/settings/index.tsx b/src/window/settings/index.tsx index 44eea6b..51a826c 100644 --- a/src/window/settings/index.tsx +++ b/src/window/settings/index.tsx @@ -55,14 +55,9 @@ export const Settings = () => {