Skip to content

Commit

Permalink
jigate: add ivr
Browse files Browse the repository at this point in the history
  • Loading branch information
wfleischer committed May 16, 2023
1 parent e71fe78 commit b685010
Show file tree
Hide file tree
Showing 23 changed files with 83 additions and 33 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ To setup jigate using [docker-jitsi-meet]:
## Test

1. Register a SIP client to the [jigate](#jigate) service at `<sip:127.0.0.1;transport=udp>` as `"<Display name>" <sip:[email protected]>`
1. Call `sip:<meeting id>@127.0.0.1`.
1. Call `sip:[email protected]` to dial into an ivr and provide the meeting id or call `sip:<meeting id>@127.0.0.1`.

[Developing applications for FreeSWITCH]: https://medium.com/makingtuenti/developing-applications-for-freeswitch-fccbe75ada81
[docker-jitsi-meet]: https://github.com/jitsi/docker-jitsi-meet
Expand Down
1 change: 1 addition & 0 deletions jigate.env
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ JIGATE_ESL_PASSWORD=
# JIGATE_EXTERNAL_IP=127.0.0.1
# JIGATE_EXTERNAL_SIP_PORT=5060
# JIGATE_INTERNAL_IP=interface:ipv4/eth1
# JIGATE_IVR_DESTINATION=1000
# JIGATE_REGISTRATION_PASSWORD=
# JIGATE_REGISTRATION_USERNAME=user
# JIGATE_RTP_END_PORT=20250
Expand Down
1 change: 1 addition & 0 deletions jigate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ services:
- JIGATE_EXTERNAL_SIP_PORT=${JIGATE_EXTERNAL_SIP_PORT:-5060}
- JIGATE_INTERNAL_IP=${JIGATE_INTERNAL_IP:-interface:ipv4/eth1}
- JIGATE_INTERNAL_SIP_PORT=${JIGASI_SIP_PORT:-5060}
- JIGATE_IVR_DESTINATION=${JIGATE_IVR_DESTINATION:-1000}
- JIGATE_REGISTRATION_PASSWORD
- JIGATE_REGISTRATION_USERNAME=${JIGATE_REGISTRATION_USER:-user}
- JIGATE_RTP_END_PORT=${JIGATE_RTP_END_PORT:-20250}
Expand Down
17 changes: 14 additions & 3 deletions jigate/etc/freeswitch/dialplan/inbound.xml
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<include>
<context name="public">
<extension name="inbound">
<condition field="destination_number" expression=".*">
<action application="event" data="Event-Name=JIGASI_CALL,Meeting-URI=${sip_h_X-Meeting-URI}"/>
<extension name="header-call">
<condition field="sip_h_X-Meeting-URI" expression="^.+$">
<action application="event" data="Event-Name=HEADER_CALL"/>
</condition>
</extension>
<extension name="ivr-call">
<condition field="destination_number" expression="$${jigate_ivr_destination}">
<action application="event" data="Event-Name=CALL_IVR"/>
<action application="park"/>
</condition>
</extension>
<extension name="uri-call">
<condition field="destination_number" expression="^\d+(\.[a-z0-9-_.]+)?$">
<action application="set" data="sip_h_X-Meeting-URI=$0"/>
<action application="event" data="Event-Name=HEADER_CALL"/>
</condition>
</extension>
</context>
</include>
1 change: 1 addition & 0 deletions jigate/etc/freeswitch/freeswitch.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<X-PRE-PROCESS cmd="env-set" data="jigate_external_sip_port=$JIGATE_EXTERNAL_SIP_PORT"/>
<X-PRE-PROCESS cmd="env-set" data="jigate_internal_ip=$JIGATE_INTERNAL_IP"/>
<X-PRE-PROCESS cmd="env-set" data="jigate_internal_sip_port=$JIGATE_INTERNAL_SIP_PORT"/>
<X-PRE-PROCESS cmd="env-set" data="jigate_ivr_destination=$JIGATE_IVR_DESTINATION"/>
<X-PRE-PROCESS cmd="env-set" data="jigate_registration_password=$JIGATE_REGISTRATION_PASSWORD"/>
<X-PRE-PROCESS cmd="env-set" data="jigate_registration_username=$JIGATE_REGISTRATION_USERNAME"/>
<X-PRE-PROCESS cmd="env-set" data="jigate_rtp_end_port=$JIGATE_RTP_END_PORT"/>
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
25 changes: 15 additions & 10 deletions src/audioMessage.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
export enum AudioMessage {
ConferenceEndedByHost = 'jigate/en/us/conference_ended_by_host.wav',
ConferenceStarted = 'jigate/en/us/conference_started.wav',
HandIsLowered = 'jigate/en/us/hand_is_lowered.wav',
HandIsRaised = 'jigate/en/us/hand_is_raised.wav',
NotAllowedToUnmute = 'jigate/en/us/not_allowed_to_unmute.wav',
PleaseUnmute = 'jigate/en/us/please_unmute.wav',
PleaseWaitConferenceStartShortly = 'jigate/en/us/please_wait_conference_start_shortly.wav',
PleaseWaitToJoinConference = 'jigate/en/us/please_wait_to_join_conference.wav',
YouAreMuted = 'jigate/en/us/you_are_muted.wav',
YouAreUnmuted = 'jigate/en/us/you_are_unmuted.wav',
ConferenceEndedByHost = 'en/us/conference_ended_by_host.wav',
ConferenceStarted = 'en/us/conference_started.wav',
ConnectingToYourConference = 'en/us/connecting_to_your_conference.wav',
EnterYourMeetingId = 'en/us/enter_your_meeting_id.wav',
HandIsLowered = 'en/us/hand_is_lowered.wav',
HandIsRaised = 'en/us/hand_is_raised.wav',
NotAllowedToUnmute = 'en/us/not_allowed_to_unmute.wav',
PleaseUnmute = 'en/us/please_unmute.wav',
PleaseWaitConferenceStartShortly = 'en/us/please_wait_conference_start_shortly.wav',
PleaseWaitToJoinConference = 'en/us/please_wait_to_join_conference.wav',
SomethingWentWrong = 'en/us/something_went_wrong.wav',
UnknownMeetingId = 'en/us/unknown_meeting_id.wav',
WeDidNotReceiveAnyInput = 'en/us/we_did_not_receive_any_input.wav',
YouAreMuted = 'en/us/you_are_muted.wav',
YouAreUnmuted = 'en/us/you_are_unmuted.wav',
}
7 changes: 6 additions & 1 deletion src/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export enum CustomChannelVariables {
CallState = 'call_state',
HandRaised = 'hand_raised',
LoopingAudioMessage = 'looping_audio_message',
MeetingId = 'meeting_id',
Muted = 'muted',
Nickname = 'nickname',
}
Expand All @@ -33,7 +34,8 @@ export enum EventFilter {
ChannelHangup = 'esl::event::CHANNEL_HANGUP::*',
HandRaiseToggle = 'esl::event::HAND_RAISE_TOGGLE::*',
Heartbeat = 'esl::event::HEARTBEAT::*',
JigasiCall = 'esl::event::JIGASI_CALL::*',
CallHeader = 'esl::event::CALL_HEADER::*',
CallIVR = 'esl::event::CALL_IVR::*',
MuteToggle = 'esl::event::MUTE_TOGGLE::*',
RecvInfo = 'esl::event::RECV_INFO::*',
SessionHeartbeat = 'esl::event::SESSION_HEARTBEAT::*',
Expand All @@ -55,6 +57,7 @@ export class ChannelEvent {
jigasiMessage: JigasiMessage | undefined;
jigasiUuid;
loopingAudioMessage;
meetingId;
meetingURI;
muted;
name;
Expand All @@ -79,6 +82,7 @@ export class ChannelEvent {
[`variable_${CustomChannelVariables.CallState}`]: callState,
[`variable_${CustomChannelVariables.HandRaised}`]: handRaised,
[`variable_${CustomChannelVariables.LoopingAudioMessage}`]: loopingAudioMessage,
[`variable_${CustomChannelVariables.MeetingId}`]: meetingId,
[`variable_${CustomChannelVariables.Muted}`]: muted,
[`variable_${CustomChannelVariables.Nickname}`]: nickname,
} = event.headers;
Expand All @@ -96,6 +100,7 @@ export class ChannelEvent {
this.isJigasiCall = calleeIdNumber == JIGASI_USER_ID;
this.isJigasiEvent = sipFromUser == JIGASI_USER_ID;
this.loopingAudioMessage = loopingAudioMessage as AudioMessage;
this.meetingId = meetingId;
this.meetingURI = meetingUri;
this.muted = muted == 'true';
this.name = name;
Expand Down
62 changes: 44 additions & 18 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ const init = () => {
connection.on(ConnectionEvent.Error, handleConnectionFailure);

connection.on(EventFilter.ChannelBridge, handleChannelBridge);
connection.on(EventFilter.ChannelExecuteComplete, handleChannelExecuteComplete);
connection.on(EventFilter.ChannelHangup, handleChannelHangup);
connection.on(EventFilter.JigasiCall, handleJigasiCall);
connection.on(EventFilter.CallHeader, handleCallHeader);
connection.on(EventFilter.CallIVR, handleCallIVR);
connection.on(EventFilter.HandRaiseToggle, handleHandRaiseToggle);
connection.on(EventFilter.MuteToggle, handleMuteToggle);
connection.on(EventFilter.RecvInfo, handleRecvInfo);
Expand All @@ -49,6 +51,34 @@ const handleConnectionFailure = () => {
setTimeout(init, CONNECTION_RETRY_TIMEOUT);
}


const connectToMeeting = (meetingURI: string, participantUuid: string) => {
// Split meetingURI at the first dot. See https://stackoverflow.com/a/4607799/13431526.
const [ roomName, domainBase ] = meetingURI.split(/\.(.+)$/s)

freeswitch.executeAsync('log', `CONSOLE New Jigasi call to Jitsi meeting id ${roomName}`, participantUuid);
domainBase && freeswitch.executeAsync('set', `sip_h_X-Domain-Base=${domainBase}`, participantUuid);
freeswitch.executeAsync('multiset', `sip_h_X-Room-Name=${roomName} originate_timeout=3600`, participantUuid);
freeswitch.executeAsync('bridge', `{absolute_codec_string='OPUS'}[leg_timeout=3600]user/${jigasiSipUri}`, participantUuid);
// setCallState(event, CallState.WaitConference)
};

const handleCallHeader = (event: Event) => (event => {
const { meetingURI, name, participantUuid } = event;

if (meetingURI) {
Log.info(`${name} event with participantUuid (${participantUuid}) and Meeting-URL (${meetingURI}).`);
connectToMeeting(meetingURI, participantUuid);
} else Log.error(`${name} event without a Meeting-URI. This is unsupported.`)
})(new ChannelEvent(event));

const handleCallIVR = (event: Event) => (event => {
const { participantUuid } = event;

freeswitch.executeAsync('answer', '', participantUuid);
freeswitch.executeAsync('play_and_get_digits', `9 10 3 5000 =# ${AudioMessage.EnterYourMeetingId} ${AudioMessage.UnknownMeetingId} meeting_id \\d+`, participantUuid);
})(new ChannelEvent(event));

const handleChannelBridge = (event: Event) => (event => {
const { jigasiUuid, name, participantUuid } = event;

Expand All @@ -67,6 +97,19 @@ const handleChannelBridge = (event: Event) => (event => {
} else Log.error(`${name} event for participantUuid (${participantUuid}) without a jigasiUuid (${jigasiUuid}). This is unexpected.`)
})(new ChannelEvent(event));

const handleChannelExecuteComplete = (event: Event) => (event => {
const { application, meetingId, name, participantUuid } = event;

if (application == 'play_and_get_digits') {
if (typeof meetingId == 'string') {
Log.info(`${name} event of play_and_get_digits with participantUuid (${participantUuid}) and meetingId (${meetingId}).`);

const meetingURI = meetingId;
connectToMeeting(meetingURI, participantUuid);
}
}
})(new ChannelEvent(event));

const handleChannelHangup = (event: Event) => (event => {
const { createdTime, hangupCause, isBridged, isInbound, jigasiUuid, name, participantUuid } = event;

Expand All @@ -83,23 +126,6 @@ const handleChannelHangup = (event: Event) => (event => {
}
})(new ChannelEvent(event));

const handleJigasiCall = (event: Event) => (event => {
const { meetingURI, name, participantUuid } = event;

if (meetingURI) {
Log.info(`${name} event with participantUuid (${participantUuid}) and Meeting-URL (${meetingURI}).`);

// Split meetingURI at the first dot. See https://stackoverflow.com/a/4607799/13431526.
const [ roomName, domainBase ] = meetingURI.split(/\.(.+)$/s)

freeswitch.executeAsync('log', `CONSOLE New Jigasi call to Jitsi meeting id ${roomName}`, participantUuid);
domainBase && freeswitch.executeAsync('set', `sip_h_X-Domain-Base=${domainBase}`, participantUuid);
freeswitch.executeAsync('multiset', `sip_h_X-Room-Name=${roomName} originate_timeout=3600`, participantUuid);
freeswitch.executeAsync('bridge', `{absolute_codec_string='OPUS'}[leg_timeout=3600]user/${jigasiSipUri}`, participantUuid);
setCallState(event, CallState.WaitConference)
} else Log.error(`${name} event without a Meeting-URI. This is unsupported.`)
})(new ChannelEvent(event));

const handleMuteToggle = (event: Event) => (event => {
const { avModeration: avModeration, muted, name, participantUuid } = event;

Expand Down

0 comments on commit b685010

Please sign in to comment.