add new audios and fix review comment

This commit is contained in:
Quan HL
2023-10-24 11:56:28 +07:00
parent e0dd95a494
commit cec3ca3a3e
12 changed files with 131 additions and 24 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -16,7 +16,7 @@ export interface IconButtonMenuItems {
type IconButtonMenuProbs = {
icon: React.ReactElement;
onOpen: () => Promise<IconButtonMenuItems[]>;
onClick: (value: string) => void;
onClick: (name: string, value: string) => void;
tooltip: string;
};
@@ -53,7 +53,7 @@ export const IconButtonMenu = ({
</MenuItem>
) : (
items.map((i, idx) => (
<MenuItem key={idx} onClick={() => onClick(i.value)}>
<MenuItem key={idx} onClick={() => onClick(i.name, i.value)}>
{i.name}
</MenuItem>
))

View File

@@ -1,17 +1,26 @@
export default class SipAudioElements {
#ringing: HTMLAudioElement;
#ringBack: HTMLAudioElement;
#failed: HTMLAudioElement;
#answer: HTMLAudioElement;
#busy: HTMLAudioElement;
#remote: HTMLAudioElement;
#hungup: HTMLAudioElement;
constructor() {
this.#ringing = new Audio(chrome.runtime.getURL("audios/ringing.mp3"));
this.#ringing.loop = true;
this.#ringing.volume = 0.8;
this.#failed = new Audio(chrome.runtime.getURL("audios/failed.mp3"));
this.#ringBack = new Audio(chrome.runtime.getURL("audios/us-ringback.mp3"));
this.#ringBack.loop = true;
this.#ringBack.volume = 0.8;
this.#failed = new Audio(chrome.runtime.getURL("audios/call-failed.mp3"));
this.#failed.volume = 0.3;
this.#answer = new Audio(chrome.runtime.getURL("audios/failed.mp3"));
this.#answer.volume = 0.3;
this.#busy = new Audio(chrome.runtime.getURL("audios/us-busy-signal.mp3"));
this.#busy.volume = 0.3;
this.#hungup = new Audio(
chrome.runtime.getURL("audios/remote-party-hungup-tone.mp3")
);
this.#hungup.volume = 0.3;
this.#remote = new Audio();
}
@@ -28,20 +37,38 @@ export default class SipAudioElements {
}
}
playRingback(volume: number | undefined): void {
if (volume) {
this.#ringBack.volume = volume;
}
this.#ringBack.play();
}
pauseRingback(): void {
if (!this.#ringBack.paused) {
this.#ringBack.pause();
}
}
playFailed(volume: number | undefined): void {
this.pauseRinging();
this.pauseRingback();
if (volume) {
this.#failed.volume = volume;
}
this.#failed.play();
}
playRemotePartyHungup(volume: number | undefined): void {
if (volume) {
this.#hungup.volume = volume;
}
this.#hungup.play();
}
playAnswer(volume: number | undefined): void {
this.pauseRinging();
// if (volume) {
// this.#answer.volume = volume;
// }
// this.#answer.play();
this.pauseRingback();
}
isRemoteAudioPaused(): boolean {
@@ -53,6 +80,11 @@ export default class SipAudioElements {
this.#remote.play();
}
stopAll() {
this.pauseRinging();
this.pauseRingback();
}
isPLaying(audio: HTMLAudioElement) {
return (
audio.currentTime > 0 &&

View File

@@ -57,6 +57,8 @@ export default class SipSession extends events.EventEmitter {
});
if (this.#audio.isRemoteAudioPaused() && !this.replaces) {
this.#audio.playRinging(undefined);
} else {
this.#audio.playRingback(undefined);
}
});
@@ -94,12 +96,16 @@ export default class SipSession extends events.EventEmitter {
this.#audio.playFailed(undefined);
} else {
this.#audio.pauseRinging();
this.#audio.pauseRingback();
}
});
this.#rtcSession.on("ended", (data: EndEvent): void => {
const { originator, cause, message } = data;
let description;
if (originator === "remote") {
this.#audio.playRemotePartyHungup(undefined);
}
if (message && originator === "remote" && message.hasHeader("Reason")) {
const reason = Grammar.parse(message.getHeader("Reason"), "Reason");
if (reason) {

View File

@@ -58,3 +58,11 @@ export const isSipClientIdle = (callStatus: string) => {
callStatus === SipConstants.SESSION_FAILED
);
};
export const normalizeUrl = (input: string): string => {
// Extract the domain name
const url = new URL(input.startsWith("http") ? input : `https://${input}`);
// Return the fully formed URL
return `${url.protocol}//${url.hostname}/api/v1`;
};

View File

@@ -15,11 +15,15 @@ import Phone from "./phone";
import Settings from "./settings";
import { DEFAULT_COLOR_SCHEME } from "src/common/constants";
import { useEffect, useState } from "react";
import { getCallHistories, getSettings } from "src/storage";
import {
getAdvancedSettings,
getCallHistories,
getSettings,
} from "src/storage";
import jambonz from "src/imgs/jambonz.svg";
import CallHistories from "./history";
import { CallHistory } from "src/common/types";
import { AdvancedAppSettings, CallHistory } from "src/common/types";
export const WindowApp = () => {
const [sipDomain, setSipDomain] = useState("");
@@ -30,6 +34,9 @@ export const WindowApp = () => {
const [callHistories, setCallHistories] = useState<CallHistory[]>([]);
const [calledNumber, setCalledNumber] = useState("");
const [tabIndex, setTabIndex] = useState(0);
const [advancedSettings, setAdvancedSettings] = useState<AdvancedAppSettings>(
getAdvancedSettings()
);
const tabsSettings = [
{
title: "Dialer",
@@ -41,6 +48,7 @@ export const WindowApp = () => {
sipDisplayName={sipDisplayName}
sipServerAddress={sipServerAddress}
calledNumber={[calledNumber, setCalledNumber]}
advancedSettings={advancedSettings}
/>
),
},
@@ -75,6 +83,7 @@ export const WindowApp = () => {
const loadSettings = () => {
const settings = getSettings();
setAdvancedSettings(getAdvancedSettings());
setSipDomain(settings.sipDomain);
setSipServerAddress(settings.sipServerAddress);
setSipUsername(settings.sipUsername);

View File

@@ -24,12 +24,15 @@ import {
Play,
Users,
} from "react-feather";
import { Call, SipCallDirection, SipClientStatus } from "src/common/types";
import {
AdvancedAppSettings,
SipCallDirection,
SipClientStatus,
} from "src/common/types";
import { SipConstants, SipUA } from "src/lib";
import IncommingCall from "./incomming-call";
import DialPad from "./dial-pad";
import {
formatPhoneNumber,
isSipClientAnswered,
isSipClientIdle,
isSipClientRinging,
@@ -48,7 +51,6 @@ import {
import { OutGoingCall } from "./outgoing-call";
import { v4 as uuidv4 } from "uuid";
import IconButtonMenu, { IconButtonMenuItems } from "src/components/menu";
import { Application } from "src/api/types";
import { getApplications, getQueues, getRegisteredUser } from "src/api";
type PhoneProbs = {
@@ -58,6 +60,7 @@ type PhoneProbs = {
sipPassword: string;
sipDisplayName: string;
calledNumber: [string, React.Dispatch<React.SetStateAction<string>>];
advancedSettings: AdvancedAppSettings;
};
export const Phone = ({
@@ -67,8 +70,10 @@ export const Phone = ({
sipPassword,
sipDisplayName,
calledNumber: [calledANumber, setCalledANumber],
advancedSettings,
}: PhoneProbs) => {
const [inputNumber, setInputNumber] = useState("");
const [appName, setAppName] = useState("");
const inputNumberRef = useRef(inputNumber);
const [status, setStatus] = useState<SipClientStatus>("offline");
const [isConfigured, setIsConfigured] = useState(false);
@@ -93,9 +98,12 @@ export const Phone = ({
setIsConfigured(false);
clientGoOffline();
}
}, [sipDomain, sipUsername, sipPassword, sipServerAddress, sipDisplayName]);
useEffect(() => {
const advancedSettings = getAdvancedSettings();
setIsAdvancedMode(!!advancedSettings.accountSid);
}, [sipDomain, sipUsername, sipPassword, sipServerAddress, sipDisplayName]);
}, [advancedSettings]);
useEffect(() => {
inputNumberRef.current = inputNumber;
@@ -400,7 +408,10 @@ export const Phone = ({
decline={handleDecline}
/>
) : (
<OutGoingCall number={inputNumber} cancelCall={handleDecline} />
<OutGoingCall
number={inputNumber || appName}
cancelCall={handleDecline}
/>
)
) : (
<VStack
@@ -414,7 +425,7 @@ export const Phone = ({
<IconButtonMenu
icon={<Users />}
tooltip="Call to user"
onClick={(value) => {
onClick={(_, value) => {
setInputNumber(value);
makeOutboundCall(value);
}}
@@ -443,9 +454,10 @@ export const Phone = ({
<IconButtonMenu
icon={<List />}
tooltip="Call to a queue"
onClick={(value) => {
onClick={(name, value) => {
setAppName(`Queue ${name}`);
const calledQueue = `queue-${value}`;
setInputNumber(calledQueue);
setInputNumber("");
makeOutboundCall(calledQueue);
}}
onOpen={() => {
@@ -469,9 +481,10 @@ export const Phone = ({
<IconButtonMenu
icon={<GitMerge />}
tooltip="Call to an application"
onClick={(value) => {
onClick={(name, value) => {
setAppName(`App ${name}`);
const calledAppId = `app-${value}`;
setInputNumber(calledAppId);
setInputNumber("");
makeOutboundCall(calledAppId);
}}
onOpen={() => {

View File

@@ -3,6 +3,7 @@ import {
FormControl,
FormLabel,
HStack,
Icon,
Image,
Input,
Spacer,
@@ -10,20 +11,27 @@ import {
VStack,
} from "@chakra-ui/react";
import { useEffect, useState } from "react";
import { CheckCircle, XCircle } from "react-feather";
import { getApplications } from "src/api";
import { AdvancedAppSettings } from "src/common/types";
import PasswordInput from "src/components/password-input";
import InfoIcon from "src/imgs/icons/Info.svg";
import ResetIcon from "src/imgs/icons/Reset.svg";
import { getAdvancedSettings, saveAddvancedSettings } from "src/storage";
import { normalizeUrl } from "src/utils";
export const AdvancedSettings = () => {
const [apiKey, setApiKey] = useState("");
const [apiServer, setApiServer] = useState("");
const [accountSid, setAccountSid] = useState("");
const [isCredentialOk, setIsCredentialOk] = useState(false);
const [isAdvancedMode, setIsAdvancedMode] = useState(false);
useEffect(() => {
const settings = getAdvancedSettings();
if (settings.apiServer) {
setIsAdvancedMode(true);
checkCredential();
setApiServer(settings.apiServer);
}
if (settings.apiKey) {
@@ -34,20 +42,35 @@ export const AdvancedSettings = () => {
}
}, []);
const handleSubmit = () => {
const checkCredential = () => {
getApplications()
.then(() => {
setIsCredentialOk(true);
})
.catch(() => {
setIsCredentialOk(false);
});
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setApiServer(normalizeUrl(apiServer));
const settings: AdvancedAppSettings = {
accountSid,
apiKey,
apiServer,
apiServer: normalizeUrl(apiServer),
};
saveAddvancedSettings(settings);
setIsAdvancedMode(true);
checkCredential();
};
const resetSetting = () => {
saveAddvancedSettings({} as AdvancedAppSettings);
setApiKey("");
setApiServer("");
setAccountSid("");
setIsAdvancedMode(false);
};
return (
@@ -85,6 +108,22 @@ export const AdvancedSettings = () => {
<PasswordInput password={[apiKey, setApiKey]} isRequired />
</FormControl>
</VStack>
{isAdvancedMode && (
<HStack w="full" mt={2} mb={2}>
<Icon
as={isCredentialOk ? CheckCircle : XCircle}
color={isCredentialOk ? "green.500" : "red.500"}
boxSize={6}
/>
<Text
fontSize="14px"
color={isCredentialOk ? "green.500" : "red.500"}
>
Credential is {isCredentialOk ? "valid" : "invalid"}
</Text>
</HStack>
)}
<Button colorScheme="jambonz" type="submit" w="full">
Save
</Button>