This commit is contained in:
Quan HL
2024-05-08 15:39:00 +07:00
parent 9b961c83f9
commit deb4c13531
6 changed files with 191 additions and 129 deletions

View File

@@ -1,5 +1,7 @@
import { import {
Application, Application,
ConferenceParticipantAction,
ConferenceParticipantActions,
FetchError, FetchError,
FetchTransport, FetchTransport,
Queue, Queue,
@@ -8,6 +10,7 @@ import {
} from "./types"; } from "./types";
import { MSG_SOMETHING_WRONG } from "./constants"; import { MSG_SOMETHING_WRONG } from "./constants";
import { getAdvancedSettings } from "src/storage"; import { getAdvancedSettings } from "src/storage";
import { EmptyData } from "src/common/types";
const fetchTransport = <Type>( const fetchTransport = <Type>(
url: string, url: string,
@@ -152,3 +155,21 @@ export const getSelfRegisteredUser = (username: string) => {
`${advancedSettings.apiServer}/Accounts/${advancedSettings.accountSid}/RegisteredSipUsers/${username}` `${advancedSettings.apiServer}/Accounts/${advancedSettings.accountSid}/RegisteredSipUsers/${username}`
); );
}; };
export const getConferences = () => {
const advancedSettings = getAdvancedSettings();
return getFetch<string[]>(
`${advancedSettings.apiServer}/Accounts/${advancedSettings.accountSid}/Conferences`
);
};
export const updateConferenceParticipantAction = (
callSid: string,
payload: ConferenceParticipantAction
) => {
const advancedSettings = getAdvancedSettings();
return putFetch<EmptyData, ConferenceParticipantAction>(
`${advancedSettings.apiServer}/Accounts/${advancedSettings.accountSid}/Calls/${callSid}`,
payload
);
};

View File

@@ -45,3 +45,20 @@ export interface RegisteredUser {
allow_direct_user_calling: boolean; allow_direct_user_calling: boolean;
registered_status: string; registered_status: string;
} }
export type ConferenceParticipantActions =
| "tag"
| "untag"
| "coach"
| "mute"
| "unmute"
| "hold"
| "unhold";
export interface ConferenceParticipantAction {
action: ConferenceParticipantActions;
tag: string;
}
export interface UpdateCall {
conferenceParticipantAction: ConferenceParticipantAction;
}

View File

@@ -62,12 +62,18 @@ export default class SipSession extends events.EventEmitter {
} }
}); });
this.#rtcSession.on("accepted", () => { this.#rtcSession.on(
this.emit(SipConstants.SESSION_ANSWERED, { "accepted",
status: SipConstants.SESSION_ANSWERED, ({ response }: { response: IncomingResponse }) => {
}); this.emit(SipConstants.SESSION_ANSWERED, {
this.#audio.playAnswer(undefined); status: SipConstants.SESSION_ANSWERED,
}); callSid: response.hasHeader("X-Call-Sid")
? response.getHeader("X-Call-Sid")
: null,
});
this.#audio.playAnswer(undefined);
}
);
this.#rtcSession.on("failed", (data: EndEvent): void => { this.#rtcSession.on("failed", (data: EndEvent): void => {
let { originator, cause, message } = data; let { originator, cause, message } = data;
@@ -195,16 +201,19 @@ export default class SipSession extends events.EventEmitter {
direction: this.#rtcSession.direction, direction: this.#rtcSession.direction,
}); });
}); });
// pc.addEventListener('track', (event: RTCPeerConnectionEventMap["track"]): void => { pc.addEventListener(
// const stream: MediaStream = new MediaStream([event.track]) "track",
// if (this.#rtcSession.direction === 'outgoing') { (event: RTCPeerConnectionEventMap["track"]): void => {
// this.#audio.pauseRinging(); const stream: MediaStream = new MediaStream([event.track]);
// } if (this.#rtcSession.direction === "outgoing") {
// this.#audio.playRemote(stream, "track"); this.#audio.pauseRinging();
// this.emit(SipConstants.SESSION_TRACK, { }
// direction: this.#rtcSession.direction this.#audio.playRemote(stream);
// }); this.emit(SipConstants.SESSION_TRACK, {
// }); direction: this.#rtcSession.direction,
});
}
);
} }
get rtcSession() { get rtcSession() {

View File

@@ -1,108 +1,100 @@
import { import { SipSession, SipModel, SipConstants } from "./index";
SipSession, SipModel, SipConstants
} from "./index";
export default class SipSessionManager { export default class SipSessionManager {
#sessions: Map<string, SipModel.SipSessionState>;
#sessions: Map<string, SipModel.SipSessionState>; constructor() {
this.#sessions = new Map();
}
constructor() { activate(session: SipSession) {
this.#sessions = new Map(); this.#sessions.forEach((v, k) => {
if (k !== session.id) {
v.active = false;
session.setActive(false);
} else {
v.active = true;
session.setActive(true);
}
});
}
updateSession(field: string, session: SipSession, args: any): void {
const state: SipModel.SipSessionState = this.getSessionState(session.id);
if (state) {
switch (field) {
case SipConstants.SESSION_RINGING:
state.status = args.status;
break;
case SipConstants.SESSION_ANSWERED:
state.status = args.status;
break;
case SipConstants.SESSION_FAILED:
case SipConstants.SESSION_ENDED:
state.status = args.status;
state.endState = {
cause: args.cause,
status: args.status,
originator: args.endState,
description: args.description,
};
this.#sessions.delete(session.id);
break;
case SipConstants.SESSION_MUTED:
state.muteStatus = args.status;
break;
case SipConstants.SESSION_HOLD:
state.holdState = {
originator: args.originator,
status: args.status,
};
break;
case SipConstants.SESSION_ICE_READY:
state.iceReady = true;
break;
case SipConstants.SESSION_ACTIVE:
state.active = true;
break;
}
}
}
getSessionState(id: string): SipModel.SipSessionState {
const state = this.#sessions.get(id);
if (!state) {
throw new Error("Session not found");
}
return state;
}
getSession(id: string): SipSession {
return this.getSessionState(id).sipSession;
}
newSession(session: SipSession): void {
this.#sessions.set(session.id, {
id: session.id,
sipSession: session,
startDateTime: new Date(),
active: true,
status: "init",
});
}
get activeSession(): SipSession {
if (this.#sessions.size === 0) {
throw new Error("No sessions");
} }
activate(session: SipSession) { const state = [...this.#sessions.values()].filter((s) => s.active);
this.#sessions.forEach((v, k) => { if (state.length) {
if (k !== session.id) { return state[0].sipSession;
v.active = false; } else {
session.setActive(false); throw new Error("No Active sessions");
} else {
v.active = true;
session.setActive(true);
}
});
} }
}
updateSession(field: string, session: SipSession, args: any): void { get count() {
return this.#sessions.size;
const state: SipModel.SipSessionState = this.getSessionState(session.id); }
if (state) { }
switch (field) {
case SipConstants.SESSION_RINGING:
state.status = args.status;
break;
case SipConstants.SESSION_ANSWERED:
state.status = args.status;
break;
case SipConstants.SESSION_FAILED:
case SipConstants.SESSION_ENDED:
state.status = args.status;
state.endState = {
cause: args.cause,
status: args.status,
originator: args.endState,
description: args.description
}
this.#sessions.delete(session.id);
break;
case SipConstants.SESSION_MUTED:
state.muteStatus = args.status;
break;
case SipConstants.SESSION_HOLD:
state.holdState = {
originator: args.originator,
status: args.status
}
break;
case SipConstants.SESSION_ICE_READY:
state.iceReady = true;
break;
case SipConstants.SESSION_ACTIVE:
state.active = true;
break;
}
}
}
getSessionState(id: string): SipModel.SipSessionState {
const state = this.#sessions.get(id);
if (!state) {
throw new Error("Session not found");
}
return state;
}
getSession(id: string): SipSession {
return this.getSessionState(id).sipSession;
}
newSession(session: SipSession): void {
this.#sessions.set(session.id,
{
id: session.id,
sipSession: session,
startDateTime: new Date(),
active: true,
status: 'init',
});
}
get activeSession(): SipSession {
if (this.#sessions.size === 0) {
throw new Error("No sessions");
}
const state = [...this.#sessions.values()].filter((s) => s.active);
if (state.length) {
return state[0].sipSession;
} else {
throw new Error("No Active sessions");
}
}
get count() {
return this.#sessions.size;
}
}

View File

@@ -28,8 +28,8 @@ export const IncommingCall = ({
as={FontAwesomeIcon} as={FontAwesomeIcon}
icon={faPhone} icon={faPhone}
color="jambonz.500" color="jambonz.500"
width="60px" width="30px"
height="60px" height="30px"
/> />
<Text fontSize="15px">Incoming call from</Text> <Text fontSize="15px">Incoming call from</Text>
<Text fontSize="24px" fontWeight="bold"> <Text fontSize="24px" fontWeight="bold">

View File

@@ -42,6 +42,7 @@ import { v4 as uuidv4 } from "uuid";
import IconButtonMenu, { IconButtonMenuItems } from "src/components/menu"; import IconButtonMenu, { IconButtonMenuItems } from "src/components/menu";
import { import {
getApplications, getApplications,
getConferences,
getQueues, getQueues,
getRegisteredUser, getRegisteredUser,
getSelfRegisteredUser, getSelfRegisteredUser,
@@ -126,6 +127,7 @@ export const Phone = ({
allow_direct_user_calling: false, allow_direct_user_calling: false,
} }
); );
const callSidRef = useRef("");
const toast = useToast(); const toast = useToast();
useEffect(() => { useEffect(() => {
@@ -204,9 +206,12 @@ export const Phone = ({
}, [status]); }, [status]);
useEffect(() => { useEffect(() => {
setInterval(() => { const timer = setInterval(() => {
fetchRegisterUser(); fetchRegisterUser();
}, 10_000); }, 10000);
return () => {
clearInterval(timer);
};
}, []); }, []);
const fetchRegisterUser = () => { const fetchRegisterUser = () => {
@@ -311,6 +316,7 @@ export const Phone = ({
setInputNumber(args.session.user); setInputNumber(args.session.user);
}); });
sipClient.on(SipConstants.SESSION_ANSWERED, (args) => { sipClient.on(SipConstants.SESSION_ANSWERED, (args) => {
callSidRef.current = args.callSid;
const currentCall = getCurrentCall(); const currentCall = getCurrentCall();
if (currentCall) { if (currentCall) {
currentCall.timeStamp = Date.now(); currentCall.timeStamp = Date.now();
@@ -628,17 +634,34 @@ export const Phone = ({
tooltip="Join a conference" tooltip="Join a conference"
noResultLabel="No conference" noResultLabel="No conference"
onClick={(name, value) => { onClick={(name, value) => {
setPageView(PAGE_VIEW.START_NEW_CONFERENCE); if (value === PAGE_VIEW.START_NEW_CONFERENCE.toString()) {
setPageView(PAGE_VIEW.START_NEW_CONFERENCE);
} else {
setPageView(PAGE_VIEW.JOIN_CONFERENCE);
}
}} }}
onOpen={() => { onOpen={() => {
return new Promise<IconButtonMenuItems[]>((resolve) => { return new Promise<IconButtonMenuItems[]>(
resolve([ (resolve, reject) => {
{ getConferences()
name: "Start New Conference", .then(({ json }) => {
value: "start_new_conference", const sortedApps = json.sort((a, b) =>
}, a.localeCompare(b)
]); );
}); resolve([
{
name: "Start new conference",
value: PAGE_VIEW.START_NEW_CONFERENCE.toString(),
},
...sortedApps.map((a) => ({
name: a,
value: a,
})),
]);
})
.catch((err) => reject(err));
}
);
}} }}
/> />
</HStack> </HStack>