feat(invite): send DTMF tones if specified as part of phone number text

If phone number to be invited into the conference ends with a comma then
the following part will be stored in redux state and sent later as DTMF
tones after Jigasi connects to the conference.
This commit is contained in:
paweldomas 2019-10-03 13:57:25 -05:00
parent 8dc0f30a49
commit bc54a87f2e
7 changed files with 134 additions and 3 deletions

View File

@ -61,6 +61,15 @@ export const SET_DIAL_IN_SUMMARY_VISIBLE = 'SET_DIAL_IN_SUMMARY_VISIBLE';
*/
export const SET_INVITE_DIALOG_VISIBLE = 'SET_INVITE_DIALOG_VISIBLE';
/**
* The type of Redux action which stores pending dtmf. Check out the {@link storePendingDTMF} action for more details.
* {
* type: STORE_PENDING_DTMF,
* pendingDtmf: ?string
* }
*/
export const STORE_PENDING_DTMF = 'STORE_PENDING_DTMF';
/**
* The type of the action which signals an error occurred while requesting dial-
* in numbers.

View File

@ -13,13 +13,15 @@ import {
SET_CALLEE_INFO_VISIBLE,
SET_DIAL_IN_SUMMARY_VISIBLE,
SET_INVITE_DIALOG_VISIBLE,
STORE_PENDING_DTMF,
UPDATE_DIAL_IN_NUMBERS_FAILED,
UPDATE_DIAL_IN_NUMBERS_SUCCESS
} from './actionTypes';
import {
getDialInConferenceID,
getDialInNumbers,
invitePeopleAndChatRooms
invitePeopleAndChatRooms,
splitPhoneNumberAndDTMF
} from './functions';
import logger from './logger';
@ -98,7 +100,12 @@ export function invite(
// For each number, dial out. On success, remove the number from
// {@link invitesLeftToSend}.
const phoneInvitePromises = phoneNumbers.map(item => {
const numberToInvite = item.number;
const {
dtmf,
phoneNumber: numberToInvite
} = splitPhoneNumberAndDTMF(item.number);
dtmf && dispatch(storePendingDTMF(dtmf));
return conference.dial(numberToInvite)
.then(() => {
@ -157,6 +164,25 @@ export function invite(
};
}
/**
* Stores pending DTMF tones to be sent after a phone connection is established. If more than one phone numbers are
* invited into the conference then weird things may happen, as there's no way to match participant with the invited
* phone number.
*
* @param {string} pendingDtmf - A string with digits and dtmf characters that will be sent after Jigasi participant
* joins the conference and enters the {@link CONNECTED_USER} state.
* @returns {{
* pendingDtmf: ?string,
* type: STORE_PENDING_DTMF
* }}
*/
export function storePendingDTMF(pendingDtmf: ?string) {
return {
type: STORE_PENDING_DTMF,
pendingDtmf
};
}
/**
* Sends AJAX requests for dial-in numbers and conference ID.
*

View File

@ -340,7 +340,9 @@ function isMaybeAPhoneNumber(text: string): boolean {
* @returns {RegExp}
*/
function isPhoneNumberRegex(): RegExp {
let regexString = '^[0-9+()-\\s]*$';
// The ',' '*' '#' characters are allowed to be able to specify pending DTMF tones to be dialed just after the phone
// connection is established.
let regexString = '^[0-9+()-\\s,*#]*$';
if (typeof interfaceConfig !== 'undefined') {
regexString = interfaceConfig.PHONE_NUMBER_REGEX || regexString;
@ -349,6 +351,37 @@ function isPhoneNumberRegex(): RegExp {
return new RegExp(regexString);
}
/**
* Extracts the DTMF part out of the phone number text. The DTMF is to be specified after a comma and must contain
* only DTMF tone characters for example: "+12223334444,23,4*9#".
*
* @param {string} phoneNumberText - See the text above.
* @returns {{
* phoneNumber: string,
* dtmf: ?string
* }}
*/
export function splitPhoneNumberAndDTMF(phoneNumberText: string) {
const firstCommaIdx = phoneNumberText.indexOf(',');
let dtmf, phoneNumber = phoneNumberText;
if (firstCommaIdx !== -1) {
dtmf = phoneNumberText.substr(firstCommaIdx);
phoneNumber = phoneNumberText.substr(0, firstCommaIdx);
// There's no point in playing just commas, so clear the dtmf var
if (!dtmf.replace(',', '').length) {
dtmf = undefined;
}
}
return {
phoneNumber,
dtmf
};
}
/**
* Sends an ajax request to a directory service.
*

View File

@ -4,3 +4,4 @@ export * from './functions';
import './reducer';
import './middleware';
import './pendingDtmfMiddleware';

View File

@ -0,0 +1,39 @@
import { CONNECTED_USER } from '../presence-status';
import { getCurrentConference } from '../base/conference';
import { StateListenerRegistry } from '../base/redux';
import { storePendingDTMF } from './actions';
import logger from './logger';
import { getPendingDtmf } from './selectors';
StateListenerRegistry.register(
state => {
const jigasiParticipantId
= state['features/base/participants']
.find(p => p.isJigasi && p.presence?.toLowerCase() === CONNECTED_USER)
?.id;
return jigasiParticipantId;
},
(jigasiParticipantId, { getState, dispatch }) => {
const state = getState();
const pendingDtmf = jigasiParticipantId && getPendingDtmf(state);
const conference = getCurrentConference(state);
if (conference && pendingDtmf) {
logger.info('Sending pending DTMF tones');
conference.sendTones(pendingDtmf);
dispatch(storePendingDTMF(undefined));
}
}
);
StateListenerRegistry.register(
state => getCurrentConference(state),
(conference, { dispatch }, prevConference) => {
if (prevConference && conference !== prevConference) {
dispatch(storePendingDTMF(undefined));
}
}
);

View File

@ -8,6 +8,7 @@ import {
SET_CALLEE_INFO_VISIBLE,
SET_DIAL_IN_SUMMARY_VISIBLE,
SET_INVITE_DIALOG_VISIBLE,
STORE_PENDING_DTMF,
UPDATE_DIAL_IN_NUMBERS_FAILED,
UPDATE_DIAL_IN_NUMBERS_SUCCESS
} from './actionTypes';
@ -23,6 +24,13 @@ const DEFAULT_STATE = {
calleeInfoVisible: false,
inviteDialogVisible: false,
numbersEnabled: true,
/**
* Pending DTMF tones. See {@link storePendingDTMF} for more info.
*
* @type {string|undefined}
*/
pendingDtmf: undefined,
pendingInviteRequests: []
};
@ -62,6 +70,12 @@ ReducerRegistry.register('features/invite', (state = DEFAULT_STATE, action) => {
inviteDialogVisible: action.visible
};
case STORE_PENDING_DTMF:
return {
...state,
pendingDtmf: action.pendingDtmf
};
case UPDATE_DIAL_IN_NUMBERS_FAILED:
return {
...state,

View File

@ -0,0 +1,9 @@
/**
* Retrieves pending DTMF tones if any. See {@link storePendingDTMF} for more info.
*
* @param {Object} state - The Redux state.
* @returns {string}
*/
export function getPendingDtmf(state) {
return state['features/invite'].pendingDtmf;
}