import {camelCase} from 'lodash'; import PermissionError from '../common/errors/permission'; import {CONTROLS, HTTP_VERBS} from '../constants'; import MeetingRequest from '../meeting/request'; import LoggerProxy from '../common/logs/logger-proxy'; import {Control, Setting} from './enums'; import {ControlConfig} from './types'; import Util from './util'; import {CAN_SET, CAN_UNSET, ENABLED} from './constants'; /** * docs * https://sqbu-github.cisco.com/pages/WebExSquared/locus/guides/mute.html * https://confluence-eng-gpk2.cisco.com/conf/display/LOCUS/Hard+Mute+and+Audio+Privacy#HardMuteandAudioPrivacy-SelfMuteonEntry * https://confluence-eng-gpk2.cisco.com/conf/pages/viewpage.action?spaceKey=UC&title=WEBEX-124454%3A+UCF%3A+Hard+mute+support+for+Teams+joining+Webex+meeting * https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-180867 * https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-393351 */ /** * @description ControlsOptionsManager is responsible for handling the behavior of participant controls when somebody joins a meeting * @export * @private * @class Recording */ export default class ControlsOptionsManager { /** * @instance * @type {MeetingRequest} * @private * @memberof ControlsOptionsManager */ private request: MeetingRequest; /** * @instance * @type {Array} * @private * @memberof ControlsOptionsManager */ private displayHints: Array = []; /** * @instance * @type {string} * @private * @memberof ControlsOptionsManager */ private locusUrl: string; /** * @instance * @type {string} * @private * @memberof ControlsOptionsManager */ private mainLocusUrl: string; /** * @param {MeetingRequest} request * @param {Object} options * @constructor * @memberof ControlsOptionsManager */ constructor( request: MeetingRequest, options?: { locusUrl: string; displayHints?: Array; } ) { this.initialize(request); this.set(options); } /** * @param {MeetingRequest} request * @returns {void} * @private * @memberof ControlsOptionsManager */ private initialize(request: MeetingRequest) { this.request = request; } /** * @param {Object} options * @returns {void} * @public * @memberof ControlsOptionsManager */ public set(options?: {locusUrl: string; displayHints?: Array}) { this.extract(options); } /** * @param {string} url * @param {boolean} isMainLocus * @returns {void} * @public * @memberof ControlsOptionsManager */ public setLocusUrl(url: string, isMainLocus?: boolean) { this.locusUrl = url; if (isMainLocus) { this.mainLocusUrl = url; } } /** * @param {Array} hints * @returns {void} * @public * @memberof ControlsOptionsManager */ public setDisplayHints(hints: Array) { this.displayHints = hints; } /** * @returns {string} * @public * @memberof ControlsOptionsManager */ public getLocusUrl() { return this.locusUrl; } /** * @returns {Array} * @public * @memberof ControlsOptionsManager */ public getDisplayHints() { return this.displayHints; } /** * @param {Object} options * @returns {void} * @private * @memberof ControlsOptionsManager */ private extract(options?: {locusUrl: string; displayHints?: Array}) { this.setDisplayHints(options?.displayHints); this.setLocusUrl(options?.locusUrl); } /** * Set controls for this meeting. * * @param {Array} controls - Spread Array of ControlConfigs * @returns {Promise>}- Promise resolving if the request was successful. */ public update(...controls: Array) { const payloads = controls.map((control) => { if (!Object.keys(Control).includes(control.scope)) { throw new Error( `updating meeting control scope "${control.scope}" is not a supported scope` ); } if (!Util.canUpdate(control, this.displayHints)) { throw new PermissionError( `updating meeting control scope "${control.scope}" not allowed, due to moderator property.` ); } return { [control.scope]: control.properties, }; }); return payloads.reduce((previous, payload) => { const extraBody = this.mainLocusUrl && this.mainLocusUrl !== this.locusUrl ? {authorizingLocusUrl: this.locusUrl} : {}; return previous.then(() => // @ts-ignore this.request.request({ uri: `${this.mainLocusUrl || this.locusUrl}/${CONTROLS}`, body: {...payload, ...extraBody}, method: HTTP_VERBS.PATCH, }) ); }, Promise.resolve()); } /** * @param {Setting} setting * @private * @memberof ControlsOptionsManager * @returns {Promise} */ private setControls(setting: { [Setting.muted]?: boolean; [Setting.disallowUnmute]?: boolean; [Setting.muteOnEntry]?: boolean; [Setting.roles]?: Array; }): Promise { LoggerProxy.logger.log( `ControlsOptionsManager:index#setControls --> ${JSON.stringify(setting)}` ); const body: Record = {}; let error: PermissionError; let shouldSkipCheckToMergeBody = false; Object.entries(setting).forEach(([key, value]) => { if ( !shouldSkipCheckToMergeBody && value !== undefined && !Util?.[`${value ? CAN_SET : CAN_UNSET}${key}`](this.displayHints) ) { error = new PermissionError(`${key} [${value}] not allowed, due to moderator property.`); } if (error) { return; } switch (key) { case Setting.muted: shouldSkipCheckToMergeBody = true; body.audio = body.audio ? {...body.audio, [camelCase(key)]: value} : {[camelCase(key)]: value}; break; case Setting.disallowUnmute: case Setting.muteOnEntry: if (Object.keys(setting).includes(Setting.muted)) { body.audio = body.audio ? {...body.audio, [camelCase(key)]: value} : {[camelCase(key)]: value}; body.audio[camelCase(key)] = value; } else { body[camelCase(key)] = {[ENABLED]: value}; } break; case Setting.roles: if (Array.isArray(value)) { body.audio = body.audio ? {...body.audio, [camelCase(key)]: value} : {[camelCase(key)]: value}; } break; default: error = new PermissionError(`${key} [${value}] not allowed, due to moderator property.`); } }); if (error) { return Promise.reject(error); } const extraBody = this.mainLocusUrl && this.mainLocusUrl !== this.locusUrl ? {authorizingLocusUrl: this.locusUrl} : {}; // @ts-ignore return this.request.request({ uri: `${this.mainLocusUrl || this.locusUrl}/${CONTROLS}`, body: {...body, ...extraBody}, method: HTTP_VERBS.PATCH, }); } /** * @public * @param {boolean} enabled * @memberof ControlsOptionsManager * @returns {Promise} */ public setMuteOnEntry(enabled: boolean): Promise { return this.setControls({[Setting.muteOnEntry]: enabled}); } /** * @public * @param {boolean} enabled * @memberof ControlsOptionsManager * @returns {Promise} */ public setDisallowUnmute(enabled: boolean): Promise { return this.setControls({[Setting.disallowUnmute]: enabled}); } /** * @public * @param {boolean} mutedEnabled * @param {boolean} disallowUnmuteEnabled * @param {boolean} muteOnEntryEnabled * @param {array} roles which should be muted * @memberof ControlsOptionsManager * @returns {Promise} */ public setMuteAll( mutedEnabled: boolean, disallowUnmuteEnabled: boolean, muteOnEntryEnabled: boolean, roles: Array ): Promise { return this.setControls({ [Setting.muted]: mutedEnabled, [Setting.disallowUnmute]: disallowUnmuteEnabled, [Setting.muteOnEntry]: muteOnEntryEnabled, [Setting.roles]: roles, }); } }