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