feat(sipcall) implement sip invite
This commit is contained in:
parent
39011d8fd3
commit
ae21a09bd6
|
@ -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"
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -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,}))$/;
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue