feat(sipcall) implement sip invite

This commit is contained in:
Tudor-Ovidiu Avram 2021-03-18 15:32:14 +02:00
parent 39011d8fd3
commit ae21a09bd6
6 changed files with 141 additions and 9 deletions

View File

@ -28,6 +28,7 @@
"shareInvite": "Share meeting invitation", "shareInvite": "Share meeting invitation",
"shareLink": "Share the meeting link to invite others", "shareLink": "Share the meeting link to invite others",
"shareStream": "Share the live streaming link", "shareStream": "Share the live streaming link",
"sip": "SIP: {{address}}",
"telephone": "Telephone: {{number}}", "telephone": "Telephone: {{number}}",
"title": "Invite people to this meeting", "title": "Invite people to this meeting",
"yahooEmail": "Yahoo Email" "yahooEmail": "Yahoo Email"

View File

@ -3,7 +3,7 @@
import type { Dispatch } from 'redux'; import type { Dispatch } from 'redux';
import { getInviteURL } from '../base/connection'; import { getInviteURL } from '../base/connection';
import { getParticipants } from '../base/participants'; import { getLocalParticipant, getParticipants } from '../base/participants';
import { inviteVideoRooms } from '../videosipgw'; import { inviteVideoRooms } from '../videosipgw';
import { import {
@ -18,7 +18,8 @@ import {
import { import {
getDialInConferenceID, getDialInConferenceID,
getDialInNumbers, getDialInNumbers,
invitePeopleAndChatRooms invitePeopleAndChatRooms,
inviteSipEndpoints
} from './functions'; } from './functions';
import logger from './logger'; import logger from './logger';
@ -102,7 +103,9 @@ export function invite(
inviteServiceCallFlowsUrl inviteServiceCallFlowsUrl
} = state['features/base/config']; } = state['features/base/config'];
const inviteUrl = getInviteURL(state); const inviteUrl = getInviteURL(state);
const { sipInviteUrl } = state['features/base/config'];
const { jwt } = state['features/base/jwt']; const { jwt } = state['features/base/jwt'];
const { name: displayName } = getLocalParticipant(state);
// First create all promises for dialing out. // First create all promises for dialing out.
const phoneNumbers const phoneNumbers
@ -164,6 +167,20 @@ export function invite(
invitesLeftToSend invitesLeftToSend
= invitesLeftToSend.filter(({ type }) => type !== 'videosipgw'); = invitesLeftToSend.filter(({ type }) => type !== 'videosipgw');
const sipEndpoints
= invitesLeftToSend.filter(({ type }) => type === 'sip');
conference && inviteSipEndpoints(
sipEndpoints,
sipInviteUrl,
jwt,
conference.options.name,
displayName
);
invitesLeftToSend
= invitesLeftToSend.filter(({ type }) => type !== 'sip');
return ( return (
Promise.all(allInvitePromises) Promise.all(allInvitePromises)
.then(() => invitesLeftToSend)); .then(() => invitesLeftToSend));

View File

@ -12,7 +12,8 @@ import {
getInviteResultsForQuery, getInviteResultsForQuery,
getInviteTypeCounts, getInviteTypeCounts,
isAddPeopleEnabled, isAddPeopleEnabled,
isDialOutEnabled isDialOutEnabled,
isSipInviteEnabled
} from '../../functions'; } from '../../functions';
import logger from '../../logger'; import logger from '../../logger';
@ -38,6 +39,11 @@ export type Props = {
*/ */
_dialOutEnabled: boolean, _dialOutEnabled: boolean,
/**
* Whether or not to allow sip invites.
*/
_sipInviteEnabled: boolean,
/** /**
* The JWT token. * The JWT token.
*/ */
@ -96,7 +102,7 @@ export default class AbstractAddPeopleDialog<P: Props, S: State>
/** /**
* Invite people and numbers to the conference. The logic works by inviting * Invite people and numbers to the conference. The logic works by inviting
* numbers, people/rooms, and videosipgw in parallel. All invitees are * numbers, people/rooms, sip endpoints and videosipgw in parallel. All invitees are
* stored in an array. As each invite succeeds, the invitee is removed * stored in an array. As each invite succeeds, the invitee is removed
* from the array. After all invites finish, close the modal if there are * from the array. After all invites finish, close the modal if there are
* no invites left to send. If any are left, that means an invite failed * no invites left to send. If any are left, that means an invite failed
@ -214,7 +220,8 @@ export default class AbstractAddPeopleDialog<P: Props, S: State>
_dialOutEnabled: dialOutEnabled, _dialOutEnabled: dialOutEnabled,
_jwt: jwt, _jwt: jwt,
_peopleSearchQueryTypes: peopleSearchQueryTypes, _peopleSearchQueryTypes: peopleSearchQueryTypes,
_peopleSearchUrl: peopleSearchUrl _peopleSearchUrl: peopleSearchUrl,
_sipInviteEnabled: sipInviteEnabled
} = this.props; } = this.props;
const options = { const options = {
addPeopleEnabled, addPeopleEnabled,
@ -222,7 +229,8 @@ export default class AbstractAddPeopleDialog<P: Props, S: State>
dialOutEnabled, dialOutEnabled,
jwt, jwt,
peopleSearchQueryTypes, peopleSearchQueryTypes,
peopleSearchUrl peopleSearchUrl,
sipInviteEnabled
}; };
return getInviteResultsForQuery(query, options); return getInviteResultsForQuery(query, options);
@ -259,6 +267,7 @@ export function _mapStateToProps(state: Object) {
_dialOutEnabled: isDialOutEnabled(state), _dialOutEnabled: isDialOutEnabled(state),
_jwt: state['features/base/jwt'].jwt, _jwt: state['features/base/jwt'].jwt,
_peopleSearchQueryTypes: peopleSearchQueryTypes, _peopleSearchQueryTypes: peopleSearchQueryTypes,
_peopleSearchUrl: peopleSearchUrl _peopleSearchUrl: peopleSearchUrl,
_sipInviteEnabled: isSipInviteEnabled(state)
}; };
} }

View File

@ -285,7 +285,7 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
*/ */
_parseQueryResults(response = []) { _parseQueryResults(response = []) {
const { t, _dialOutEnabled } = this.props; const { t, _dialOutEnabled } = this.props;
const users = response.filter(item => item.type !== 'phone'); const users = response.filter(item => item.type !== 'phone' && item.type !== 'sip');
const userDisplayItems = []; const userDisplayItems = [];
for (const user of users) { for (const user of users) {
@ -348,9 +348,25 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
}; };
}); });
const sipAddresses = response.filter(item => item.type === 'sip');
const sipDisplayItems = sipAddresses.map(sip => {
return {
filterValues: [
sip.address
],
content: t('addPeople.sip', { address: sip.address }),
description: '',
item: sip,
value: sip.address
};
});
return [ return [
...userDisplayItems, ...userDisplayItems,
...numberDisplayItems ...numberDisplayItems,
...sipDisplayItems
]; ];
} }

View File

@ -42,3 +42,9 @@ export const OUTGOING_CALL_RINGING_SOUND_ID = 'OUTGOING_CALL_RINGING_SOUND_ID';
* @type {string} * @type {string}
*/ */
export const OUTGOING_CALL_START_SOUND_ID = 'OUTGOING_CALL_START_SOUND_ID'; export const OUTGOING_CALL_START_SOUND_ID = 'OUTGOING_CALL_START_SOUND_ID';
/**
* Regex for matching email addresses.
*/
// eslint-disable-next-line max-len
export const EMAIL_ADDRESS_REGEX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

View File

@ -10,6 +10,7 @@ import { toState } from '../base/redux';
import { doGetJSON, parseURIString } from '../base/util'; import { doGetJSON, parseURIString } from '../base/util';
import { isVpaasMeeting } from '../billing-counter/functions'; import { isVpaasMeeting } from '../billing-counter/functions';
import { EMAIL_ADDRESS_REGEX } from './constants';
import logger from './logger'; import logger from './logger';
declare var $: Function; declare var $: Function;
@ -122,6 +123,11 @@ export type GetInviteResultsOptions = {
*/ */
peopleSearchUrl: string, peopleSearchUrl: string,
/**
* Whether or not to check sip invites.
*/
sipInviteEnabled: boolean,
/** /**
* The jwt token to pass to the search service. * The jwt token to pass to the search service.
*/ */
@ -149,6 +155,7 @@ export function getInviteResultsForQuery(
dialOutEnabled, dialOutEnabled,
peopleSearchQueryTypes, peopleSearchQueryTypes,
peopleSearchUrl, peopleSearchUrl,
sipInviteEnabled,
jwt jwt
} = options; } = options;
@ -233,6 +240,13 @@ export function getInviteResultsForQuery(
}); });
} }
if (sipInviteEnabled && isASipAddress(text)) {
results.push({
type: 'sip',
address: text
});
}
return results; return results;
}); });
} }
@ -374,6 +388,21 @@ export function isDialOutEnabled(state: Object): boolean {
&& conference && conference.isSIPCallingSupported(); && conference && conference.isSIPCallingSupported();
} }
/**
* Determines if inviting sip endpoints is enabled or not.
*
* @param {Object} state - Current state.
* @returns {boolean} Indication of whether dial out is currently enabled.
*/
export function isSipInviteEnabled(state: Object): boolean {
const { sipInviteUrl } = state['features/base/config'];
const { features = {} } = getLocalParticipant(state);
return state['features/base/jwt'].jwt
&& Boolean(sipInviteUrl)
&& String(features['sip-outbound-call']) === 'true';
}
/** /**
* Checks whether a string looks like it could be for a phone number. * Checks whether a string looks like it could be for a phone number.
* *
@ -392,6 +421,16 @@ function isMaybeAPhoneNumber(text: string): boolean {
return Boolean(digits.length); return Boolean(digits.length);
} }
/**
* Checks whether a string matches a sip address format.
*
* @param {string} text - The text to check.
* @returns {boolean} True if provided text matches a sip address format.
*/
function isASipAddress(text: string): boolean {
return EMAIL_ADDRESS_REGEX.test(text);
}
/** /**
* RegExp to use to determine if some text might be a phone number. * RegExp to use to determine if some text might be a phone number.
* *
@ -764,3 +803,47 @@ export function isSharingEnabled(sharingFeature: string) {
|| typeof interfaceConfig.SHARING_FEATURES === 'undefined' || typeof interfaceConfig.SHARING_FEATURES === 'undefined'
|| (interfaceConfig.SHARING_FEATURES.length && interfaceConfig.SHARING_FEATURES.indexOf(sharingFeature) > -1); || (interfaceConfig.SHARING_FEATURES.length && interfaceConfig.SHARING_FEATURES.indexOf(sharingFeature) > -1);
} }
/**
* Sends a post request to an invite service.
*
* @param {Immutable.List} inviteItems - The list of the "sip" type items to invite.
* @param {string} sipInviteUrl - The invite service that generates the invitation.
* @param {string} jwt - The jwt token.
* @param {string} roomName - The name to the conference.
* @param {string} displayName - The user display name.
* @returns {Promise} - The promise created by the request.
*/
export function inviteSipEndpoints( // eslint-disable-line max-params
inviteItems: Array<Object>,
sipInviteUrl: string,
jwt: string,
roomName: string,
displayName: string
): Promise<void> {
if (inviteItems.length === 0) {
return Promise.resolve();
}
return fetch(
`${sipInviteUrl}?token=${jwt}`,
{
body: JSON.stringify({
callParams: {
callUrlInfo: {
baseUrl: window.location.origin,
callName: roomName
}
},
sipClientParams: {
displayName,
sipAddress: inviteItems.map(item => item.address)
}
}),
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
}
);
}