mirror of
https://github.com/jambonz/chrome-extension-dialer.git
synced 2025-12-19 04:47:45 +00:00
wip
This commit is contained in:
@@ -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
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user