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 {
Application,
ConferenceParticipantAction,
ConferenceParticipantActions,
FetchError,
FetchTransport,
Queue,
@@ -8,6 +10,7 @@ import {
} from "./types";
import { MSG_SOMETHING_WRONG } from "./constants";
import { getAdvancedSettings } from "src/storage";
import { EmptyData } from "src/common/types";
const fetchTransport = <Type>(
url: string,
@@ -152,3 +155,21 @@ export const getSelfRegisteredUser = (username: string) => {
`${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;
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.emit(SipConstants.SESSION_ANSWERED, {
status: SipConstants.SESSION_ANSWERED,
});
this.#audio.playAnswer(undefined);
});
this.#rtcSession.on(
"accepted",
({ response }: { response: IncomingResponse }) => {
this.emit(SipConstants.SESSION_ANSWERED, {
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 => {
let { originator, cause, message } = data;
@@ -195,16 +201,19 @@ export default class SipSession extends events.EventEmitter {
direction: this.#rtcSession.direction,
});
});
// pc.addEventListener('track', (event: RTCPeerConnectionEventMap["track"]): void => {
// const stream: MediaStream = new MediaStream([event.track])
// if (this.#rtcSession.direction === 'outgoing') {
// this.#audio.pauseRinging();
// }
// this.#audio.playRemote(stream, "track");
// this.emit(SipConstants.SESSION_TRACK, {
// direction: this.#rtcSession.direction
// });
// });
pc.addEventListener(
"track",
(event: RTCPeerConnectionEventMap["track"]): void => {
const stream: MediaStream = new MediaStream([event.track]);
if (this.#rtcSession.direction === "outgoing") {
this.#audio.pauseRinging();
}
this.#audio.playRemote(stream);
this.emit(SipConstants.SESSION_TRACK, {
direction: this.#rtcSession.direction,
});
}
);
}
get rtcSession() {

View File

@@ -1,108 +1,100 @@
import {
SipSession, SipModel, SipConstants
} from "./index";
import { SipSession, SipModel, SipConstants } from "./index";
export default class SipSessionManager {
#sessions: Map<string, SipModel.SipSessionState>;
#sessions: Map<string, SipModel.SipSessionState>;
constructor() {
this.#sessions = new Map();
}
constructor() {
this.#sessions = new Map();
activate(session: SipSession) {
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) {
this.#sessions.forEach((v, k) => {
if (k !== session.id) {
v.active = false;
session.setActive(false);
} else {
v.active = true;
session.setActive(true);
}
});
const state = [...this.#sessions.values()].filter((s) => s.active);
if (state.length) {
return state[0].sipSession;
} else {
throw new Error("No Active sessions");
}
}
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");
}
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;
}
}
get count() {
return this.#sessions.size;
}
}

View File

@@ -28,8 +28,8 @@ export const IncommingCall = ({
as={FontAwesomeIcon}
icon={faPhone}
color="jambonz.500"
width="60px"
height="60px"
width="30px"
height="30px"
/>
<Text fontSize="15px">Incoming call from</Text>
<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 {
getApplications,
getConferences,
getQueues,
getRegisteredUser,
getSelfRegisteredUser,
@@ -126,6 +127,7 @@ export const Phone = ({
allow_direct_user_calling: false,
}
);
const callSidRef = useRef("");
const toast = useToast();
useEffect(() => {
@@ -204,9 +206,12 @@ export const Phone = ({
}, [status]);
useEffect(() => {
setInterval(() => {
const timer = setInterval(() => {
fetchRegisterUser();
}, 10_000);
}, 10000);
return () => {
clearInterval(timer);
};
}, []);
const fetchRegisterUser = () => {
@@ -311,6 +316,7 @@ export const Phone = ({
setInputNumber(args.session.user);
});
sipClient.on(SipConstants.SESSION_ANSWERED, (args) => {
callSidRef.current = args.callSid;
const currentCall = getCurrentCall();
if (currentCall) {
currentCall.timeStamp = Date.now();
@@ -628,17 +634,34 @@ export const Phone = ({
tooltip="Join a conference"
noResultLabel="No conference"
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={() => {
return new Promise<IconButtonMenuItems[]>((resolve) => {
resolve([
{
name: "Start New Conference",
value: "start_new_conference",
},
]);
});
return new Promise<IconButtonMenuItems[]>(
(resolve, reject) => {
getConferences()
.then(({ json }) => {
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>