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",
"shareLink": "Share the meeting link to invite others",
"shareStream": "Share the live streaming link",
"sip": "SIP: {{address}}",
"telephone": "Telephone: {{number}}",
"title": "Invite people to this meeting",
"yahooEmail": "Yahoo Email"

View File

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

View File

@ -12,7 +12,8 @@ import {
getInviteResultsForQuery,
getInviteTypeCounts,
isAddPeopleEnabled,
isDialOutEnabled
isDialOutEnabled,
isSipInviteEnabled
} from '../../functions';
import logger from '../../logger';
@ -38,6 +39,11 @@ export type Props = {
*/
_dialOutEnabled: boolean,
/**
* Whether or not to allow sip invites.
*/
_sipInviteEnabled: boolean,
/**
* 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
* 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
* 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
@ -214,7 +220,8 @@ export default class AbstractAddPeopleDialog<P: Props, S: State>
_dialOutEnabled: dialOutEnabled,
_jwt: jwt,
_peopleSearchQueryTypes: peopleSearchQueryTypes,
_peopleSearchUrl: peopleSearchUrl
_peopleSearchUrl: peopleSearchUrl,
_sipInviteEnabled: sipInviteEnabled
} = this.props;
const options = {
addPeopleEnabled,
@ -222,7 +229,8 @@ export default class AbstractAddPeopleDialog<P: Props, S: State>
dialOutEnabled,
jwt,
peopleSearchQueryTypes,
peopleSearchUrl
peopleSearchUrl,
sipInviteEnabled
};
return getInviteResultsForQuery(query, options);
@ -259,6 +267,7 @@ export function _mapStateToProps(state: Object) {
_dialOutEnabled: isDialOutEnabled(state),
_jwt: state['features/base/jwt'].jwt,
_peopleSearchQueryTypes: peopleSearchQueryTypes,
_peopleSearchUrl: peopleSearchUrl
_peopleSearchUrl: peopleSearchUrl,
_sipInviteEnabled: isSipInviteEnabled(state)
};
}

View File

@ -285,7 +285,7 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
*/
_parseQueryResults(response = []) {
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 = [];
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 [
...userDisplayItems,
...numberDisplayItems
...numberDisplayItems,
...sipDisplayItems
];
}

View File

@ -42,3 +42,9 @@ export const OUTGOING_CALL_RINGING_SOUND_ID = 'OUTGOING_CALL_RINGING_SOUND_ID';
* @type {string}
*/
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 { isVpaasMeeting } from '../billing-counter/functions';
import { EMAIL_ADDRESS_REGEX } from './constants';
import logger from './logger';
declare var $: Function;
@ -122,6 +123,11 @@ export type GetInviteResultsOptions = {
*/
peopleSearchUrl: string,
/**
* Whether or not to check sip invites.
*/
sipInviteEnabled: boolean,
/**
* The jwt token to pass to the search service.
*/
@ -149,6 +155,7 @@ export function getInviteResultsForQuery(
dialOutEnabled,
peopleSearchQueryTypes,
peopleSearchUrl,
sipInviteEnabled,
jwt
} = options;
@ -233,6 +240,13 @@ export function getInviteResultsForQuery(
});
}
if (sipInviteEnabled && isASipAddress(text)) {
results.push({
type: 'sip',
address: text
});
}
return results;
});
}
@ -374,6 +388,21 @@ export function isDialOutEnabled(state: Object): boolean {
&& 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.
*
@ -392,6 +421,16 @@ function isMaybeAPhoneNumber(text: string): boolean {
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.
*
@ -764,3 +803,47 @@ export function isSharingEnabled(sharingFeature: string) {
|| typeof interfaceConfig.SHARING_FEATURES === 'undefined'
|| (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'
}
}
);
}