src/conferences/ConferenceManager.js
import {cloneDeep} from 'lodash-es';
import {v4 as uuid} from 'uuid';
import {EventEmitter} from '../eventemitter';
import {bindMethods} from '../utils';
import {Conference} from './Conference';
import {ConferenceLog} from './ConferenceLog';
import {WrongPasswordError} from './errors/WrongPasswordError';
import {AccessBeforeTimeError} from './errors/AccessBeforeTimeError';
import Logs from '../Logs';
const log = Logs.instance.getLogger('SippoJS/calls/ConferenceManager');
/**
* This class provides access to every conference related feature
* This class must never be directly instantiated, instances of this class
* can be obtained using Session#getConferenceManager.
*
* ## Events
*
* - `conferenceInvitation` {@link Conference} - Emitted every time an invitation to
* a conference room is received
*/
export class ConferenceManager {
/**
* @protected
* @param {WacProxy} wacProxy
* @param {UserManager} userManager
*/
static newInstance(wacProxy, userManager) {
return new ConferenceManager(wacProxy, userManager);
}
/**
* @private
*/
constructor(wacProxy, userManager) {
this._conferenceService = wacProxy.getConferenceService();
this._conferenceLogService = wacProxy.getConferenceLogService();
this._kManageService = wacProxy.getKManageService();
this._userManager = userManager;
this._defaultMediaConstraints = {
audio: true,
video: true,
};
this._conferences = new Map();
this._conferenceLog = null;
/** @type {EventEmitter} */
this.emitter = new EventEmitter();
bindMethods(this, [
'_onIncomingConference',
]);
this._bindEventHandlers();
}
/**
* @private
*/
_bindEventHandlers() {
this._conferenceService.on('incomingConference', this._onIncomingConference);
}
/**
* @private
*/
async _onIncomingConference(room, mediaTypes) {
const conference = await this._getConferenceForRoom(room);
await conference.getLocalMediaHandler().setMediaTypes(mediaTypes);
this._conferences.set(room.id, conference);
this.emitter.emit('conferenceInvitation', conference);
}
/**
* @return {MediaStreamConstraints}
*/
getDefaultMediaConstraints() {
return cloneDeep(this._defaultMediaConstraints);
}
/**
* Sets the constraints that will be used as initial value for media requests when
* initializing conferences. For more information about valid values see
* {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints}
* @example <caption>Enabling noise reduction techniques</caption>
* conferenceManager.setDefaultMediaConstraints({
* audio: {
* autoGainControl: true,
* echoCancellation: true,
* noiseSuppression: true,
* },
* video: true,
* });
* @param {MediaStreamConstraints} defaultMediaConstraints
*/
setDefaultMediaConstraints(defaultMediaConstraints) {
this._defaultMediaConstraints = cloneDeep(defaultMediaConstraints);
}
/**
* Creates a new conference room
* @return {Promise<Conference>}
*/
async createConference() {
const room = await this._conferenceService.create();
return this._getConferenceForRoom(room);
}
/**
* Creates a new conference and invites an agent to it
* @param {MediaTypes} mediaTypes the media types that will be used to call an agent
* @param {Object} [contextInfo={}] arbitrary context that can be sent to an agent
* @return {Promise<Conference>}
*/
async callAgent(mediaTypes, contextInfo = {}) {
const conference = await this.conferenceManager.createConference();
await conference.join();
const agent = `wac-agent:${uuid()}`;
const response = await conference.inviteParticipant(
agent,
undefined,
mediaTypes,
undefined,
undefined,
undefined,
contextInfo,
);
if (response.code < 200 || response.code >= 300) {
throw new Error('Error calling an agent');
}
return conference;
}
/**
* @return {ConferenceLog}
*/
getConferenceLog() {
if (this._conferenceLog === null) {
this._conferenceLog = new ConferenceLog(this._conferenceLogService);
}
return this._conferenceLog;
}
async _getConferenceForRoom(room) {
return Conference.create(this._userManager, room, this._kManageService, this._defaultMediaConstraints);
}
/**
* Asks the server for the current invitations available to the user
* @return {Promise<Conference[]>}
*/
getCurrentInvitations() {
log.debug('calls/ConferenceManager.js/getCurrentInvitations');
return this._conferenceService.getCurrentInvitations().then((rooms) => {
log.debug('calls/ConferenceManager.js/getCurrentInvitations/rooms', rooms);
let conferences = rooms.map(async (room) => {
const conference = await this._getConferenceForRoom(room);
await conference.getLocalMediaHandler().setMediaTypes(room.invite.mediatypes);
this._conferences.set(room.id, conference);
this.emitter.emit('conferenceInvitation', conference);
return conference;
});
return Promise.all(conferences);
});
}
/**
* Get a Conference by uri
*
* @param {string} uri
* @returns {Promise<Conference>} fulfilled when the response is received
* @deprecated Use "getMeetingConference" or "getQuickConference" instead
*/
async getConferenceByUri(uri) {
const room = await this._conferenceService.getRoomByUri(uri);
return this._getConferenceForRoom(room);
}
/**
* Get a conference for a meeting
*
* @param {string} meetingId
* @param {string} [password]
* @returns {Promise<Conference>} fulfilled when the response is received
* @throws {AccessBeforeTimeError} thrown when trying to access the conference before scheduled time
* @throws {WrongPasswordError} thrown when the conference is protected by password and the password is not recived or is not valid.
*/
async getMeetingConference(meetingId, password) {
try {
const conferenceRoom = await this._conferenceService.getMeetingConference(meetingId, password);
return await this._getConferenceForRoom(conferenceRoom);
} catch (error) {
switch (error.message) {
case 410:
throw new AccessBeforeTimeError();
case 403:
throw new WrongPasswordError();
default:
throw error;
}
}
}
/**
* Gets a quick conference
*
* @param {string} quickConferenceId
* @returns {Promise<Conference>} fulfilled when the response is received
*
*/
async getQuickConference(quickConferenceId) {
const conferenceRoom = await this._conferenceService.getQuickConference(quickConferenceId);
return await this._getConferenceForRoom(conferenceRoom);
}
}