Home Reference Source

src/conferences/ConferenceManager.js

import {cloneDeep} from 'lodash-es';
import {v4 as uuid} from 'uuid';

import Logs from '../Logs';
import {EventEmitter} from '../eventemitter';
import {bindMethods} from '../utils/bindMethods';

import {Conference} from './Conference';
import {ConferenceLog} from './ConferenceLog';
import {AccessBeforeTimeError} from './errors/AccessBeforeTimeError';
import {WrongPasswordError} from './errors/WrongPasswordError';

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, permissionManager) {
		return new ConferenceManager(wacProxy, userManager, permissionManager);
	}

	/**
	 * @private
	 */
	constructor(wacProxy, userManager, permissionManager) {
		this._conferenceService = wacProxy.getConferenceService();
		this._conferenceLogService = wacProxy.getConferenceLogService();
		this._kManageService = wacProxy.getKManageService();
		this._userManager = userManager;
		this._permissionManager = permissionManager;
		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
	 * @param {object} options
	 * @param {boolean} options.recording boolean indicating whether the conference is going to be recorded or not. This applies
	 * only if the user has {@link PermissionManager.canSetRecordOption} permission
	 * @return {Promise<Conference>}
	 */
	async createConference(options = {recording: false}) {
		let recording;
		try {
			recording = this._permissionManager.canSetRecordOption() ? options.recording : this._permissionManager.canAlwaysRecord();
		} catch (error) {
			log.error('Error acquiring permissions: ', error.message);
			recording = false;
		}
		const room = await this._conferenceService.create(recording);
		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);
			const 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);
	}
}